bash shell编程:
1. 读取用户变量:
read命令是用于从终端或者文件中读取输入的内建命令,read命令读取整行输入,每行末尾的换行符不被读入。在read命令后面,如果没有指定变量名,读取的数据将被自动赋值给特定的变量reply。下面的列表给出了read命令的常用方式:
命令格式 | 描述 |
read answer | 从标准输入读取输入并赋值给变量answer。 |
read first last | 从标准输入读取输入到第一个空格或者回车,将输入的第一个单词放到变量first中,并将该行其他的输入放在变量last中。 |
read | 从标准输入读取一行并赋值给特定变量reply。 |
read -a arrayname | 把单词清单读入arrayname的数组里。 |
read -p prompt | 打印提示,等待输入,并将输入存储在reply中。 |
read -r line | 允许输入包含反斜杠。 |
见下面的示例(绿色高亮部分的文本为控制台手工输入信息):
/> read answer #等待读取输入,直到回车后表示输入完毕,并将输入赋值给变量answer
hello #控制台输入hello
/> echo $answer #打印变量
hello
#等待一组输入,每个单词之间使用空格隔开,直到回车结束,并分别将单词依次赋值给这三个读入变量。
/> read one two three
1 2 3 #在控制台输入1 2 3,它们之间用空格隔开。
/> echo "one = $one, two = $two, three = $three"
one = 1, two = 2, three = 3
/> read #等待控制台输入,并将结果赋值给特定内置变量reply。
this is reply #在控制台输入该行。
/> echo $reply #打印输出特定内置变量reply,以确认是否被正确赋值。
this is reply
/> read -p "enter your name: " #输出"enter your name: "文本提示,同时等待输入,并将结果赋值给reply。
enter you name: stephen #在提示文本之后输入stephen
/> echo $reply
stephen
#等待控制台输入,并将输入信息视为数组,赋值给数组变量friends,输入信息用空格隔开数组的每个元素
/> read -a friends
tim tom helen
/> echo "i have ${#friends} friends"
i have 3 friends
/> echo "they are ${friends[0]}, ${friends[1]} and ${friends[2]}."
they are tim, tom and helen.
2. 状态判断:
test是shell中提供的内置命令,主要用于状态的检验,如果结果为0,表示成功,否则表示失败。见如下示例:
/> name=stephen
/> test $name != stephen
/> echo $?
1
需要注意的是test命令不支持shell中提供的各种通配符,如:
/> test $name = [ss]tephen
/> echo $?
1
test命令还可以中括号予以替换,其语义保持不变,如:
/> [ $name = stephen ]
/> echo $?
0
在shell中还提供了另外一种用于状态判断的方式:[[ expr ]],和test不同的是,该方式中的表达式支持通配符,如:
/> name=stephen
/> [[ $name == [ss]tephen ]]
/> echo $?
0
#在[[ expression ]]中,expression可以包含&&(逻辑与)和||(逻辑或)。
/> [[ $name == [ss]tephen && $friend == "jose" ]]
/> echo $?
1
/> shopt -s extglob #打开shell的扩展匹配模式。
/> name=tommy
# "[tt]o (m)y"的含义为,以t或t开头,后面跟着一个o,再跟着一个或者多个m,最后以一个y结尾。
/> [[ $name == [tt]o (m)y ]]
/> echo $?
0
在shell中还提供了let命令的判断方式: (( expr )),该方式的expr部分,和c语言提供的表达式规则一致,如:
/> x=2
/> y=3
/> (( x > 2 ))
/> echo $?
1
/> (( x < 2 ))
/> echo $?
0
/> (( x == 2 && y == 3 ))
/> echo $?
0
/> (( x > 2 || y < 3 ))
/> echo $?
1
下面的表格是test命令支持的操作符:
判断操作符 | 判断为真的条件 |
字符串判断 | |
[ stringa=stringb ] | stringa等于stringb |
[ stringa==stringb ] | stringa等于stringb |
[ stringa!=stringb ] | stringa不等于stringb |
[ string ] | string不为空 |
[ -z string ] | string长度为0 |
[ -n string ] | string长度不为0 |
逻辑判断 | |
[ stringa -a stringb ] | stringa和stringb都是真 |
[ stringa -o stringb ] | stringa或stringb是真 |
[ !string ] | string不为真 |
逻辑判断(复合判断) | |
[[ pattern1 && pattern2 ]] | pattern1和pattern2都是真 |
[[ pattern1 || pattern2 ] | pattern1或pattern2是真 |
[[ !pattern ]] | pattern不为真 |
整数判断 | |
[ inta -eq intb ] | inta等于intb |
[ inta -ne intb ] | inta不等于intb |
[ inta -gt intb ] | inta大于intb |
[ inta -ge intb ] | inta大于等于intb |
[ inta -lt intb ] | inta小于intb |
[ inta -le intb ] | inta小于等于intb |
文件判断中的二进制操作 | |
[ filea -nt fileb ] | filea比fileb新 |
[ filea -ot fileb ] | filea比fileb旧 |
[ filea -ef fileb ] | filea和fileb有相同的设备或者inode值 |
文件检验 | |
[ -d $file ] or [[ -d $file ]] | file为目录且存在时为真 |
[ -e $file ] or [[ -e $file ]] | file为文件且存在时为真 |
[ -f $file ] or [[ -f $file ]] | file为非目录普通文件存在时为真 |
[ -s $file ] or [[ -s $file ]] | file文件存在, 且长度不为0时为真 |
[ -l $file ] or [[ -l $file ]] | file为链接符且存在时为真 |
[ -r $file ] or [[ -r $file ]] | file文件存在且可读时为真 |
[ -w $file ] or [[ -w $file ]] | file文件存在且可写时为真 |
[ -x $file ] or [[ -x $file ]] | file文件存在且可执行时为真 |
注:在逻辑判断(复合判读中),pattern可以包含元字符,在字符串的判断中,pattern2必须被包含在引号中。
let命令支持的操作符和c语言中支持的操作符完全相同,如:
,-,*,/,% 加,减,乘,除,去模
>>,<< 右移和左移
>=,<=,==,!= 大于等于,小于等于,等于,不等于
&,|,^ 按位与,或,非
&&,||,! 逻辑与,逻辑或和取反
还有其含义和c语言等同的快捷操作符,如=,*=,/=,%=, =,-=,<<=,>>=,&=,|=,^=。
3. 流程控制语句:
if语句格式如下:
#if语句的后面是shell命令,如果该命令执行成功返回0,则执行then后面的命令。
if command
then
command
command
fi
#用test命令测试其后面expression的结果,如果为真,则执行then后面的命令。
if test expression
then
command
fi
#下面的格式和test expression等同
if [ string/numeric expression ]
then
command
fi
#下面的两种格式也可以用于判断语句的条件表达式,而且它们也是目前比较常用的两种。
if [[ string expression ]]
then
command
fi
if (( numeric expression )) #let表达式
then
command
fi
见如下示例:
/> cat > test1.sh #从命令行直接编辑test1.sh文件。
echo -e "are you ok(y/n)? \c"
read answer
#这里的$answer变量必须要用双引号扩住,否则判断将失败。当变量$answer等于y或y时,支持下面的echo命令。
if [ "$answer" = y -o "$answer" = y ]
then
echo "glad to see it."
fi
ctrl d
/> . ./test1.sh
are you ok(y/n)? y
glad to see it.
上面的判断还可以替换为:
/> cat > test2.sh
echo -e "are you ok(y/n or maybe)? \c"
read answer
# [[ ]]复合命令操作符允许其中的表达式包含元字符,这里输入以y或y开头的任意单词,或maybe都执行then后面的echo。
if [[ $answer == [yy]* || $answer = maybe ]]
then
echo "glad to hear it.
fi
ctrl d
/> . ./test2.sh
are you ok(y/n or maybe)? yes
glad to hear it.
下面的例子将使用shell中的扩展通配模式。
/> shopt -s extglob #打开该扩展模式
/> answer="not really"
/> if [[ $answer = [nn]o?( way |t really) ]]
> then
> echo "i am sorry."
> fi
i am sorry.
对于本示例中的扩展通配符,这里需要给出一个具体的解释。[nn]o匹配no或no,?( way|t really)则表示0个或1个( way或t really),因此answer变量匹配的字符串为no、no、not really、not really、no way、no way。
下面的示例使用了let命令操作符,如:
/> cat > test3.sh
if (( $# != 2 )) #等同于 [ $# -ne 2 ]
then
echo "usage: $0 arg1 arg2" 1>&2
exit 1 #exit退出值为0-255之间,只有0表示成功。
fi
if (( $1 < 0 || $1 > 30 )) #等同于 [ $1 -lt 0 -o $1 -gt 30 ]
then
echo "arg1 is out of range."
exit 2
fi
if (( $2 <= 20 )) #等同于 [ $2 -le 20 ]
then
echo "arg2 is out of range."
fi
ctrl d
/> sh ./test3.sh
usage: ./test3.sh arg1 arg2
/> echo $? #shell脚本的退出值为exit的参数值。
1
/> sh ./test3.sh 40 30
arg1 is out of range.
/> echo $?
2
下面的示例为如何在if的条件表达式中检验空变量:
/> cat > test4.sh
if [ "$name" = "" ] #双引号就表示空字符串。
then
echo "name is null."
fi
ctrl d
/> . ./test4.sh
name is null.
if/elif/else语句的使用方式和if语句极为相似,相信有编程经验的人都不会陌生,这里就不再赘述了,其格式如下:
if command
then
command
elif command
then
command
else
command
fi
见如下示例脚本:
/> cat > test5.sh
echo -e "how old are you? \c"
read age
if [ $age -lt 0 -o $age -gt 120 ] #等同于 (( age < 0 || age > 120 ))
then
echo "you are so old."
elif [ $age -ge 0 -a $age -le 12 ] #等同于 (( age >= 0 && age <= 12 ))
then
echo "you are child."
elif [ $age -ge 13 -a $age -le 19 ] #等同于 (( age >= 13 && age <= 19 ))
then
echo "you are 13--19 years old."
elif [ $age -ge 20 -a $age -le 29 ] #等同于 (( age >= 20 && age <= 29 ))
then
echo "you are 20--29 years old."
elif [ $age -ge 30 -a $age -le 39 ] #等同于 (( age >= 30 && age <= 39 ))
then
echo "you are 30--39 years old."
else
echo "you are above 40."
fi
ctrl d
/> . ./test5.sh
how old are you? 50
you are above 40.
case语句格式如下:
case variable in
value1)
command
;; #相同于c语言中case语句内的break。
value2)
command
;;
*) #相同于c语言中switch语句内的default
command
;;
esac
见如下示例脚本:
/> cat > test6.sh
#!/bin/sh
echo -n "choose a color: "
read color
case "$color" in
[bb]l??)
echo "you select blue color."
;;
[gg]ree*)
echo "you select green color."
;;
red|orange)
echo "you select red or orange."
;;
*)
echo "you select other color."
;;
esac
echo "out of case command."
/> . ./test6.sh
choose a color: green
you select green color.
out of case command.
4. 循环语句:
bash shell中主要提供了三种循环方式:for、while和until。
for循环声明格式:
for variable in word_list
do
command
done
见如下示例脚本:
/> cat > test7.sh
for score in math english physics chemist #for将循环读取in后面的单词列表,类似于java的for-each。
do
echo "score = $score"
done
echo "out of for loop"
ctrl d
/> . ./test7.sh
score = math
score = english
score = physics
score = chemist
out of for loop
/> cat > mylist #构造数据文件
tom
patty
ann
jake
ctrl d
/> cat > test8.sh
#!/bin/sh
for person in $(cat mylist) #for将循环读取cat mylist命令的执行结果。
do
echo "person = $person"
done
echo "out of for loop."
ctrl d
/> . ./test8.sh
person = tom
person = patty
person = ann
person = jake
out of for loop.
/> cat > test9.sh
for file in test[1-8].sh #for将读取test1-test8,后缀为.sh的文件
do
if [ -f $file ] #判断文件在当前目录是否存在。
then
echo "$file exists."
fi
done
ctrl d
/> . ./test9.sh
test2.sh exists.
test3.sh exists.
test4.sh exists.
test5.sh exists.
test6.sh exists.
test7.sh exists.
test8.sh exists.
/> cat > test10.sh
for name in $* #读取脚本的命令行参数数组,还可以写成for name的简化形式。
do
echo "hi, $name"
done
ctrl d
/> . ./test10.sh stephen ann
hi, stephen
hi, ann
while循环声明格式:
while command #如果command命令的执行结果为0,或条件判断为真时,执行循环体内的命令。
do
command
done
见如下示例脚本:
/> cat > test1.sh
num=0
while (( num < 10 )) #等同于 [ $num -lt 10 ]
do
echo -n "$num "
let num =1
done
echo -e "\nhere's out of loop."
ctrl d
/> . ./test1.sh
0 1 2 3 4 5 6 7 8 9
here's out of loop.
/> cat > test2.sh
go=start
echo type q to quit.
while [[ -n $go ]] #等同于[ -n "$go" ],如使用该风格,$go需要被双引号括起。
do
echo -n how are you.
read word
if [[ $word == [qq] ]] #等同于[ "$word" = q -o "$word" = q ]
then
echo bye.
go= #将go变量的值置空。
fi
done
ctrl d
/> . ./test2.sh
how are you. hi
how are you. q
bye.
until循环声明格式:
until command #其判断条件和while正好相反,即command返回非0,或条件为假时执行循环体内的命令。
do
command
done
见如下示例脚本:
/> cat > test3.sh
until who | grep stephen #循环体内的命令将被执行,直到stephen登录,即grep命令的返回值为0时才退出循环。
do
sleep 1
echo "stephen still doesn't login."
done
ctrl d
shift命令声明格式:shift [n]
shift命令用来把脚本的位置参数列表向左移动指定的位数(n),如果shift没有参数,则将参数列表向左移动一位。一旦移位发生,被移出列表的参数就被永远删除了。通常在while循环中,shift用来读取列表中的参数变量。
见如下示例脚本:
/> set stephen ann sheryl mark #设置4个参数变量。
/> shift #向左移动参数列表一次,将stephen移出参数列表。
/> echo $*
ann sheryl mark
/> shift 2 #继续向左移动两位,将sheryl和ann移出参数列表
/> echo $*
mark
/> shift 2 #继续向左移动两位,由于参数列表中只有mark了,因此本次移动失败。
/> echo $*
mark
/> cat > test4.sh
while (( $# > 0 )) #等同于 [ $# -gt 0 ]
do
echo $*
shift
done
ctrl d
/> . ./test4.sh a b c d e
a b c d e
b c d e
c d e
d e
e
break命令声明格式:break [n]
和c语言不同的是,shell中break命令携带一个参数,即可以指定退出循环的层数。如果没有指定,其行为和c语言一样,即退出最内层循环。如果指定循环的层数,则退出指定层数的循环体。如果有3层嵌套循环,其中最外层的为1,中间的为2,最里面的是3。
见如下示例脚本:
/> cat > test5.sh
while true
do
echo -n "are you ready to move on?"
read answer
if [[ $answer == [yy] ]]
then
break
else
echo "come on."
fi
done
echo "here we are."
ctrl d
/> . ./test5.sh
are you ready to move on? y
here we are
continue命令声明格式:continue [n]
和c语言不同的是,shell中continue命令携带一个参数,即可以跳转到指定层级的循环顶部。如果没有指定,其行为和c语言一样,即跳转到最内层循环的顶部。如果指定循环的层数,则跳转到指定层级循环的顶部。如果有3层嵌套循环,其中最外层的为3,中间的为2,最里面的是1。
/> cat maillist #测试数据文件maillist的内容为以下信息。
stephen
ann
sheryl
mark
/> cat > test6.sh
for name in $(cat maillist)
do
if [[ $name == stephen ]]; then
continue
else
echo "hello, $name."
fi
done
ctrl d
/> . ./test6.sh
hello, ann.
hello, sheryl.
hello, mark.
i/o重新定向和子shell:
文件中的输入可以通过管道重新定向给一个循环,输出也可以通过管道重新定向给一个文件。shell启动一个子shell来处理i/o重新定向和管道。在循环终止时,循环内部定义的任何变量对于脚本的其他部分来说都是不可见的。
/> cat > demodata #为下面的脚本构造测试数据
abc
def
ghi
crtl d
/> cat > test7.sh
if (( $# < 1 )) #如果脚本参数的数量小于1,则给出错误提示后退出。
then
echo "usage: $0 filename " >&2
exit 1
fi
count=1
cat $1 | while read line #参数一中的文件被cat命令输出后,通过管道逐行输出给while read line。
do
let $((count == 1)) && echo "processing file $1..." > /dev/tty #该行的echo将输出到当前终端窗口。
echo -e "$count\t$line" #将输出行号和文件中该行的内容,中间用制表符隔开。
let count =1
done > outfile #将while循环中所有的输出,除了>/dev/tty之外,其它的全部输出到outfile文件。
ctrl d
/> . ./test7.sh demodata #只有一行输出,其余的都输出到outfile中了。
processing file demodata...
/> cat outfile
1 abc
2 def
3 ghi
/> cat > test8.sh
for i in 9 7 2 3 5 4
do
echo $i
done | sort -n #直接将echo的输出通过管道重定向sort命令。
ctrl d
/> . ./test8.sh
2
3
4
5
7
9
5. ifs和循环:
shell的内部域分隔符可以是空格、制表符和换行符。它可以作为命令的分隔符用在例如read、set和for等命令中。如果在列表中使用不同的分隔符,用户可以自己定义这个符号。在修改之前将ifs原始符号的值保存在另外一个变量中,这样在需要的时候还可以还原。
见如下示例脚本:
/> cat > test9.sh
names=stephen:ann:sheryl:john #names变量包含的值用冒号分隔。
oldifs=$ifs #保留原有ifs到oldifs变量,便于后面的还原。
ifs=":"
for friends in $names #这是遍历以冒号分隔的names变量值。
do
echo hi $friends
done
ifs=$oldifs #将ifs还原为原有的值。
set jerry tom angela
for classmates in $* #再以原有ifs的值变量参数列表。
do
echo hello $classmates
done
ctrl d
/> . ./test9.sh
hi stephen
hi ann
hi sheryl
hi john
hello jerry
hello tom
hello angela
6. 函数:
shell中函数的职能以及优势和c语言或其它开发语言基本相同,只是语法格式上的一些差异。下面是shell中使用函数的一些基本规则:
1) 函数在使用前必须定义。
2) 函数在当前环境下运行,它和调用它的脚本共享变量,并通过位置参量传递参数。而该位置参量将仅限于该函数,不会影响到脚本的其它地方。
3) 通过local函数可以在函数内建立本地变量,该变量在出了函数的作用域之后将不在有效。
4) 函数中调用exit,也将退出整个脚本。
5) 函数中的return命令返回函数中最后一个命令的退出状态或给定的参数值,该参数值的范围是0-256之间。如果没有return命令,函数将返回最后一个shell的退出值。
6) 如果函数保存在其它文件中,就必须通过source或dot命令把它们装入当前脚本。
7) 函数可以递归。
8) 将函数从shell中清空需要执行:unset -f function_name。
9) 将函数输出到子shell需要执行:export -f function_name。
10) 可以像捕捉shell命令的返回值一样获取函数的返回值,如$(function_name)。
shell中函数的声明格式如下:
function function_name { command; command; }
见如下示例脚本:
/> cat > test1.sh
function increment() { #定义函数increment。
local sum #定义本地变量sum。
let "sum=$1 1"
return $sum #返回值是sum的值。
}
echo -n "the num is "
increment 5 #increment函数调用。
echo $? #输出increment函数的返回值。
ctrl d
/> . ./test1.sh
the num is 6
7. 陷阱信号(trap):
在shell程序运行的时候,可能收到各种信号,有的来自于操作系统,有的来自于键盘,而该shell在收到信号后就立刻终止运行。但是在有些时候,你可能并不希望在信号到达时,程序就立刻停止运行并退出。而是他能希望忽略这个信号而一直在运行,或者在退出前作一些清除操作。trap命令就允许你控制你的程序在收到信号以后的行为。
其格式如下:
trap 'command; command' signal-number
trap 'command; command' signal-name
trap signal-number
trap signal-name
后面的两种形式主要用于信号复位,即恢复处理该信号的缺省行为。还需要说明的是,如果trap后面的命令是使用单引号括起来的,那么该命令只有在捕获到指定信号时才被执行。如果是双引号,则是在trap设置时就可以执行变量和命令替换了。
下面是系统给出的信号数字和信号名称的对照表:
1)sighup 2)sigint 3)sigquit 4)sigill 5)sigtrap 6)sigabrt 7)sigbus 8)sigfpe
9)sigkill 10) sigusr1 11)sigegv 12)sigusr2 13)sigpipe 14)sigalrm 15)sigterm 17)sigchld
18)sigcont 19)sigstop ... ...
见如下示例脚本:
/> trap 'rm tmp*;exit 1' 1 2 15 #该命令表示在收到信号1、2和15时,该脚本将先执行rm tmp*,然后exit 1退出脚本。
/> trap 2 #当收到信号2时,将恢复为以前的动作,即退出。
/> trap " " 1 2 #当收到信号1和2时,将忽略这两个信号。
/> trap - #表示恢复所有信号处理的原始值。
/> trap 'trap 2' 2 #在第一次收到信号2时,执行trap 2,这时将信号2的处理恢复为缺省模式。在收到信号2时,shell程序退出。
/> cat > test2.sh
trap 'echo "control c will not terminate $0."' 2 #捕获信号2,即在键盘上按ctrl c。
trap 'echo "control \ will not terminate $0."' 3 #捕获信号3,即在键盘上按ctrl \。
echo "enter stop to quit shell."
while true #无限循环。
do
echo -n "go go...."
read
if [[ $reply == [ss]top ]] #直到输入stop或stop才退出循环和脚本。
then
break
fi
done
ctrl d
/> . ./test2.sh
enter stop to quit shell.
go go....^ccontrol c will not terminate -bash.
^\control \ will not terminate -bash.
stop
8. 用getopts处理命令行选项:
这里的getopts命令和c语言中的getopt几乎是一致的,因为脚本的位置参量在有些时候是失效的,如ls -lrt等。这时候-ltr都会被保存在$1中,而我们实际需要的则是三个展开的选项,即-l、-r和-t。见如下带有getopts的示例脚本:
/> cat > test3.sh
#!/bin/sh
while getopts xy options #x和y是合法的选项,并且将-x读入到变量options中,读入时会将x前面的横线去掉。
do
case $options in
x) echo "you entered -x as an option" ;;
y) echo "you entered -y as an option" ;;
esac
done
/> ./test3.sh -xy
you entered -x as an option
you entered -y as an option
/> ./test3.sh -x
you entered -x as an option
/> ./test3.sh -b #如果输入非法选项,getopts会把错误信息输出到标准错误。
./test3.sh: illegal option -- b
/> ./test3.sh b #该命令不会有执行结果,因为b的前面有没横线,因此是非法选项,将会导致getopts停止处理并退出。
/> cat > test4.sh
#!/bin/sh
while getopts xy options 2>/dev/null #如果再出现选项错误的情况,该重定向会将错误输出到/dev/null。
do
case $options in
x) echo "you entered -x as an option" ;;
y) echo "you entered -y as an option" ;;
\?) echo "only -x and -y are valid options" 1>&2 # ?表示所有错误的选项,即非-x和-y的选项。
esac
done
/> . ./test4.sh -g #遇到错误的选项将直接执行\?)内的代码。
only -x and -y are valid options
/> . ./test4.sh -xg
you entered -x as an option
only -x and -y are valid options
/> cat > test5.sh
#!/bin/sh
while getopts xyz: arguments 2>/dev/null #z选项后面的冒号用于提示getopts,z选项后面必须有一个参数。
do
case $arguments in
x) echo "you entered -x as an option." ;;
y) echo "you entered -y as an option." ;;
z) echo "you entered -z as an option." #z的后面会紧跟一个参数,该参数保存在内置变量optarg中。
echo "\$optarg is $optarg.";
;;
\?) echo "usage opts4 [-xy] [-z argument]"
exit 1 ;;
esac
done
echo "the number of arguments passed was $(( $optind - 1 ))" #optind保存一下将被处理的选项的位置,他是永远比实际命令行参数多1的数。
/> ./test5.sh -xyz foo
you entered -x as an option.
you entered -y as an option.
you entered -z as an option.
$optarg is foo.
the number of arguments passed was 2
/> ./test5.sh -x -y -z boo
you entered -x as an option.
you entered -y as an option.
you entered -z as an option.
$optarg is boo.
the number of arguments passed was 4
9. eval命令与命令行解析:
eval命令可以对命令行求值,做shell替换,并执行命令行,通常在普通命令行解析不能满足要求时使用。
/> set a b c d
/> echo the last argument is \$$#
the last argument is $4
/> eval echo the last argument is \$$# #eval命令先进行了变量替换,之后再执行echo命令。
the last argument is d
转载自stephen liu
,仅做学习收藏用途。
,
二十三. bash shell编程:
1. 读取用户变量:
read命令是用于从终端或者文件中读取输入的内建命令,read命令读取整行输入,每行末尾的换行符不被读入。在read命令后面,如果没有指定变量名,读取的数据将被自动赋值给特定的变量reply。下面的列表给出了read命令的常用方式:
命令格式 | 描述 |
read answer | 从标准输入读取输入并赋值给变量answer。 |
read first last | 从标准输入读取输入到第一个空格或者回车,将输入的第一个单词放到变量first中,并将该行其他的输入放在变量last中。 |
read | 从标准输入读取一行并赋值给特定变量reply。 |
read -a arrayname | 把单词清单读入arrayname的数组里。 |
read -p prompt | 打印提示,等待输入,并将输入存储在reply中。 |
read -r line | 允许输入包含反斜杠。 |
见下面的示例(绿色高亮部分的文本为控制台手工输入信息):
/> read answer #等待读取输入,直到回车后表示输入完毕,并将输入赋值给变量answer
hello #控制台输入hello
/> echo $answer #打印变量
hello
#等待一组输入,每个单词之间使用空格隔开,直到回车结束,并分别将单词依次赋值给这三个读入变量。
/> read one two three
1 2 3 #在控制台输入1 2 3,它们之间用空格隔开。
/> echo "one = $one, two = $two, three = $three"
one = 1, two = 2, three = 3
/> read #等待控制台输入,并将结果赋值给特定内置变量reply。
this is reply #在控制台输入该行。
/> echo $reply #打印输出特定内置变量reply,以确认是否被正确赋值。
this is reply
/> read -p "enter your name: " #输出"enter your name: "文本提示,同时等待输入,并将结果赋值给reply。
enter you name: stephen #在提示文本之后输入stephen
/> echo $reply
stephen
#等待控制台输入,并将输入信息视为数组,赋值给数组变量friends,输入信息用空格隔开数组的每个元素
/> read -a friends
tim tom helen
/> echo "i have ${#friends} friends"
i have 3 friends
/> echo "they are ${friends[0]}, ${friends[1]} and ${friends[2]}."
they are tim, tom and helen.
2. 状态判断:
test是shell中提供的内置命令,主要用于状态的检验,如果结果为0,表示成功,否则表示失败。见如下示例:
/> name=stephen
/> test $name != stephen
/> echo $?
1
需要注意的是test命令不支持shell中提供的各种通配符,如:
/> test $name = [ss]tephen
/> echo $?
1
test命令还可以中括号予以替换,其语义保持不变,如:
/> [ $name = stephen ]
/> echo $?
0
在shell中还提供了另外一种用于状态判断的方式:[[ expr ]],和test不同的是,该方式中的表达式支持通配符,如:
/> name=stephen
/> [[ $name == [ss]tephen ]]
/> echo $?
0
#在[[ expression ]]中,expression可以包含&&(逻辑与)和||(逻辑或)。
/> [[ $name == [ss]tephen && $friend == "jose" ]]
/> echo $?
1
/> shopt -s extglob #打开shell的扩展匹配模式。
/> name=tommy
# "[tt]o (m)y"的含义为,以t或t开头,后面跟着一个o,再跟着一个或者多个m,最后以一个y结尾。
/> [[ $name == [tt]o (m)y ]]
/> echo $?
0
在shell中还提供了let命令的判断方式: (( expr )),该方式的expr部分,和c语言提供的表达式规则一致,如:
/> x=2
/> y=3
/> (( x > 2 ))
/> echo $?
1
/> (( x < 2 ))
/> echo $?
0
/> (( x == 2 && y == 3 ))
/> echo $?
0
/> (( x > 2 || y < 3 ))
/> echo $?
1
下面的表格是test命令支持的操作符:
判断操作符 | 判断为真的条件 |
字符串判断 | |
[ stringa=stringb ] | stringa等于stringb |
[ stringa==stringb ] | stringa等于stringb |
[ stringa!=stringb ] | stringa不等于stringb |
[ string ] | string不为空 |
[ -z string ] | string长度为0 |
[ -n string ] | string长度不为0 |
逻辑判断 | |
[ stringa -a stringb ] | stringa和stringb都是真 |
[ stringa -o stringb ] | stringa或stringb是真 |
[ !string ] | string不为真 |
逻辑判断(复合判断) | |
[[ pattern1 && pattern2 ]] | pattern1和pattern2都是真 |
[[ pattern1 || pattern2 ] | pattern1或pattern2是真 |
[[ !pattern ]] | pattern不为真 |
整数判断 | |
[ inta -eq intb ] | inta等于intb |
[ inta -ne intb ] | inta不等于intb |
[ inta -gt intb ] | inta大于intb |
[ inta -ge intb ] | inta大于等于intb |
[ inta -lt intb ] | inta小于intb |
[ inta -le intb ] | inta小于等于intb |
文件判断中的二进制操作 | |
[ filea -nt fileb ] | filea比fileb新 |
[ filea -ot fileb ] | filea比fileb旧 |
[ filea -ef fileb ] | filea和fileb有相同的设备或者inode值 |
文件检验 | |
[ -d $file ] or [[ -d $file ]] | file为目录且存在时为真 |
[ -e $file ] or [[ -e $file ]] | file为文件且存在时为真 |
[ -f $file ] or [[ -f $file ]] | file为非目录普通文件存在时为真 |
[ -s $file ] or [[ -s $file ]] | file文件存在, 且长度不为0时为真 |
[ -l $file ] or [[ -l $file ]] | file为链接符且存在时为真 |
[ -r $file ] or [[ -r $file ]] | file文件存在且可读时为真 |
[ -w $file ] or [[ -w $file ]] | file文件存在且可写时为真 |
[ -x $file ] or [[ -x $file ]] | file文件存在且可执行时为真 |
注:在逻辑判断(复合判读中),pattern可以包含元字符,在字符串的判断中,pattern2必须被包含在引号中。
let命令支持的操作符和c语言中支持的操作符完全相同,如:
,-,,/,% 加,减,乘,除,去模
>>,<< 右移和左移
>=,<=,,!= 大于等于,小于等于,等于,不等于
&,|,^ 按位与,或,非
&&,||,! 逻辑与,逻辑或和取反
还有其含义和c语言等同的快捷操作符,如=,=,/=,%=, =,-=,<<=,>>=,&=,|=,^=。
3. 流程控制语句:
if语句格式如下:
#if语句的后面是shell命令,如果该命令执行成功返回0,则执行then后面的命令。
if command
then
command
command
fi
#用test命令测试其后面expression的结果,如果为真,则执行then后面的命令。
if test expression
then
command
fi
#下面的格式和test expression等同
if [ string/numeric expression ]
then
command
fi
#下面的两种格式也可以用于判断语句的条件表达式,而且它们也是目前比较常用的两种。
if [[ string expression ]]
then
command
fi
if (( numeric expression )) #let表达式
then
command
fi
见如下示例:
/> cat > test1.sh #从命令行直接编辑test1.sh文件。
echo -e "are you ok(y/n)? \c"
read answer
#这里的$answer变量必须要用双引号扩住,否则判断将失败。当变量$answer等于y或y时,支持下面的echo命令。
if [ "$answer" = y -o "$answer" = y ]
then
echo "glad to see it."
fi
ctrl d
/> . ./test1.sh
are you ok(y/n)? y
glad to see it.
上面的判断还可以替换为:
/> cat > test2.sh
echo -e "are you ok(y/n or maybe)? \c"
read answer
# [[ ]]复合命令操作符允许其中的表达式包含元字符,这里输入以y或y开头的任意单词,或maybe都执行then后面的echo。
if [[ $answer [yy]* || $answer = maybe ]]
then
echo "glad to hear it.
fi
ctrl d
/> . ./test2.sh
are you ok(y/n or maybe)? yes
glad to hear it.
下面的例子将使用shell中的扩展通配模式。
/> shopt -s extglob #打开该扩展模式
/> answer="not really"
/> if [[ $answer = [nn]o?( way |t really) ]]
> then
> echo "i am sorry."
> fi
i am sorry.
对于本示例中的扩展通配符,这里需要给出一个具体的解释。[nn]o匹配no或no,?( way|t really)则表示0个或1个( way或t really),因此answer变量匹配的字符串为no、no、not really、not really、no way、no way。
下面的示例使用了let命令操作符,如:
/> cat > test3.sh
if (( $# != 2 )) #等同于 [ $# -ne 2 ]
then
echo "usage: $0 arg1 arg2" 1>&2
exit 1 #exit退出值为0-255之间,只有0表示成功。
fi
if (( $1 < 0 || $1 > 30 )) #等同于 [ $1 -lt 0 -o $1 -gt 30 ]
then
echo "arg1 is out of range."
exit 2
fi
if (( $2 <= 20 )) #等同于 [ $2 -le 20 ]
then
echo "arg2 is out of range."
fi
ctrl d
/> sh ./test3.sh
usage: ./test3.sh arg1 arg2
/> echo $? #shell脚本的退出值为exit的参数值。
1
/> sh ./test3.sh 40 30
arg1 is out of range.
/> echo $?
2
下面的示例为如何在if的条件表达式中检验空变量:
/> cat > test4.sh
if [ "$name" = "" ] #双引号就表示空字符串。
then
echo "name is null."
fi
ctrl d
/> . ./test4.sh
name is null.
if/elif/else语句的使用方式和if语句极为相似,相信有编程经验的人都不会陌生,这里就不再赘述了,其格式如下:
if command
then
command
elif command
then
command
else
command
fi
见如下示例脚本:
/> cat > test5.sh
echo -e "how old are you? \c"
read age
if [ $age -lt 0 -o $age -gt 120 ] #等同于 (( age < 0 || age > 120 ))
then
echo "you are so old."
elif [ $age -ge 0 -a $age -le 12 ] #等同于 (( age >= 0 && age <= 12 ))
then
echo "you are child."
elif [ $age -ge 13 -a $age -le 19 ] #等同于 (( age >= 13 && age <= 19 ))
then
echo "you are 13--19 years old."
elif [ $age -ge 20 -a $age -le 29 ] #等同于 (( age >= 20 && age <= 29 ))
then
echo "you are 20--29 years old."
elif [ $age -ge 30 -a $age -le 39 ] #等同于 (( age >= 30 && age <= 39 ))
then
echo "you are 30--39 years old."
else
echo "you are above 40."
fi
ctrl d
/> . ./test5.sh
how old are you? 50
you are above 40.
case语句格式如下:
case variable in
value1)
command
;; #相同于c语言中case语句内的break。
value2)
command
;;
) #相同于c语言中switch语句内的default
command
;;
esac
见如下示例脚本:
/> cat > test6.sh
#!/bin/sh
echo -n "choose a color: "
read color
case "$color" in
[bb]l??)
echo "you select blue color."
;;
[gg]ree)
echo "you select green color."
;;
red|orange)
echo "you select red or orange."
;;
)
echo "you select other color."
;;
esac
echo "out of case command."
/> . ./test6.sh
choose a color: green
you select green color.
out of case command.
4. 循环语句:
bash shell中主要提供了三种循环方式:for、while和until。
for循环声明格式:
for variable in word_list
do
command
done
见如下示例脚本:
/> cat > test7.sh
for score in math english physics chemist #for将循环读取in后面的单词列表,类似于java的for-each。
do
echo "score = $score"
done
echo "out of for loop"
ctrl d
/> . ./test7.sh
score = math
score = english
score = physics
score = chemist
out of for loop
/> cat > mylist #构造数据文件
tom
patty
ann
jake
ctrl d
/> cat > test8.sh
#!/bin/sh
for person in $(cat mylist) #for将循环读取cat mylist命令的执行结果。
do
echo "person = $person"
done
echo "out of for loop."
ctrl d
/> . ./test8.sh
person = tom
person = patty
person = ann
person = jake
out of for loop.
/> cat > test9.sh
for file in test[1-8].sh #for将读取test1-test8,后缀为.sh的文件
do
if [ -f $file ] #判断文件在当前目录是否存在。
then
echo "$file exists."
fi
done
ctrl d
/> . ./test9.sh
test2.sh exists.
test3.sh exists.
test4.sh exists.
test5.sh exists.
test6.sh exists.
test7.sh exists.
test8.sh exists.
/> cat > test10.sh
for name in $ #读取脚本的命令行参数数组,还可以写成for name的简化形式。
do
echo "hi, $name"
done
ctrl d
/> . ./test10.sh stephen ann
hi, stephen
hi, ann
while循环声明格式:
while command #如果command命令的执行结果为0,或条件判断为真时,执行循环体内的命令。
do
command
done
见如下示例脚本:
/> cat > test1.sh
num=0
while (( num < 10 )) #等同于 [ $num -lt 10 ]
do
echo -n "$num "
let num =1
done
echo -e "\nhere's out of loop."
ctrl d
/> . ./test1.sh
0 1 2 3 4 5 6 7 8 9
here's out of loop.
/> cat > test2.sh
go=start
echo type q to quit.
while [[ -n $go ]] #等同于[ -n "$go" ],如使用该风格,$go需要被双引号括起。
do
echo -n how are you.
read word
if [[ $word [qq] ]] #等同于[ "$word" = q -o "$word" = q ]
then
echo bye.
go= #将go变量的值置空。
fi
done
ctrl d
/> . ./test2.sh
how are you. hi
how are you. q
bye.
until循环声明格式:
until command #其判断条件和while正好相反,即command返回非0,或条件为假时执行循环体内的命令。
do
command
done
见如下示例脚本:
/> cat > test3.sh
until who | grep stephen #循环体内的命令将被执行,直到stephen登录,即grep命令的返回值为0时才退出循环。
do
sleep 1
echo "stephen still doesn't login."
done
ctrl d
shift命令声明格式:shift [n]
shift命令用来把脚本的位置参数列表向左移动指定的位数(n),如果shift没有参数,则将参数列表向左移动一位。一旦移位发生,被移出列表的参数就被永远删除了。通常在while循环中,shift用来读取列表中的参数变量。
见如下示例脚本:
/> set stephen ann sheryl mark #设置4个参数变量。
/> shift #向左移动参数列表一次,将stephen移出参数列表。
/> echo $
ann sheryl mark
/> shift 2 #继续向左移动两位,将sheryl和ann移出参数列表
/> echo $
mark
/> shift 2 #继续向左移动两位,由于参数列表中只有mark了,因此本次移动失败。
/> echo $
mark
/> cat > test4.sh
while (( $# > 0 )) #等同于 [ $# -gt 0 ]
do
echo $
shift
done
ctrl d
/> . ./test4.sh a b c d e
a b c d e
b c d e
c d e
d e
e
break命令声明格式:break [n]
和c语言不同的是,shell中break命令携带一个参数,即可以指定退出循环的层数。如果没有指定,其行为和c语言一样,即退出最内层循环。如果指定循环的层数,则退出指定层数的循环体。如果有3层嵌套循环,其中最外层的为1,中间的为2,最里面的是3。
见如下示例脚本:
/> cat > test5.sh
while true
do
echo -n "are you ready to move on?"
read answer
if [[ $answer [yy] ]]
then
break
else
echo "come on."
fi
done
echo "here we are."
ctrl d
/> . ./test5.sh
are you ready to move on? y
here we are
continue命令声明格式:continue [n]
和c语言不同的是,shell中continue命令携带一个参数,即可以跳转到指定层级的循环顶部。如果没有指定,其行为和c语言一样,即跳转到最内层循环的顶部。如果指定循环的层数,则跳转到指定层级循环的顶部。如果有3层嵌套循环,其中最外层的为3,中间的为2,最里面的是1。
/> cat maillist #测试数据文件maillist的内容为以下信息。
stephen
ann
sheryl
mark
/> cat > test6.sh
for name in $(cat maillist)
do
if [[ $name stephen ]]; then
continue
else
echo "hello, $name."
fi
done
ctrl d
/> . ./test6.sh
hello, ann.
hello, sheryl.
hello, mark.
i/o重新定向和子shell:
文件中的输入可以通过管道重新定向给一个循环,输出也可以通过管道重新定向给一个文件。shell启动一个子shell来处理i/o重新定向和管道。在循环终止时,循环内部定义的任何变量对于脚本的其他部分来说都是不可见的。
/> cat > demodata #为下面的脚本构造测试数据
abc
def
ghi
crtl d
/> cat > test7.sh
if (( $# < 1 )) #如果脚本参数的数量小于1,则给出错误提示后退出。
then
echo "usage: $0 filename " >&2
exit 1
fi
count=1
cat $1 | while read line #参数一中的文件被cat命令输出后,通过管道逐行输出给while read line。
do
let $((count 1)) && echo "processing file $1..." > /dev/tty #该行的echo将输出到当前终端窗口。
echo -e "$count\t$line" #将输出行号和文件中该行的内容,中间用制表符隔开。
let count =1
done > outfile #将while循环中所有的输出,除了>/dev/tty之外,其它的全部输出到outfile文件。
ctrl d
/> . ./test7.sh demodata #只有一行输出,其余的都输出到outfile中了。
processing file demodata...
/> cat outfile
1 abc
2 def
3 ghi
/> cat > test8.sh
for i in 9 7 2 3 5 4
do
echo $i
done | sort -n #直接将echo的输出通过管道重定向sort命令。
ctrl d
/> . ./test8.sh
2
3
4
5
7
9
5. ifs和循环:
shell的内部域分隔符可以是空格、制表符和换行符。它可以作为命令的分隔符用在例如read、set和for等命令中。如果在列表中使用不同的分隔符,用户可以自己定义这个符号。在修改之前将ifs原始符号的值保存在另外一个变量中,这样在需要的时候还可以还原。
见如下示例脚本:
/> cat > test9.sh
names=stephen:ann:sheryl:john #names变量包含的值用冒号分隔。
oldifs=$ifs #保留原有ifs到oldifs变量,便于后面的还原。
ifs=":"
for friends in $names #这是遍历以冒号分隔的names变量值。
do
echo hi $friends
done
ifs=$oldifs #将ifs还原为原有的值。
set jerry tom angela
for classmates in $ #再以原有ifs的值变量参数列表。
do
echo hello $classmates
done
ctrl d
/> . ./test9.sh
hi stephen
hi ann
hi sheryl
hi john
hello jerry
hello tom
hello angela
6. 函数:
shell中函数的职能以及优势和c语言或其它开发语言基本相同,只是语法格式上的一些差异。下面是shell中使用函数的一些基本规则:
1) 函数在使用前必须定义。
2) 函数在当前环境下运行,它和调用它的脚本共享变量,并通过位置参量传递参数。而该位置参量将仅限于该函数,不会影响到脚本的其它地方。
3) 通过local函数可以在函数内建立本地变量,该变量在出了函数的作用域之后将不在有效。
4) 函数中调用exit,也将退出整个脚本。
5) 函数中的return命令返回函数中最后一个命令的退出状态或给定的参数值,该参数值的范围是0-256之间。如果没有return命令,函数将返回最后一个shell的退出值。
6) 如果函数保存在其它文件中,就必须通过source或dot命令把它们装入当前脚本。
7) 函数可以递归。
8) 将函数从shell中清空需要执行:unset -f function_name。
9) 将函数输出到子shell需要执行:export -f function_name。
10) 可以像捕捉shell命令的返回值一样获取函数的返回值,如$(function_name)。
shell中函数的声明格式如下:
function function_name { command; command; }
见如下示例脚本:
/> cat > test1.sh
function increment() { #定义函数increment。
local sum #定义本地变量sum。
let "sum=$1 1"
return $sum #返回值是sum的值。
}
echo -n "the num is "
increment 5 #increment函数调用。
echo $? #输出increment函数的返回值。
ctrl d
/> . ./test1.sh
the num is 6
7. 陷阱信号(trap):
在shell程序运行的时候,可能收到各种信号,有的来自于操作系统,有的来自于键盘,而该shell在收到信号后就立刻终止运行。但是在有些时候,你可能并不希望在信号到达时,程序就立刻停止运行并退出。而是他能希望忽略这个信号而一直在运行,或者在退出前作一些清除操作。trap命令就允许你控制你的程序在收到信号以后的行为。
其格式如下:
trap 'command; command' signal-number
trap 'command; command' signal-name
trap signal-number
trap signal-name
后面的两种形式主要用于信号复位,即恢复处理该信号的缺省行为。还需要说明的是,如果trap后面的命令是使用单引号括起来的,那么该命令只有在捕获到指定信号时才被执行。如果是双引号,则是在trap设置时就可以执行变量和命令替换了。
下面是系统给出的信号数字和信号名称的对照表:
1)sighup 2)sigint 3)sigquit 4)sigill 5)sigtrap 6)sigabrt 7)sigbus 8)sigfpe
9)sigkill 10) sigusr1 11)sigegv 12)sigusr2 13)sigpipe 14)sigalrm 15)sigterm 17)sigchld
18)sigcont 19)sigstop ... ...
见如下示例脚本:
/> trap 'rm tmp;exit 1' 1 2 15 #该命令表示在收到信号1、2和15时,该脚本将先执行rm tmp*,然后exit 1退出脚本。
/> trap 2 #当收到信号2时,将恢复为以前的动作,即退出。
/> trap " " 1 2 #当收到信号1和2时,将忽略这两个信号。
/> trap - #表示恢复所有信号处理的原始值。
/> trap 'trap 2' 2 #在第一次收到信号2时,执行trap 2,这时将信号2的处理恢复为缺省模式。在收到信号2时,shell程序退出。
/> cat > test2.sh
trap 'echo "control c will not terminate $0."' 2 #捕获信号2,即在键盘上按ctrl c。
trap 'echo "control \ will not terminate $0."' 3 #捕获信号3,即在键盘上按ctrl \。
echo "enter stop to quit shell."
while true #无限循环。
do
echo -n "go go...."
read
if [[ $reply [ss]top ]] #直到输入stop或stop才退出循环和脚本。
then
break
fi
done
ctrl d
/> . ./test2.sh
enter stop to quit shell.
go go....^ccontrol c will not terminate -bash.
^\control \ will not terminate -bash.
stop
8. 用getopts处理命令行选项:
这里的getopts命令和c语言中的getopt几乎是一致的,因为脚本的位置参量在有些时候是失效的,如ls -lrt等。这时候-ltr都会被保存在$1中,而我们实际需要的则是三个展开的选项,即-l、-r和-t。见如下带有getopts的示例脚本:
/> cat > test3.sh
#!/bin/sh
while getopts xy options #x和y是合法的选项,并且将-x读入到变量options中,读入时会将x前面的横线去掉。
do
case $options in
x) echo "you entered -x as an option" ;;
y) echo "you entered -y as an option" ;;
esac
done
/> ./test3.sh -xy
you entered -x as an option
you entered -y as an option
/> ./test3.sh -x
you entered -x as an option
/> ./test3.sh -b #如果输入非法选项,getopts会把错误信息输出到标准错误。
./test3.sh: illegal option -- b
/> ./test3.sh b #该命令不会有执行结果,因为b的前面有没横线,因此是非法选项,将会导致getopts停止处理并退出。
/> cat > test4.sh
#!/bin/sh
while getopts xy options 2>/dev/null #如果再出现选项错误的情况,该重定向会将错误输出到/dev/null。
do
case $options in
x) echo "you entered -x as an option" ;;
y) echo "you entered -y as an option" ;;
\?) echo "only -x and -y are valid options" 1>&2 # ?表示所有错误的选项,即非-x和-y的选项。
esac
done
/> . ./test4.sh -g #遇到错误的选项将直接执行\?)内的代码。
only -x and -y are valid options
/> . ./test4.sh -xg
you entered -x as an option
only -x and -y are valid options
/> cat > test5.sh
#!/bin/sh
while getopts xyz: arguments 2>/dev/null #z选项后面的冒号用于提示getopts,z选项后面必须有一个参数。
do
case $arguments in
x) echo "you entered -x as an option." ;;
y) echo "you entered -y as an option." ;;
z) echo "you entered -z as an option." #z的后面会紧跟一个参数,该参数保存在内置变量optarg中。
echo "\$optarg is $optarg.";
;;
\?) echo "usage opts4 [-xy] [-z argument]"
exit 1 ;;
esac
done
echo "the number of arguments passed was $(( $optind - 1 ))" #optind保存一下将被处理的选项的位置,他是永远比实际命令行参数多1的数。
/> ./test5.sh -xyz foo
you entered -x as an option.
you entered -y as an option.
you entered -z as an option.
$optarg is foo.
the number of arguments passed was 2
/> ./test5.sh -x -y -z boo
you entered -x as an option.
you entered -y as an option.
you entered -z as an option.
$optarg is boo.
the number of arguments passed was 4
9. eval命令与命令行解析:
eval命令可以对命令行求值,做shell替换,并执行命令行,通常在普通命令行解析不能满足要求时使用。
/> set a b c d
/> echo the last argument is \$$#
the last argument is $4
/> eval echo the last argument is \$$# #eval命令先进行了变量替换,之后再执行echo命令。
the last argument is d