0%

shell笔记

参考自https://www.runoob.com/linux/linux-shell.html

运行shell脚本

  1. 作为可执行程序, ./test.sh, 注意添加#!/bin/bash标记, 打开一个子shell来读取并执行test.sh中的命令, 需要”执行权限”

  2. 作为解释器参数bash test.sh, 打开一个子shell来读取并执行test.sh中的命令, 可以无”执行权限”

  3. source命令, source test.sh, 在当前bash环境下读取并执行test.sh中的命令, 可以无”执行权限”

shell变量

赋值

  1. 显式赋值

注意: 变量名和等号之间不能有空格

your_name="didong"

  1. 语句赋值
1
2
3
for file in `ls /etc`

for file in $(ls /etc)

使用

1
2
3
4
your_name="didong"
echo $your_name

echo ${your_name}

注意: 花括号可选, 但花括号可以用来识别变量边界, 比如:

1
2
3
for skill in Ada Coffe Action Java; do
echo "I am good at ${skill}Script"
done

只读变量

readonly your_name, 将变量设为只读

删除变量

unset your_name, 删除变量, 但不能删除只读变量

变量类型

  1. 局部变量

在脚本或命令中定义, 仅在当前shell示例中有效, 其他shell启动的程序不能访问局部变量

  1. 环境变量

所有的程序, 包括shell启动的程序, 都能访问环境变量, 有些程序需要环境变量来保证其正常运行; 必要的时候shell脚本也可以定义环境变量.

  1. shell变量

shell变量是由shell程序设置的特殊变量; shell变量中有一部分是环境变量, 有一部分是局部变量, 这些变量保证了shell的正常运行

字符串

单引号

限制:

  • 单引号里的任何字符都会原样输出, 单引号字符串中的变量是无效的
  • 单引号字串中不能出现单独一个单引号(对单引号使用转义符后也不行), 但可成对出现, 作为字符串拼接使用

双引号

优点:

  • 双引号里可以有变量
  • 双引号里可以出现转义字符

拼接字符串

1
2
3
4
5
6
7
8
9
your_name="runoob"
# 使用双引号拼接
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting $greeting_1
# 使用单引号拼接
greeting_2='hello, '$your_name' !'
greeting_3='hello, ${your_name} !'
echo $greeting_2 $greeting_3

输出结果:

1
2
hello, runoob ! hello, runoob !
hello, runoob ! hello, ${your_name} !

获取字符串长度

1
2
string="abcd"
echo ${#string} #输出 4

或使用length:

1
2
string="hello, everyone my name is didong"
expr length "$string"

使用expr命令时, 表达式中的运算符左右必须包含空格, 如果不包含空格, 将会输出表达式本身
对于某些运算符, 还需要我们使用\进行转义, 否则会提示语法错误, 如*

expr命令

expr命令是一个手工命令行计数器, 用于求表达式变量的值, 一般用于整数值, 也可用于字符串

语法: expr 表达式

详细内容直接man

提取子字符串

从字符串第 2 个字符开始截取 4 个字符(从0开始计数):

1
2
3
4
5
string="runoob is a great site"
echo ${string:1:4} # 输出 unoo
echo ${string:4} # 输出 runo
echo ${string:0-4:3} # 0-4表示从右边算起第四个字符开始, 3表示字符个数, 输出sit
echo ${string:0-4} # 输出site

字符串截取

设有变量var=http://qiniu.wangqy.top/didong/images/

  1. #号截取, 删除左边字符, 保留右边字符

echo ${var#*//}

从左边开始删除第一个//号及左边的所有字符

  1. ##号截取, 删除左边字符, 保留右边字符

echo ${var##*/}

从左边开始删除最后(最右边)一个/号及左边所有字符

  1. %号截取, 删除右边字符, 保留左边字符

echo ${var%/*}

从左边开始删除第一个/号及右边字符

  1. %%号截取, 删除右边字符, 保留左边字符

echo ${var%%/*}

从左边开始, 删除最后(最左边)一个/号及右边的字符

查找子字符

查找字符 i 或 o 的位置(哪个字母先出现就计算哪个):

1
2
string="runoob is a great site"
echo `expr index "$string" io` # 输出 4

注意: 这里的输出是从 1 开始计数

数组

只支持一维数组, 不支持多位数组, 并且没有限定数组的大小, 数组下标由0开始

定义

1
2
3
4
5
6
7
8
array_name=(value0 value1 value2 value3)
# 或
array_name=(
value0
value1
value2
value3
)

下标的范围没有限制

读取

1
2
valuen=${array_name[n]} # 单个元素
echo ${array_name[@]} # 使用 @或* 获取所有元素

获取数组长度

1
2
3
4
5
6
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}

Bourne shell (原生kernel下) 下不支持数组, 只能通过模拟来实现数组功能

1
2
3
4
5
6
7
8
9
10
#!/bin/sh
#注意不是/bin/bash
echo "##############使用eval函数###############"
eval a1=bili
eval a2=nico
eval a3=yama

for i in 1 2 3 ; do
eval echo "\$a$i"
done

eval函数

语法: eval cmdline

eval会对后面的cmdline进行两遍扫描, 如果第一遍扫描后, cmdline是个普通命令, 则执行此命令; 如果cmdline中含有变量的间接引用, 则保证间接引用的语义

注释

单行

1
# 这是注释

多行

  1. 可以把这一段要注释的代码用一对花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释一样的效果

  2. 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
:<<EOF
注释内容...
注释内容...
注释内容...
EOF
###########或
:<<'
注释内容...
注释内容...
注释内容...
'
###########或
:<<!
注释内容...
注释内容...
注释内容...
!
###########或
# 等等
# 其中符号可以更换

read命令用于获取键盘输入信息

read [-options] [variable...]

read命令一个一个词组地接受输入的参数, 每个词组需要使用空格进行分隔; 如果输入的词组个数大于需要的参数个数, 则多出的词组将被作为整体为最后一个参数接收

参数 | 说明
-p | 输入提示文字
-n | 输入字符长度限制
-t | 输入限时
-s | 隐藏输入内容

1
2
read -p "请输入一段文字:" -n 6 -t 5 -s password
echo -e "\npassword is $password"

输出结果:

1
2
请输入一段文字:
password is didong

传递参数

传递参数: 执行shell脚本时, 向脚本传递参数
获取参数: $n, 0为脚本文件名, 1为第一个参数, 2为第二个参数, 以此类推…

特殊参数处理字符

参数处理 说明
$# 传递到脚本的参数个数
$* 以一个单字符串显示所有向脚本传递的参数
$$ 脚本运行的当前进程id号
$! 后台运行的最后一个进程的id号
$@ 与$*相同, 但使用时加引号, 并在引号中返回每个参数
$- 显示shell使用的当前选项, 与set命令功能相同
$? 显示最后命令的退出状态, 0表示没有错误, 其他任何值表示有错误

注意: 传递的参数中如果包含空格, 应该使用单引号或者双引号将该参数括起来, 以便于脚本将这个参数作为整体来接收

中括号的用法

[]常常可以使用test命令来代替

[]与操作数之间一定要有一个空格, 否则会报错

算数比较

1
2
[ $var -eq 0 ]  # 当 $var 等于 0 时, 返回真
[ $var -ne 0 ] # 当 $var 不等于 0 时, 返回真

其他比较操作符:

操作符 说明
-gt 大于
-lt 小于
-ge 大于或等于
-le 小于或等于

文件系统属性测试

操作符 | 说明
[ -f $file_var ] | 变量 $file_var 是一个正常的文件路径或文件名 (file), 则返回真
[ -x $var ] | 变量 $var 包含的文件可执行 (execute), 则返回真
[ -d $var ] | 变量 $var 包含的文件是目录 (directory), 则返回真
[ -e $var ] | 变量 $var 包含的文件存在 (exist), 则返回真
[ -c $var ] | 变量 $var 包含的文件是一个字符设备文件的路径 (character), 则返回真
[ -b $var ] | 变量 $var 包含的文件是一个块设备文件的路径 (block), 则返回真
[ -w $var ] | 变量 $var 包含的文件可写(write), 则返回真
[ -r $var ] | 变量 $var 包含的文件可读 (read), 则返回真
[ -L $var ] | 变量 $var 包含是一个符号链接 (link), 则返回真

字符串比较

在进行字符串比较时, 最好使用双中括号[[ ]], 因为单中括号可能会导致一些错误, 因此最好避开它们

操作符 说明
[[ $str1 = $str2 ]] 当 str1等于str2 时返回真, =前后有空格, 如果不加空格, 就变成了赋值语句
[[ $str1 == $str2 ]] 同上
[[ $str1 != $str2 ]] 如果 str1 与 str2 不相同, 则返回真
[[ -z $str1 ]] 如果 str1 是空字符串, 则返回真
[[ -n $str1 ]] 如果 str1 是非空字符串, 则返回真

组合

中括号中

可以通过-a(and)或-o(or)结合多个条件进行测试:

1
2
[ $var1 -ne 0 -a $var2 -gt 2 ]  # 使用逻辑与 -a
[ $var1 -ne 0 -o $var2 -gt 2 ] # 使用逻辑或 -o

中括号外

使用逻辑运算符&&||将多个条件组合起来:

1
2
3
4
5
6
str1="Not empty"
str2=""
if [[ -n $str1 ]] && [[ -z $str2 ]];
then
echo str1 is nonempty and str2 is empty string.
fi

test命令

test命令也可以执行条件检测, 用test可以避免使用过多的括号, 如:

if [ $var -eq 0 ]; then echo "True"; fi

等价于:

if test $var -eq 0; then echo "True"; fi

set命令

功能

用于设置shell的执行方式

语法

set [+-abCdefhHklmnpPtuvx]

参数说明

参数 说明
a 标示已修改的变量, 以供输出至环境变量
b 使被中止的后台程序立刻回报执行状态
C 转向所产生的文件无法覆盖已存在的文件
d Shell预设会用杂凑表记忆使用过的指令, 以加速指令的执行; 使用-d参数可取消
e 若指令回传值不等于0, 则立即退出shell
f 取消使用通配符
h 自动记录函数的所在位置
H 可利用!加<指令编号>的方式来执行history中记录的指令
k 指令所给的参数都会被视为此指令的环境变量
l 记录for循环的变量名称
m 使用监视模式
n 只读取指令, 而不实际执行
p 启动优先顺序模式
P 执行指令时, 会以实际的文件或目录来取代符号连接
t 执行完随后的指令, 即退出shell
u 当执行时使用到未定义过的变量, 则显示错误信息
v 显示shell所读取的输入值
x 执行指令后, 会显示该指令及所下的参数
+<参数> 取消某个set曾启动的参数

基本运算符

原生bash不支持简单的数学运算, 但是可以通过其他命令来实现, 如awk和expr, expr最常用

算数运算符

+ - * / % = == !=

关系运算符

关系运算符只支持数字, 不支持字符串, 除非字符串的值是数值

参照算数比较部分

布尔运算符

注: 变量a为10, b为20

运算符 说明 举例
! 非运算 [ ! false ] 返回 true
-o 或运算 [ $a -lt 20 -o $b -gt 100 ] 返回 true
-a 与运算 [ $a -lt 20 -a $b -gt 100 ] 返回 false

逻辑运算符

运算符 说明
&& 逻辑的AND
|| 逻辑的OR

字符串运算符

变量a为”abc”, b为”efg”

运算符 | 说明 | 举例
= | 相等 | [ $a = $b ] 返回 false
!= | 不相等 | [$a != $b ] 返回 true
-z | 检测字符串长度是否为0, 为0返回true | [ -z $a ] 返回 false
-n | 检测字符串长度是否为0, 不为0返回true | [ -n $a ]返回 true
$ | 检测字符串是否为空, 不为空返回true | [ $a ]返回 true

文件测试运算符

用于检测Unix文件的各种属性

操作符 | 说明
-b file | 检测文件是否是块设备, 如果是, 则返回true
-c file | 检测文件是否是字符设备文件, 如果是, 则返回true
-d file | 检测文件是否是目录, 如果是, 则返回true
-f file | 检测文件是否是普通文件(既不是目录也不是设备文件), 如果是, 则返回true
-g file | 检测文件是否设置了SGID位, 如果是, 则返回true
-k file | 检测文件是否设置了粘着位(Sticky Bit), 如果是, 则返回true
-p file | 检测文件是否是有名管道, 如果是, 则返回true
-u file | 检测文件是否设置了SUID位, 如果是, 则返回true
-r file | 检测文件是否可读, 如果是, 则返回true
-w file | 检测文件是否可写, 如果是, 则返回true
-x file | 检测文件是否可执行, 如果是, 则返回true
-s file | 检测文件是否为空(文件大小是否大于0), 不为空返回true
-e file | 检测文件(包括目录)是否存在, 如果是, 则返回true
-S file | 判断某文件是否socket
-L file | 检测文件是否存在并且是一个符号链接

其他

使用[[ ... ]]条件判断结构, 而不是[ ... ], 能够防止脚本中的许多逻辑错误. 比如, &&, ||, <>操作附能够正常存在于[[]]条件判断结构中, 但是如果出现在[]结构中的话, 会报错

使用[]时, ><需要使用反斜线转义

相加的写法

1.

1
2
3
4
a=10
b=20
c=`expr ${a} + ${b}`
echo "$c"

2.

1
2
c=${ `expr 10 + 20` }
echo "$c"

3.

1
2
c=$[ 10 + 20]
echo "$c"

4.

1
2
3
a=10
b=20
c=$(($a+$b))

echo命令

  1. 显示普通字符串

echo "It is a test"

双引号可省略

echo It is a test

  1. 显示转义字符

echo "\"It is a test\""

结果为

"It is a test"

同样, 双引号也可以省略

  1. 显示变量

read 命令从标准输入中读取一行, 并把输入行的每个字段的值指定给shell变量

1
2
read name
echo "$name It is a test"
  1. 显示换行
1
2
echo -e "OK! \n" # -e 开启转义
echo "It is a test"

输出结果:

1
2
3
OK!

It is a test
  1. 显示不换行
1
2
echo -e "OK! \c"
echo "It is a test"

输出结果:

1
OK! It is a test
  1. 显示结果定向至文件
1
echo "It is a test" > myfile
  1. 原样输出字符串, 不进行转义或取变量(用单引号)
1
echo '$name\"'

输出结果:

1
$name\"
  1. 显示命令执行结果(反引号)
1
echo `date`

结果将显示当前日期

总结

能否引用变量 | 能否引用转义符 | 能否引用文本格式符(如: 换行符, 制表符)
单引号 | 否 | 否 | 否
双引号 | 能 | 能 | 能
无引号 | 能 | 能 | 否

printf命令

printf由POSIX标准所定义, 因此使用printf的脚本比使用echo移植性好

printf使用引用文本或空格分割的参数

语法; printf format-string [arguments...]

具体格式化字符串参照C中的printf()函数

注意:

  • 单引号与双引号效果一样
  • 没有引号也可以输出
  • 格式只指定了一个参数, 但多出的参数仍然会安装该格式输出, format-string被重用
  • 如果没有arguments, 那么 %s 用 NULL 代替, %d 用 0 代替

流程控制

和Java, PHP等语言不一样, sh的流程控制不可为空, 如果else分支没有语句执行, 就不要写这个else

if else

if

1
2
3
4
5
6
7
if condition
then
command1
command2
...
commandN
fi

也可以写成一行(每行命令末尾要加分号)

if else

1
2
3
4
5
6
7
8
9
if condition
then
command1
command2
...
commandN
else
command
fi

if else-if else

1
2
3
4
5
6
7
8
9
if condition
then
command1
elif condition2
then
command2
else
commandN
fi

for循环

1
2
3
4
5
6
7
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done

也可以写成一行(每行命令末尾要加分号)

类似于C中的for循环

1
2
3
4
5
6
7
for((assignment;condition;next));
do
command1;
command2;
...
commandN;
done;

注意: 如果要在循环体中进行for中的next操作, 记得变量要加$, 不然程序会变成死循环

while语句

1
2
3
while condition
do command
done

let命令

let命令是BASH中用于计算的工具, 用于执行一个或多个表达式, 变量计算中不需要加上$来表示变量. 如果表达式中包含了空格或其他特殊字符, 则必须引起来.

语法格式: let arg [arg ...], 其中arg为要执行的表达式

与C/C++表达式类似

无限循环

1
2
3
4
while :
do
command
done

或者

1
2
3
4
while true
do
command
done

或者

1
for (( ; ; ))

until循环

until循环执行一系列命令直至条件为true时停止, while循环执行一系列命令直至条件为false时停止

1
2
3
4
until condition
do
command
done

case 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
casein
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac

如果没有匹配的模式, 使用*捕获该值

跳出循环

break

continue

shell函数

1
2
3
4
5
[ function ] funname [()]
{
action;
[return int;]
}

说明:

  • 可以带function funname()定义, 也可以直接funname()定义, 不带任何参数
  • 参数返回, 可以显示加return返回, 如果不加, 将以最后一条命令运行结果, 作为返回值, return后跟数值n(o-255)
  • funname调用函数

输入/输出重定向

命令 说明
command > file 将输出重定向到file
command < file 将输入重定向到file
command >> file 将输出以追加的方式重定向到file
n > file 将文件描述符为n的文件重定向到file
n >> file 将文件描述符为n的文件以追加的方式重定向到file
n >& m 将输出文件m和n合并
n <& m 将输入文件m和n合并
<< tag 将开始标记tag和结束标记tag之间的内容作为输入

文件描述符0通常是标准输入(STDIN), 1是标准输出(STDOUT), 2是标准错误输出(STDERR)

command1 < infile > outfile
同时替换输入和输出, 执行command1, 从文件infile读取内容, 然后将输出写入到outfile中

如果希望stderr重定向到file
command 2 > file
如果希望stderr追加到file文件末尾
command 2 > file

如果希望将stdout和stderr合并后重定向到file
command > file 2>&1
或者
command >> file 2>&1

Here Document

1
2
3
command << delimiter
document
delimiter

将两个delimiter之间的内容(documnet)作为输入传递给command
结尾的delimiter一定要顶格写, 前面不能有任何字符, 后面也不能有任何字符, 包括空格和tab缩进
开始的delimiter前后的空格会被忽略掉

示例:

1
2
3
4
5
6
wc -l << EOF
DIDA
LAB
DiDong
EOF
3 # 输出结果为3行

/dev/null文件

如果希望执行某个命令, 但又不希望在屏幕上显示输出结果, 那么可以将输出重定向到/dev/null
command > /dev/null
/dev/null是一个特殊的文件, 写入到它的内容都会被丢弃; 如果尝试从该文件读取内容, 什么也读不到
如果希望屏蔽stdout和stderr
command > /dev/null 2>&1

顺序问题

1
2
3
4
find /etc -name .bashrc > list 2>&1
# 和
find /etc -name .bashrc 2>&1 > list
# 之间的区别

第一种
先要将输出到stdout的内容重定向到文件, 此时文件list就是这个程序的stdout, 再将stderr重定向到stdout, 也就是文件list
第二种
先将要输出的stderr的内容重定向到stdout, 此时会产生一个stdout拷贝, 作为程序的stderr, 而程序原本要输出到stdout的内容, 依然是对接在stdout原身上的, 因此第二步重定向stdout, 对stdout的拷贝不产生任何影响

其他问题

直接在FreeBSD或者csh中使用command > file 2>&1的时候会得到这个错误:Ambiguous output redirect. 出错的原因在于FreeBSD默认使用csh, 在csh中如果想把标准输出和错误输出同时重定向到一个文件, 需要用command >& file

shell文件包含

包含外部脚本, 方便封装一些公用的代码作为一个独立的文件

1
2
3
. filename
# 或
source filename

被包含的文件不需要可执行权限

Thank you for your reward !