linux之shell脚本quickStart
这篇文章主要参考于《跟老男孩学linux运维:Shell编程实战》,方便写shell脚本时参考,只列一些shell脚本中的容易混淆的知识点。
1 变量
变量就是用一个固定的字符串代替更多、更复杂的内容,使用变量的最大好处就是使程序开发更为方便。
1.1 普通变量
变量可分为全局变量和普通变量(局部变量),变量赋值时“=”两边不能加任何空格。
全局变量:可以在创建其shell脚本以及其派生出来的任意子进程shell脚本中使用。全局变量又分为自定义环境变量和bash内置的环境变量。
普通变量:只能在创建它们的shell函数或shell脚本中使用。
设置环境变量方法:
# 方法1:
export 变量名=value
# 方法2:
变量名=value
# 方法3:
declare -x 变量名=value
取消本地变量和环境变量:
unset # 使用unset消除本地变量和环境变量
查看环境变量:
# 方法1:
set # 命令输出所有变量,包括全局变量和局部变量、函数。
env # 命令只显示全局变量
变量赋值时要注意单引号,双引号的细微差别:
名称 | 说明 |
---|---|
单引号 | 所见即所得,即输出时会将单引号内的所有内容原样输出,单引号看到是什么就是什么。 |
双引号 | 如果内容中有命令(要加反引号)、变量、特殊转义符等,会先将其解析,然后输出最终内容 |
无引号 | 赋值时,如果变量内容有空格,则会造成赋值不完整,如果内容中有命令(要加反引号),变量、特殊转义符等,会解析输出 |
建议:shell脚本中,字符串变量一定要加双引号,如果是数字可以不加
输出变量时,可以使用$c 和${c} 两种方法,而$(c) 有别的用途,如下:
把一个命令的结果作为变量的内容赋值:
# 方法1
变量名=`ls` # 把命令用反引号引起来
# 方法2
变量名=$(ls) # 把命令用$()括起来,推荐这种方法
这一点与Makefile的方式并不相同,不要弄混了。
shell中变量引用与Makefile中的差别:
$x 单字符变量 | $xx 或 $xxx_x多字符变量 | ${} | $() | |
---|---|---|---|---|
shell | 变量取值 | 变量取值 | 变量取值 | 相当于``,解析命令结果 |
Makefile | 变量取值 | No | ${xxx_x}变量取值 | 变量取值 |
建议: Makefile中变量一定要写成$()或${}的形式,shell中使用$xx或${}的形式,shell中的$()另有用途。
1.2 shell特殊变量
在shell脚本中,存在一些特殊且重要的变量,例如:$0、$1、$#等。
位置变量 | 说明 |
---|---|
$0 | 获取当前执行shell脚本的文件名(包括脚本路径) |
$n | 获取当前执行的shell脚本第n个参数值,n=1..9,当n=0时表示脚本的文件名,如果n>9,则用大括号括起来,如${10},接的参数以空格隔开 |
$# | 获取当前执行的shell脚本后面接的参数总个数 |
$* | 获取当前shell脚本所有传参的参数,不加引号和$@相同,如果给$*加上双引号,例如"$*",则表示将所有参数视为单个字符串,相当于"$1 $2 $3" |
$@ | 获取当前shell脚本所有传参的参数,不加引号和$*相同,如果给$@加上双引号,例如"$@",则表示将所有参数视为独立字符串, 相当于"$1" "$2" "$3", 这是将多参数传递给其它程序的最好方式,因为它会保留内嵌到参数里的空格。 $* 与 $@相同,但"$*" 与 "$@"两者有区别。 |
$? | $?用于存储上一个命令的执行状态,它的取值范围通常为0-255,其中0表示执行成功,非0表示出现错误 |
1.3 shell特殊扩展变量
Shell的特殊扩展变量说明如下:
表达式 | 说明 |
---|---|
VAL=${parameter:-word} |
如果变量parameter没有定义或者值为空,则返回word字符串并替代变量的值,即VAL=word 用途:可以定义变量的默认值,这样如果变量未定义,则返回默认值 |
VAL=${parameter:=word} |
如果变量parameter没有定义或者值为空,则设置这个变量值为word,并返回其值, 即parameter=word且VAL=word,基本同${parameter:-word},但该变量又额外给parameter变量赋值了 |
VAL=${parameter:?word} |
如果变量parameter没有定义或者值为空,则返回word字符串作为标准错误输出,否则输出变量的值 用途:当变量parameter异常,报错并输出提醒。 |
VAL=${parameter:+word} |
如果变量parameter没有定义或者值为空,则什么都不做,否则返回word字符串并替代变量的值。 |
在上述表达式内的冒号都是可选的。
2 运算符
2.1 空格
空格的注意事项:
在shell脚本中,空格的使用需要十分注意(不像C、Java等语言对空格不敏感)。
空格有如下注意事项:
-
定义变量时,=的两边不可以留空格,因为shell 会认为空格前的为一个命令,但是在()内部不限制,如for ((i= 1;i < 3;i= i+1))是正确的;
-
(())内外部括号之间无空格,( () )这样报错。但内部括号内不限制,随便如s=$(( $i + 1 ))可以;
-
条件测试语句[ ] 的两边都要留空格;
-
条件测试的内容,如果是字符串比较的话, 比较符号两边要留空格;
-
操作符之间要用空格分开,如 test ! -d $1,其中的!和-d就要用空格分开,空格是命令解析中的重要分隔符;
-
命令和其后的参数或对象之间一定要有空格;
-
取变量值的符号'$'和后边的变量或括号不能有空格;
2.2 (()) 与 [ ]
(()) 与 [ ]用于条件表达式,区别见第4章,当然(())除了用于条件表达式外,还常用于算术数值运算。
2.3 || 与 &&
有时候,下一条命令依赖前一条命令是否执行成功。如:前一条命令成功执行之后再执行另一条命令,或者前一条命令执行失败后再执行另一条命令等。shell 提供了 && 和 || 来实现命令执行控制的功能,shell 将根据 && 或 || 前面命令的返回值来控制其后面命令的执行,这类似与C语言中的短路逻辑原则。
-
前一条命令成功执行之后再执行另一条命令 &&
语法格式如下:
command1 && command2 [&& command3 ...]
只有在 && 左边的命令返回真,&& 右边的命令才会被执行。
-
前一条命令执行失败后再执行另一条命令 ||
语法格式如下:
command1 || command2 [|| command3 ...]
只有在 || 左边的命令返回假,|| 右边的命令才会被执行。
3 常用命令
列出一些shell中常用的命令:
3.1 read
$ read -n2 -p 'please input you choice:\n' var
-n2表示只接收2个字符,输入回车后,输入值保存到var变量中
3.2 echo
echo -e可以输出换行,可以解析转义字符。
$ echo -e 'aaa\nbbb' 输出:
aaa
bbb
3.3 eval
eval命令对变量进行两次扫描。
使用举例:
$ cat test
Hello World
$ myfile="cat test"
$ echo $myfile
cat test
$ eval $myfile
Hello World
# eval命令将会对该变量进行两次扫瞄。
3.4 双小括号(())
(())常用于整数运算中。shell中常见的算术运算命令如下:
其操作方法如下:
运算命令 | 意义 |
---|---|
((i=i+1)) | 此种书写为运算之后赋值,即将i+1的运算结果赋值给变量i 注意不能用echo ((i=i+1))来输出表达式的值,但可以用echo $((i=i+1))输出其值 |
i = $((i+1)) | 可以在(())前加$符,表示将表达式运算后赋值给i |
((8 > 7 && 5 == 5)) | 可以进行比较操作以及逻辑操作 |
echo $((2+1)) | 需要直接输入表达式的运算结果时,可以在(())前加$符 |
4 流程控制
4.1 条件表达式
条件表达式一般搭配&&与|| 来使用,进行一些简单的判断,或者搭配if语句来使用,实现一些较复杂的判断逻辑。
4.1.1 条件表达式几种形式
条件测试语法 | 说明 |
---|---|
语法1: test <测试表达式> | 利用test命令进行条件测试表达式的方法,test命令和<测试表达式>之间至少有一个空格 |
语法2:[ <测试表达式> ] | 单中括号,和test命令的用法相同,[]的便捷和内容之间至少有一个空格 |
语法3:[[ <测试表达式> ]] | 双中括号,是比test和[]更新的语法,支持通配符,双大括号里的两端也要有空格 |
语法4:((<测试表达式>)) | 双小括号,一般用于if语句里,常用于计算,(())两端不需要有空格 |
4.1.2 文件测试表达式
常用的文件测试操作符 | 说明 |
---|---|
-e 文件 | exit,文件是否存在,区别与"-f"的是,-e不区分是文件夹还是目录 |
-d 文件 | directory,文件存在且为文件夹则为真,即测试表达式成立 |
-f 文件 | file,文件存在且为普通文件则为真,即测试表达式成立 |
-s 文件 | size,文件存在且文件大小不为0则为真 |
-r 文件 | read,文件存在且可读为真 |
-w 文件 | write,文件存在且可写为真 |
-x 文件 | executable, 文件存在且可执行则为真 |
f1 -nt f2,nt为newerthan | newer than, 文件f1比文件f2新则为真,根据文件的修改时间来计算 |
f1 -ot f2,ot为olderthan | older than, 文件f1比文件f2旧则为真,根据文件的修改时间来计算 |
4.1.3 字符串测试表达式
注意:
-
对于字符串的测试,一定要将字符串加双引号后再进行对比,这样避免字符串中有空格的情况;
-
比较符号(例如=和!=)的两端一定要有空格。
常用字符串测试操作符 | 说明 |
---|---|
-n “字符串” | 若字符串的长度不为0,则为真,即测试表达式成立,n可以理解为nozero |
-z “字符串” | 若字符串的长度为0,则为真,z可以理解为zero |
“串1” = “串2” | 若字符串1等于字符串2,则为真,可使用==代替= |
“串1” != “串2” | 若字符串1不等于字符串2,则为真 |
4.1.4 整数测试表达式
在[]以及test中使用的比较符号 | 在(())和[[]]中使用的比较符号 | 说明 |
---|---|---|
-eq | ==或= | 相等,全拼为equal |
-ne | != | 不相等,全拼为not equal |
-gt | > | 大于,全拼为greater than |
-ge | >= | 大于等于,全拼为greaterequal |
-lt | < | 小于,全拼为less than |
-le | <= | 小于等于,全拼为less equal |
4.1.5 逻辑操作表达式
在[]中使用的逻辑操作符 | 在test、[[]]和(())中使用的逻辑操作符 | 说明 |
---|---|---|
-a | && | and,与,两端都为真,则结果为真 |
-o | ll | or,或,两端有一个为真,则结果为真 |
! | ! | not,非,两端相反,则结果为真 |
以下写法适用于所有条件表达式,是工作中比较常用的替代if语句的方法,以[]为例。
[ 条件1 ] && {
命令1
命令2
命令3
}
# 等价于:
if [ 条件1 ]; then
命令1
命令2
命令3
fi
# 同理:
[ 条件1 ] || {
命令1
命令2
命令3
}
# 等价于:
if [ ! 条件1 ]; then
命令1
命令2
命令3
fi
总结:
测试表达式 | [] | test | [[]] | (()) |
---|---|---|---|---|
边界是否需要空格 | 需要 | 需要 | 需要(指内部[]) | 不需要 |
文件测试表达式 | -e、-d、-f 等 | -e、-d、-f 等 | -e、-d、-f 等 | -e、-d、-f 等 |
字符串比较 | =、==、!= | =、==、!= | =、==、!= | =、==、!= |
整数比较 | -eq、-gt、-lt、-ge、-le | -eq、-gt、-lt、-ge、-le | -eq、-gt、-lt、-ge、-le 或 =、>、<、>=、<= |
=、>、<、>=、<= |
逻辑操作表达式 | !、-a、-o | !、-a、-o | !、&&、|| | !、&&、|| |
是否支持通配符 | 不支持 | 不支持 | 支持 | 不支持 |
4.2 if 语句
在所有编程语言里,if条件语句几乎是最简单、用途最广的语句格式。
一般的方式为:
# 第一种写法
if <条件表达式>
then
指令集1
fi
# 第二种写法,相当于换行
if <条件表达式>; then
指令
fi
# 第三种写法:if else 结构
if <条件表达式>; then
指令集1
else
指令集2
fi
# 第四种写法:多分支结构 if-elif-else 结构,注意if与elif后都带有then,else后面没有then
if <条件表达式1>; then
指令集1
elif <条件表达式2>; then
指令集2
else
指令集3
fi
4.3 case语句
case条件语句相当于多分支的if/elif/else条件语句,但是比条件语句看起来更工整,case语句比较适合变量值较少且为固定数字或字符串集合的情况。
如下:当变量的值等于1时,执行指令1,等于值2时执行指令2,依次类推,如果都不符合,则执行*)分支,此外,注意不同行内容的缩进距离。
case "变量" in
值1)
指令1...
;; # 双引号相当于c语言switch-case语句中的break
值2)
指令2...
;;
*) # 相当于default分支,可以不用双引号;;
指令3...
esac
4.4 循环结构
循环中常用的是while与for语句,区别是:while循环常用在守护进程,以及那些希望能持续执行不退出的应用。for循环语句主要用于执行次数有限的循环。
# 第一种写法:while循环语句
while <条件表达式>
do
指令集1
done
# 第二种写法:until循环语句(使用的很少)
# 条件不成立进入循环,条件表达式成立时终止循环
until <条件表达式>
do
指令集1
done
# 第三种写法:for循环语法结构
# 第一种for循环语句为变量取值型
for 变量名 in 变量取值列表
do
指令集1
done
# 第二种for循环语句为C语言型for循环结构
for((exp1; exp2; exp3))
do
指令集1
done
注意:sh并不支持第二种循环语句(C语言型for循环结构),见参考5
5 函数
5.1 shell函数的几种写法
# 第一种写法,function + 函数名 + 括号(推荐)
function 函数名() {
指令...
return n
}
# 以下两种方法都是简写,function和() 可以省略一个
# 第二种写法,函数名后无括号
function 函数名 {
指令...
return n
}
# 第三种写法,无function
函数名() {
指令...
return n
}
return与exit的用法:
return用来退出函数,exit用来退出脚本文件
5.2 shell函数的执行
shell函数执行的方式:
# 1. 函数不带参数时,直接调用函数名即可,也即function + 函数名 + 括号,只保留函数名即可
# 2. shell执行系统中各种程序的执行顺序为:系统别名->函数->系统命令->可执行文件
# 3. 函数需要先定义然后再执行函数,否则会报错
函数名
# 2. 带参数的函数执行方法
# 例如:函数名 $1 $2 $3
函数名 参数1 参数2
6 shell脚本的调试
有时shell脚本不正确,需要调试,可使用如下方法:
方法1:在脚本中加打印
在shell脚本中加echo
方法2:bash参数调试法
bash [-nvx] scripts.sh
其中:
-n :不会执行脚本,仅查询脚本语法是否有问题,并给出错误提示。
-v : 在执行脚本时,先将脚本内容输出到屏幕上,然后执行脚本,如果有错误,也会给出错误提示
-x :将执行的脚本内容及输出显示到屏幕上,这是对调试很有用
bash -x 的缺点: 当加载系统函数库等脚本时,有太多的输出,导致很难查看我们关注的内容,可以在脚本开始叫上set -x ,可以弥补bash -x的缺点,加了set -x后,就不需要用bash -x了。
方法3:线上语法检查工具
注意:
shell脚本出错常与环境变量设置有关,这时候,可以使用 printenv 打印环境变量。或使用 export -p 命令显示全部拥有导出属性的变量。
7 shell脚本其它
7.1 shell脚本的执行方式
当shell脚本运行时,会先找茬系统环境变量ENV(加载顺序为:/etc/profile -> ~/.bash_profile -> ~/.bashrc->/etc/bashrc),在加载了环境变量文件后,Shell脚本开始执行Shell脚本的内容。
执行脚本有如下几种方式:
方式 | 说明 |
---|---|
bash script-name 或 sh script-name | 当脚本本身没有可执行权限时的使用方法,或脚本首行没有指定解释器(推荐) |
./script-name | 在当前路径下执行脚本,脚本需要有可执行权限 |
source script-name 或 . script-name | 在当前父shell脚本中运行,(其它模式都会启动新进程执行子脚本) source或者“ . ”相当于include功能,可以将脚本本身的变量值或函数传递到当前父shell脚本中使用。 |
sh < script-name 或 cat script-name | sh | 用法不常见。 |
其中,source方法的特点是:
- 子shell脚本会直接继承父亲的shell脚本变量,函数(就像儿子随父亲姓,基因也继承父亲的),反之则不可以
- 如果希望父shell获得子shell脚本定义的变量,函数,需要使用source或. 在父shell脚本中先加载子shell脚本。
7.2 shell脚本开发规范
编程开发规范很重要,遵守规范,养成良好的编程习惯,可以大大提高开发效率,降低脚本维护成本。
# 规则1: shell脚本的第一行,通常用于指定脚本解释器,叫做解释伴随行( Shebang)
#!/bin/bash 或 #!/bin/sh
# 规则2:shell脚本中尽量不要用中文注释,应用英文注释,防止乱码
# 规则3:字符串赋值给变量应加双引号,并且等号前后不能有空格(shell脚本要注意空格的使用,见第二节,空格)
参考: