前面两篇总结了shell的交互式环境,shell不仅支持交互式使用,也支持脚本编程,shell脚本可由shell解释器执行。本篇在前面两篇的基础上总结一下BASH编程的知识。
变量和参数
上一篇已经介绍了创建变量的方法,本节进一步总结不同类型变量的创建方式。
使用 var=(element element ...) 创建数组变量。引用数组中的元素时使用 ${var[i]} , i 为下标,下标@ 复制原数组,下标 * 也复制原数组,但加双引号时它将原数组作为一个元素。给数组中单个元素赋值时可以用 var[i]=value 。 ${#var[i]} 返回元素的长度, ${#var[*]} 返回数组中元素的个数。
$ array=(Alex Harry Nancy) $ arr1=("${array[@]}") $ arr2=("${array[*]}") $ declare -a declare -a arr1='([0]="Alex" [1]="Harry" [2]="Nancy")' declare -a arr2='([0]="Alex Harry Nancy")' declare -a array='([0]="Alex" [1]="Harry" [2]="Nancy")' $ array[1]=Jerry $ echo ${array[*]} Alex Jerry Nancy $ echo ${array[@]} Alex Jerry Nancy $ echo ${#array[*]} ${#array[1]} 3 5
变量的作用域为当前脚本,可以用 export 声明变量,它将父进程的变量变为对子进程是可用的。函数中的变量默认不是局部的,作用域也为当前脚本,为了避免冲突,可以用 typeset 将它声明为局部变量。
${#var} 返回变量的长度。
${var:-default} 使用变量的值,如果值为空或未赋值,则使用给出的默认值。 ${var:=default} 和前者类似,但它同时会在值为空或未赋值时将默认值赋给变量。 ${var:?message} 可以在值为空或未赋值时显示错误信息,如果未给出 message ,则显示默认错误信息。
$ cd ${dir:?$(date +%T) error, dir not set.} -bash: dir: 10:15:29 error, dir not set.
: 可以给其后的变量赋值但不去执行它,常常用 : ${var:=default} 来给变量设置默认值。
BASH中有一种字符串模式匹配,形式为 ${varOPpattern} , OP 为: # 去除最小匹配前缀, ## 去除最大匹配前缀, % 去除最小匹配后缀, %% 去除最大匹配后缀。
$ file=/home/yeolar/a.sh $ echo ${file##/*/} a.sh $ echo ${file%/*} /home/yeolar
((var=base#n)) 语法可以给变量以其他基数赋值。
$ ((n=8#0101)); echo $n 65
位置参数
位置参数保存命令和命令后的参数。可以用 set 改变位置参数的内容,但不能在脚本内改变命令名。 $# 保存参数的个数, $0 保存命令名, $1 - $n 保存命令后的参数, $@ 和 $* 保存全部参数,和数组变量类似,加双引号时 $* 作为一个参数,而 $@ 作为一组参数。 shift 左移参数。 set 初始化参数( set 没有参数时显示已设置的shell变量,还可以用它来设置shell特性)。
$ cat a.sh echo "cmd: $0, args: $*, argnum: $#" echo "first arg: $1" echo "shift args..." shift echo "cmd: $0, args: $*, argnum: $#" echo "first arg: $1" set "$@" echo "first arg: $1" set "$*" echo "first arg: $1" set a b c echo $* $ bash a.sh x y z cmd: a.sh, args: x y z, argnum: 3 first arg: x shift args... cmd: a.sh, args: y z, argnum: 2 first arg: y first arg: y first arg: y z a b c
还有一些特殊参数: $$ 保存当前(shell)进程的PID, $! 保存最近转入后台运行的进程的PID, $? 保存上一个命令的返回状态码。
特殊参数和位置参数不能通过赋值语句改变。
表达式
可以用 let "expr" 或 ((expr)) 对算术表达式求值,多个表达式可以分别用空格和逗号分开。
$ x=1 y=1 z=0 $ let "x = x * 10 + y" z=z+1 $ echo $x $y $z 11 1 1 $ ((x = x * 10 + y, z = z + 1)) $ echo $x $y $z 111 1 2
条件表达式用 [[expr]] 求值。有个比较特别的运算符是 = ,在条件表达式中可以用它来判断相等。
运算符
BASH支持绝大部分C语言的运算符,有些运算符增加了一些特定语法结构中的含义,如管道。
控制流
if...then
if...then 的语法如下:
if test-command then command [elif test-command then command ...] [else command] fi
为了减少缩进,常常用 ; 将 then 写到上一行。
test-command 为测试命令,可以用 test 命令进行测试,但一般使用它的同义词 [] 。
# 检查参数个数 if [ $# -eq 0 ]; then # 等价于:if test $# -eq 0; then echo "Usage: cmd arg" 1>&2 exit 1 fi
对于数值的测试,可以使用:
字符串的比较可以使用 = 和 != 。
下面是一些和文件相关的检查选项:
for
for 的语法如下:
for var[ in list] do command done
seq 为可展开为列表的表达式。可以省略 in list ,这时列表为命令的参数,即 $@ 。
# whos脚本,打印用户名和全名 Usage: whos user ... for user; do gawk -F: '{print $1, $5}' /etc/passwd | grep -i "$user" done
while和until
while 和 until 类似,区别是 until 在 do 分支执行后测试,并且是测试结果为假时循环,测试结果为真时跳出循环。
语法为:
while test-command do command done until test-command do command done
# 查找拼写错误的词 while read line; do if ! grep "^$line$" "$1" > /dev/null; then echo $line fi done # 猜名字 rightname=yeolar until [ "$name" = "$rightname" ]; do echo -n "Guess: " read name done echo "Good, you've got it."
break和continue
break 和 continue 可以用于在 for 、 while 和 until 语句中跳出循环和继续下一循环。
case
case 用于多路选择。语法为:
case test-command in pattern) command ;; [pattern) command ;; ...] esac
pattern 可以使用 * 匹配任意字符串, ? 匹配单个字符, [...] 给出可匹配的字符, | 分离不同的选择。
# 命令选择 echo -e "\ncmds: A for date, B for who, C for pwd.\n" echo -n "Enter A, B or C: " read c case "$c" in a|A) date ;; b|B) who ;; c|C) pwd ;; *) echo "Invalid choice: $c" ;; esac
select
select 显示一个菜单,根据用户的选择给变量赋予相应的值,然后执行命令。退出 select 可以使用 break,或者 exit 退出整个脚本。
select var[ in list] do command done
和 for 一样,省略 in list 会用命令参数代替。 PS3 设置 select 的提示符,一般会设置为需要的提示语句。
# 命令菜单 PS3="Choose what you want to do: " select c in date who pwd exit; do if [ "$c" == "" ]; then echo -e "Invalid choice.\n" continue elif [ $c = exit ]; then echo "quit" break fi echo "You choose: $c" if [ $c = date ]; then date elif [ $c = who ]; then who elif [ $c = pwd ]; then pwd fi echo "" done
文件描述符
在BASH中,使用 exec 命令执行文件描述符相关的操作:
exec n> outfile 打开outfile作为输出文件,分配文件描述符n exec n< infile 打开infile作为输入文件,分配文件描述符n exec n<&m 打开或重定向文件描述符n,作为文件描述符m的副本 exec n<&- 关闭文件描述符n
内置命令
前面已经提到了很多BASH内置命令,这里做个总结和补充。
read 支持一些选项:
有个特殊的变量 REPLY ,保存读取的输入。
getopts 的语法为 getopts optstr var[ arg ...] , optstr 给出合法的字母选项, var 保存每次接收的选项的值, arg 为将处理的参数,省略将默认处理命令行参数。 optstr 以 : 开始时由脚本负责产生错误信息,否则由 getopts 自己产生。 optstr 用字母后的 : 表示选项接受值, OPTARG 保存和选项相关的值。OPTIND 保存选项的索引,起始值为1。
exec 既可执行脚本又可执行程序,它不创建新进程,把当前(shell)进程替换为要执行的内容。
trap 的用法是 trap ['command'] [signal] ,它捕获信号,执行 command ,如果没有 command ,那么重置trap 。
trap '' 2 15 # 捕获并忽略中断 trap 'echo Interrupted.; exit 1' INT # 捕获中断,打印信息并退出
kill 给进程发送信号,如终止进程。后面会详细讲有关进程的内容。
shell脚本
根据本篇介绍的内容和前两篇的一些知识就可以编写shell脚本了。
像Python脚本一样可以用 #! 为脚本定义解释器。
#!/bin/bash # 或者: #!/usr/bin/env bash