Shell脚本学习指南-第六章-变量、判断、重复动作
第6章 变量、判断、重复动作
6.1 变量与算术
6.1.1 变量赋值与环境
- 普通赋值:
变量=值,等号两边不能有空格 readonly:设置只读变量,不能修改、不能重新赋值,用来做常量
export:把变量导出到环境,子进程/子脚本可以继承这个变量
小技巧:只对这一条命令临时修改变量,不改变全局环境
|
|
env命令
env:打印全部环境变量env -i:清空继承的环境,只使用命令行给出的变量
|
|
unset:删除变量 / 删除函数
unset 变量名等价unset -v 变量名:删除变量unset -f 函数名:删除函数
区分
myvar=:变量还存在,只是赋值为空字符串,没有删除unset myvar:彻底把变量删掉
6.1.2 参数展开
参数展开:Shell把
$变量替换成变量实际内容的过程。
- 基础形式
- 简写:
$reminder - 完整写法:
${reminder}用大括号划定变量名边界
什么时候必须写 ${var}?变量后面紧跟字母、数字、下划线。
⚠警告:未定义的变量,参数展开会变成空字符串,会造成灾难!
6.1.2.1 展开运算符
语法:
${变量 符号 字串},冒号:可选
带冒号:条件:变量存在 并且 不为空
去掉冒号:只判断变量是否被定义,就算值是空字符串也视为成立
| 语法 | 作用 | 是否修改变量 |
|---|---|---|
${var:-word} |
变量不存在/为空,返回word;否则返回变量原值 | 否 |
${var:=word} |
变量不存在/为空,把var赋值为word,返回word | 是 |
${var:?message} |
变量不存在/为空,打印报错信息,脚本直接退出 | 否 |
${var:+word} |
变量存在且非空,返回word;否则返回空 | 否 |
示例:
去掉冒号对比演示
6.1.2.2 模式匹配运算符
#对应字符串开头(左边);%对应字符串末尾(右边)
单个符号=最短匹配;双符号=最长匹配
使用shell通配符* ?,不是正则表达式;不会修改原变量,只返回新字符串。
示例变量:path="/home/tolstoy/mem/long.file.name"
| 写法 | 作用 | 示例 | 输出结果 |
|---|---|---|---|
${var#pattern} |
开头,删最短 | ${path#/*/} |
tolstoy/mem/long.file.name |
${var##pattern} |
开头,删最长 | ${path##*/} |
long.file.name |
${var%pattern} |
末尾,删最短 | ${path%.*} |
/home/tolstoy/mem/long.file |
${var%%pattern} |
末尾,删最长 | ${path%%.*} |
/home/tolstoy/mem/long |
额外:${#var} 获取字符串字符个数
6.1.2.2 位置参数
脚本/函数的命令行参数,
$1 $2 … ${10},大于9必须加大括号。
特殊参数:
$#:参数总个数$0:脚本自身名字$*/$@:全部参数
| 写法 | 行为 |
|---|---|
$* |
无引号:按IFS做单词切割,破坏带空格参数,不推荐 |
"$*" |
全部参数合并成一个字符串,用IFS第一个字符分隔 |
$@ |
无引号:同样会切割,不推荐 |
"$@" |
标准写法,保留每个参数原始边界,空格不会被拆开 |
set -- 参数1 参数2:手动设置位置参数;--防止参数以-开头被识别为选项。
shift:位置参数整体向左移动一位;shift n移动n个。
6.1.2.3 Shell特殊内置变量
| 变量 | 说明 |
|---|---|
$? |
上一条命令退出状态码,0成功,非0失败 |
$$ |
当前shell进程PID,常用来生成临时文件名 |
$! |
最近后台命令的PID |
$0 |
脚本名称 |
ENV |
仅交互式shell读取,指定启动执行的脚本文件;脚本(非交互式)完全忽略 |
IFS |
内部字段分隔符,默认:空格、tab、换行,用来切割字符串 |
HOME |
家目录 |
PATH |
命令搜索路径 |
PPID |
父进程PID |
PS1 |
主提示符;PS2续行提示符;PS4调试set‑x提示符 |
PWD |
当前工作目录 |
6.1.3 算术展开 $(( ... ))
shell本身是字符串语言,
$(( ))专门做整数算术运算;不支持小数。
内部运算符和C语言几乎一样,* < > & |等特殊符号不需要反斜杠转义。
规则:
$((表达式)):做计算,把计算结果替换出来(参数展开)- 括号内部变量可以不写
$;位置参数必须写$,如 $(( $1 + $2 )) - 只做计算不会自动保存结果,需要赋值接收
自增自减:
区分:
$(( )):带美元符号,获取计算结果(( ))bash特有,只做判断,不返回数值。
6.2 退出状态
每一条命令结束都会返回一个整数退出状态。
约定:0 =执行成功;非0=失败。
| 退出码 | 含义 |
|---|---|
| 0 | 成功 |
| 1‑125 | 命令自定义错误码 |
| 126 | 找到命令,但无法执行 |
| 127 | 命令找不到 |
| >128 | 进程被信号杀死 |
exit命令
exit [数字]:脚本退出,向外返回退出码。
exit不带数字,等价exit $?,继承上一条命令的退出状态。
坑点:
$?会被每一条新命令覆盖;如果要保存,先存到自定义变量。
6.2.2 if‑elif‑else‑fi
语法
if判断的是命令的退出状态,不是布尔值。
6.2.3 ! && || 逻辑运算符
! 命令:取反,命令失败则整体为真
cmd1 && cmd2:短路与;cmd1成功才执行cmd2cmd1 || cmd2:短路或;cmd1失败才执行cmd2
不要滥用
&& ||代替if,可读性变差。
6.2.4 test 命令([ ])
test 表达式 和 [ 表达式 ] 完全等价。
⚠注意:
[是命令,后面、前面、结尾]前后必须要有空格。
常用测试表达式(节选)
| 表达式 | 条件为真 |
|---|---|
-b file |
file是块设备文件 |
-c file |
file是字符设备文件 |
-d file |
file是目录 |
-e file |
file存在(任意类型文件) |
-f file |
file为普通文件 |
-g file |
file设置了setgid位 |
-h file |
file是符号链接 |
-L file |
file是符号链接(等同于 -h) |
-n string |
string是非null(字符串不为空) |
-p file |
file是命名管道(FIFO文件) |
-r file |
file是可读的 |
-S file |
file是socket套接字文件 |
-s file |
file不是空的(文件大小>0) |
-t n |
文件描述符n指向终端 |
-u file |
file设置了setuid位 |
-w file |
file是可写入的 |
-x file |
file是可执行的;或者目录可被查找访问 |
-z string |
string为null(字符串为空) |
s1 = s2 |
字符串s1与s2相同 |
s1 != s2 |
字符串s1与s2不相同 |
n1 -eq n2 |
整数n1等于n2 |
n1 -ne n2 |
整数n1不等于n2 |
n1 -lt n2 |
整数n1小于n2 |
n1 -gt n2 |
整数n1大于n2 |
n1 -le n2 |
整数n1小于或等于n2 |
n1 -ge n2 |
整数n1大于n2活等于n2 |
历史老技巧:
if [ "X$answer" = "Xyes" ]古老shell兼容手段,防止变量为空造成test语法报错;现代shell只要给变量加双引号"$answer"就不再需要X前缀。
可移植性建议:不要用
test内部的‑a、‑o做与或;改用多个[ ]搭配shell的&& ||
6.3 case 语句
多重模式匹配,相当于其他语言的switch。 语法:
模式支持shell通配符
* ? [];每个分支以;;结束。
示例
6.4 循环
6.4.1 for循环
for 变量 in 列表; do … done
- 省略
in 列表:等价for 变量 in "$@",遍历全部位置参数。
6.4.2 while / until循环
while 条件命令; do ... done:条件成功就继续循环until 条件命令; do ... done:条件失败就继续循环,条件成功退出
示例:不断从尾部截短字符串直到为空
6.4.3 break continue
break:跳出循环;break N跳出N层嵌套循环continue:直接进入下一轮循环;continue N作用多层循环
6.4.4 shift 与选项解析
shift:位置参数整体左移,$#自减。
手写解析选项模板:
getopts 工具
变量说明:
OPTARG:保存带参数选项的参数值OPTIND:下一个待处理参数下标
模板,开头冒号代表关闭内置报错,自己处理错误
getopts :f:vql opt 逐个拆解,语法:getopts 选项串 变量名
- 第一个字符
::开启静默错误模式(关闭getopts自带的报错,我们自己在脚本里处理错误,推荐写法) f::f是选项,后面带冒号 = 这个选项必须跟参数。也就是-f 文件名,-f后面必须写值。v q l:选项后面无冒号,代表只是开关选项,不需要跟参数,直接-v、-q、-l即可。opt:每次循环,getopts 会把当前解析到的选项字母存入变量$opt
|
|
假设脚本名叫 demo.sh,调用:
|
|
参数列表:
$1=-f,$2=data.txt,$3=-v,$4=-q,$5=inputfile,$6=output.txt
- getopts依次解析
-f→-v→-q-f:opt=f,OPTARG=data.txt→file=data.txt-v:opt=v→verbose=1-q:opt=q→quiet=1
- 选项解析完毕,退出while循环;此时
OPTIND=5 shift $((5 -1))→shift 4,删掉前4个参数- shift之后:
$1=inputfile,$2=output.txt,这两个就是剩余普通参数,保存在$@
6.5 Shell函数
定义格式(POSIX标准,推荐)
函数必须先定义,后调用。
函数内拥有自己的位置参数 $1 $2 $@ $#。
⚠重点:
- 函数内部的
exit会终止整个脚本;函数退出返回状态请用return。 return n:给调用者返回退出状态;return等价return $?。- POSIX shell没有局部变量,全部是全局变量,注意变量互相污染。
获取函数输出:用命令替换捕获$(函数名 "$@")
函数的标准输出被$()截获,不再输出屏幕,保存进变量。
- 执行
res=$(demo) - Shell 看见
$( ... ),立刻执行括号内的demo函数。 demo内部运行echo "hello world",屏幕输出文字hello world。
⚠重点:因为包在$()里面,不会直接打印到屏幕!输出被拦截捕获。- Shell 把捕获到的输出文本:
hello world,替换掉$(demo)这整块内容。 - 于是语句实际变成:
res="hello world" - 最后
echo "$res",打印变量:hello world。
6.6 本章总览小结
- 变量:赋值、
readonly只读、export导出环境、unset删除变量/函数。 - 参数展开
${...}:- 基础取值;4套默认值运算符;字符串裁剪
# ## % %%;求长度${#var}
- 基础取值;4套默认值运算符;字符串裁剪
- 位置参数与特殊变量:
$1 $# $0 $* $@ $? $$ IFS ENV。 - 算术展开:
$(( )),整数运算。 - 退出状态:0成功非0失败;
exit脚本退出;return函数返回状态。 - 判断:
if、case、test / [ ];! && ||逻辑。 - 循环:
for while until;break continue;shift;getopts解析命令行选项。 - 函数:封装代码块;拥有独立位置参数;无原生局部变量。
文章作者 会写代码的小郎中
上次更新 2011-07-19
许可协议 CC BY-NC-ND 4.0