1. shell脚本的基本结构与执行
1.1 基本结构
shell脚本的后缀名一般为sh,并且在最开始需要指明执行脚本的解释器(即具体使用哪一种shell)
#!/bin/bash
1.2 执行
如果想要让脚本作为可执行文件来执行,则需要为其增加执行权限,即使用chmod来操作
chmod +x ./test.sh
# 添加执行权限后,此时可以直接使用./来执行脚本
./test.sh
1.3 注释
注释分为单行注释和多行注释,其中单行注释使用 #
即可,多行注释则有特定的写法
:<<EOF
注释内容...
注释内容...
注释内容...
EOF
其中的 EOF
可以使用其他的符号,比如:
:<<'
注释内容...
注释内容...
注释内容...
'
:<<!
注释内容...
注释内容...
注释内容...
!
2. 变量
2.1 变量的定义与赋值
定义变量时,变量名不加 $
符号,另外,需要注意的是,变量名和等号之间不能加空格,例如
varible="123456"
此外,变量的命名规则为:
- 变量命名只能用英文字母、数字、下划线,且首字符不能为数字
- 变量名中间不能有空格、标点符号
- 不能使用保留字
除了可以显式直接对变量赋值外,还可以使用语句为变量赋值,如:
for file in 'ls /etc'
for file in $(ls /etc)
有一种变量类型为只读变量,该变量仅可读,不允许被修改
var_1="1234"
readonly var_1
var_1="5" # 报错
2.2 使用变量
要使用一个已经定义过的变量,则需要在变量名前面添加 $
符号,此外,$
后面的大括号是可以省略的,但是一般情况下,加上这个大括号可以更好地区分变量的边界,最好是加上。
var_1="12345"
echo $var_1
echo ${var_1}
已定义的变量,可以被重新定义
var_2="before"
echo ${var_2} # 输出为before
var_2="after"
echo ${var_2} # 输出为after
当变量不再需要时,可以删除变量,但是不能删除只读变量。同时需要注意的是,这里的unset后面直接跟变量名,不需要加 $
符号
unset var_2
2.3 变量类型(作用范围)
- 局部变量:局部变量在脚本或命令中定义,仅在当前的shell中有效,其他的shell启动的程序不能访问
- 环境变量:所有的程序,包括shell启动的程序,都可以访问环境变量,有些程序需要环境变量来保证其正常运行,shell脚本也可以定义环境变量
- shell变量:由shell程序设置的特殊变量,shell变量中有一部分是环境变量,有一部分是局部变量,保证了shell的正常运行
3. 数据类型
数据类型本应属于变量的范畴,但是因为太多了,就单独拿出来
3.1 字符串
shell中的字符串可以用单引号,也可以用双引号,也可以不用引号,但是三种用法之间略有不同
str_1='string1'
str_2="string2"
str_3=string3
这些不同的字符串也会在进行拼接的时候略有不同
3.1.1 单引号字符串限制
- 单引号内的任何字符都会原样输出,其中的变量无效
- 单引号字符串不能只出现一个单独的引号,需要成对出现,(对单引号使用转义符后需要成对出现)。
str_1=' \'string1''
str_2='string2 and ${str_1}'
echo ${str_2}
# 此时的输出为 string2 and ${str_1}
- 由于单引号字符串里面不能有转义字符出现,因此,需要分三段进行拼接
your_name="name1"
# 使用单引号拼接
greeting_2='hello, '$your_name' !'
greeting_3='hello, ${your_name} !'
echo $greeting_2 $greeting_3
# 此时的输出为 hello, name1 ! hello, ${your_name} !
3.1.2 双引号字符串限制
- 双引号里可以有变量
- 双引号里可以出现转义字符
your_name="name2"
# 比如下面这个转义字符
str="Hello, I know you are \"$your_name\"! \n"
echo -e $str
# 输出结果为 Hello, I know you are "name2"!
- 由于此时的双引号内可以添加转义字符,因此这里的字符串拼接会相对来说更加灵活一些
your_name="name2"
# 使用双引号拼接
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting $greeting_1
# 输出结果为 hello, name2 ! hello, name2 !
3.1.3 获取字符串长度
使用#可以获得
string="abcd"
echo ${#string} # 输出为4
3.1.4 提取子字符串
提取子字符串时,第一个字符的索引为0,因此下面这个例子是表示从第2个字符开始截取4个字符
string="1234567890"
echo ${string:1:4}
3.1.5 查找子字符串
查找字符第一次出现的位置,下面这个例子是查找字符i或者o的位置(哪个先出现就算哪个)
string="1i2o3i4p567"
echo `expr index "${string}" io` # 输出为2
3.2 数组
bash支持一维数组,并且没有限定数组的大小。
类似于C语言,数组元素的下标从0开始编号,获取数组中的元素可以通过下标来获取,其中下标可以是数字或者表达式,值应>=0
3.2.1 定义
数组的定义,用括号表示数组,元素使用空格隔开,一般形式为:
数组名=(值1 值2 值3 ... 值n)
# 例如下面这个例子
array_name=(value0 value1 value2 value3)
# 也可以写为
array_name=(
value0
value1
value2
value3
)
# 也可以单独定义各个值,数组可以使用不连续的下标,并且下标的范围没有限制
array_name[0]=value0
array_name[1]=value1
array_name[n]=valuen
3.2.2 读取
一般的格式为:
${数组名[下标]}
# 例如
valuen=${array_name[n]}
如果需要获取数组中的所有元素,则需要使用 @
,即:
echo ${array_name[@]}
3.2.3 获取数组长度
获取数组长度的方式与获取字符串长度方式相同,也是用#
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}
4. 传递参数
在执行脚本时,可以通过命令行向脚本传递参数,脚本内获取参数的格式为 $n
,其中的n表示第n个参数。例如下面一个脚本
#!/bin/bash
echo "脚本文件名为:$0"
echo "第一个参数为:$1"
echo "第一个参数为:$2"
echo "第一个参数为:$3"
此时执行脚本可以得到结果为:
./test.sh 1 2 3
脚本文件名为:./test.sh
第一个参数为:1
第二个参数为:2
第三个参数为:3
此外,还有一些特殊的参数
参数处理 | 说明 |
$# | 传递到脚本的参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数。 如 |
$$ | 脚本运行的当前进程ID号 |
$! | 后台运行的最后一个进程的ID号 |
$@ | 与 |
$- | 显示Shell使用的当前选项,与set命令功能相同。 |
$? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 |
$*
与 $@
区别:
- 相同点:都是引用所有参数。
- 不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 “1 2 3”(传递了一个参数),而 “@” 等价于 “1” “2” “3”(传递了三个参数)。
#!/bin/bashecho "-- \$* 演示 ---"for i in "$*"; do echo $idoneecho "-- \$@ 演示 ---"for i in "$@"; do echo $idone
执行的结果为:
$ ./test.sh 1 2 3
-- $* 演示 ---
1 2 3
-- $@ 演示 ---
1
2
3
5. 运算符
shell支持多种运算符,包括算数运算符、关系运算符、布尔运算符、字符串运算符、文件测试运算符
5.1 算术运算符
由于原生的bash不支持简单的数学运算,因此需要通过一些命令来实现,比如awk、expr等,其中expr最常用
expr是一个用于表达式计算的工具,可以完成表达式的求值操作。
需要注意的是:
- 表达式和运算符之间要有空格,比如必须写成
2 + 2
,而不能写成2+2
; - 完整的表达式要被 ` ` 包含。
例如下面一个求两数之和的操作
val=`expr 2+2`
echo "两数之和为:${val}" # 输出结果为4
下面是常用的算术运算符,需要注意的是,对于相等、不相等的条件表达式,需要放在方括号之间,并且需要有空格,例如:
[ $a == $b ]
是正确的,而 [$a==$b]
是错误的
运算符 | 说明 | 举例 |
+ | 加法 |
|
- | 减法 |
|
* | 乘法 |
|
/ | 除法 |
|
% | 取余 |
|
= | 赋值 |
|
== | 相等。用于比较两个数字,相同则返回 true。 |
|
!= | 不相等。用于比较两个数字,不相同则返回 true。 |
|
具体的例子如下:
a=10
b=20
val=`expr $a + $b`
echo "a + b : $val"
val=`expr $a - $b`
echo "a - b : $val"
val=`expr $a \* $b`
echo "a * b : $val"
val=`expr $b / $a`
echo "b / a : $val"
val=`expr $b % $a`
echo "b % a : $val"
if [ $a == $b ]
then
echo "a 等于 b"
fi
if [ $a != $b ]
then
echo "a 不等于 b"
fi
5.2 关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
运算符 | 说明 | 举例 |
-eq | 检测两个数是否相等,相等返回 true。 |
|
-ne | 检测两个数是否不相等,不相等返回 true。 |
|
-gt | 检测左边的数是否大于右边的,如果是,则返回 true。 |
|
-lt | 检测左边的数是否小于右边的,如果是,则返回 true。 |
|
-ge | 检测左边的数是否大于等于右边的,如果是,则返回 true。 |
|
-le | 检测左边的数是否小于等于右边的,如果是,则返回 true。 |
|
下面的例子假定a=10,b=20
a=10
b=20
if [ $a -eq $b ]
then
echo "$a -eq $b : a 等于 b"
else
echo "$a -eq $b: a 不等于 b"
fi
if [ $a -ne $b ]
then
echo "$a -ne $b: a 不等于 b"
else
echo "$a -ne $b : a 等于 b"
fi
if [ $a -gt $b ]
then
echo "$a -gt $b: a 大于 b"
else
echo "$a -gt $b: a 不大于 b"
fi
if [ $a -lt $b ]
then
echo "$a -lt $b: a 小于 b"
else
echo "$a -lt $b: a 不小于 b"
fi
if [ $a -ge $b ]
then
echo "$a -ge $b: a 大于或等于 b"
else
echo "$a -ge $b: a 小于 b"
fi
if [ $a -le $b ]
then
echo "$a -le $b: a 小于或等于 b"
else
echo "$a -le $b: a 大于 b"
fi
5.3 布尔运算符
运算符 | 说明 | 举例 |
! | 非运算,表达式为 true 则返回 false,否则返回 true。 |
|
-o | 或运算,有一个表达式为 true 则返回 true。 |
|
-a | 与运算,两个表达式都为 true 才返回 true。 |
|
例子:
a=10
b=20
if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a == $b: a 等于 b"
fi
if [ $a -lt 100 -a $b -gt 15 ]
then
echo "$a 小于 100 且 $b 大于 15 : 返回 true"
else
echo "$a 小于 100 且 $b 大于 15 : 返回 false"
fi
if [ $a -lt 100 -o $b -gt 100 ]
then
echo "$a 小于 100 或 $b 大于 100 : 返回 true"
else
echo "$a 小于 100 或 $b 大于 100 : 返回 false"
fi
if [ $a -lt 5 -o $b -gt 100 ]
then
echo "$a 小于 5 或 $b 大于 100 : 返回 true"
else
echo "$a 小于 5 或 $b 大于 100 : 返回 false"
fi
输出结果为:
10 != 20 : a 不等于 b
10 小于 100 且 20 大于 15 : 返回 true
10 小于 100 或 20 大于 100 : 返回 true
10 小于 5 或 20 大于 100 : 返回 false
5.4 逻辑运算符
假定变量 a 为 10,变量 b 为 20:
运算符 | 说明 | 举例 |
&& | 逻辑的 AND |
|
|| | 逻辑的 OR |
|
例子:
a=10
b=20
if [[ $a -lt 100 && $b -gt 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi
if [[ $a -lt 100 || $b -gt 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi
输出结果为:
返回 false
返回 true
5.5 字符串运算符
下表列出了常用的字符串运算符,假定变量 a 为 “abc”,变量 b 为 “efg”:
运算符 | 说明 | 举例 |
= | 检测两个字符串是否相等,相等返回 true。 |
|
!= | 检测两个字符串是否不相等,不相等返回 true。 |
|
-z | 检测字符串长度是否为0,为0返回 true。 |
|
-n | 检测字符串长度是否不为 0,不为 0 返回 true。 |
|
$ | 检测字符串是否为空,不为空返回 true。 |
|
例子:
a="abc"
b="efg"
if [ $a = $b ]
then
echo "$a = $b : a 等于 b"
else
echo "$a = $b: a 不等于 b"
fi
if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a != $b: a 等于 b"
fi
if [ -z $a ]
then
echo "-z $a : 字符串长度为 0"
else
echo "-z $a : 字符串长度不为 0"
fi
if [ -n "$a" ]
then
echo "-n $a : 字符串长度不为 0"
else
echo "-n $a : 字符串长度为 0"
fi
if [ $a ]
then
echo "$a : 字符串不为空"
else
echo "$a : 字符串为空"
fi
输出结果
abc = efg: a 不等于 b
abc != efg : a 不等于 b
-z abc : 字符串长度不为 0
-n abc : 字符串长度不为 0
abc : 字符串不为空
6. 流程控制
6.1 if-else
if-else语句分为三种,一种是单if,一种是单if+else,一种是if+elif+else
- if语句
if语法格式
if condition
then
command1
command2
...
commandn
fi
如果写成1行,则有:
if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi
- if-else语句
if condition
then
command1
command2
...
commandn
else
command
fi
- if-elif-else语句
if condition1
then
command1
elif condition2
then
command2
else
command3
fi
下面是一个例子,判断a和b的大小关系
a=10
b=20
if [ ${a} -eq ${b} ]
then
echo "a = b"
elif [ ${a} -gt ${b} ]
then
echo "a > b"
elif [ ${a} -lt ${b} ]
then
echo "a < b"
else
echo "error"
fi
# 输出为 a < b
if-else语句经常与test命令结合使用,如下所示
num1=$[2*3]
num2=$[1+5]
if test $[num1] -eq $[num2]
then
echo '两个数字相等!'
else
echo '两个数字不相等!'
fi
6.2 for循环
for循环的一般格式为:
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
如果写成一行,则为
for var in item1 item2 ... itemN; do command1; command2… done;
当变量的值在列表中,for循环就会执行一次所有命令,使用变量名获取列表中的当前取值。命令可以为任何有效的shell命令和语句。in列表可以包含替换、字符串、文件名
6.3 while语句
while语句的基本格式为:
while condition
do
command1
command2
...
commandN
done
例子:
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done
6.4 until循环
until 循环执行一系列命令直至条件为 true 时停止。
until 循环与 while 循环在处理方式上刚好相反。
一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。
until condition
do
command
done
例子:
a=0
until [ ! $a -lt 10 ]
do
echo $a
a=`expr $a + 1`
done
6.5 case语句
shell中的case语句需要注意,break使用 ;;
来表示。
case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac
下面是一个例子
echo '输入 1 到 4 之间的数字:'
echo '你输入的数字为:'
read aNum
case $aNum in
1) echo '你选择了 1'
;;
2) echo '你选择了 2'
;;
3) echo '你选择了 3'
;;
4) echo '你选择了 4'
;;
*) echo '你没有输入 1 到 4 之间的数字'
;;
esac
6.6 跳出循环
- break命令
例子:
while :
do
echo -n "输入 1 到 5 之间的数字:"
read aNum
case $aNum in
1|2|3|4|5) echo "你输入的数字为 $aNum!"
;;
*) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"
break
;;
esac
done
- continue命令
while :
do
echo -n "输入 1 到 5 之间的数字: "
read aNum
case $aNum in
1|2|3|4|5) echo "你输入的数字为 $aNum!"
;;
*) echo "你输入的数字不是 1 到 5 之间的!"
continue
echo "游戏结束"
;;
esac
done
7. 函数
函数的基本定义如下,需要注意的是,下面的 []
表示可选项,并不是需要带在代码里面的,也就是说:
- 可以通过
function func_name()
来定义,也可以直接用func_name()
,或者func_name
来定义 - 对于参数返回,可以显式加
return 内容
,如果不加,则表示无返回值,这里返回的内容为数值(0-255) - 函数的返回值在调用了该函数之后通过
$?
获取,这里需要注意的是,如果在函数后,没有立即查看$?
的值,而是先插入了一条别的 echo 命令,最后再查看$?
的值得到的是 0,也就是上一条 echo 命令的结果,而先前调用的函数的返回值被覆盖了。 - 函数的参数,只需在函数内部通过
$n
的方式来获取,这个获取方式与取命令行参数是类似的
[ function ] func_name [()]
{
command;
[return int;]
}
具体的例子
funWithReturn(){
echo "这个函数会对传入的两个数字进行相加运算..."
aNum=$1
anotherNum=$2
echo "参数一共有 $# 个"
echo "两个数字分别为 $aNum 和 $anotherNum !"
return $(($aNum+$anotherNum))
}
funWithReturn(1,2)
echo "输入的两个数字之和为 $? !"
8. 外部包含
shell也可以包含外部脚本,这样可以很方便地封装一些公用的代码作为一个独立的文件
. filename # 注意中间有个空格
source filename
注意,被包含的文件不需要可执行的权限,只需要写内容即可