Bash shell 脚本编程全攻略(上篇)
什么是shell脚本呢?
当命令不在命令行上执行,而是通过一个文件执行时,该文件就称为shell脚本,脚本以非交互的方式运行。Shell脚本把命令通过一些语法组织起来,便能实现特定的功能。
Shell脚本主要运用在系统运维中,主要功能有:
自动化常用命令;
执行系统管理和故障排除;
创建简单的应用程序;
处理文本或文件。
Shell脚本创建与执行
Shell脚本通常在编辑器中编写,由命令及其注释组成,注释是跟在井号(#)后面的内容,用来对脚本进行注释。
脚本左上角的第一行会指出由哪个程序来执行脚本中的行。这一行通常称为shbang行,必须为顶端第一行,如由bash来执行则为:
#!/bin/bash
在执行shell脚本时,须先给予执行权力,然后在命令行上指定脚本的路径运行;或是直接运行解释器,将脚本作为解释器的参数运行。
了解shell脚本编程,我们先要了解其一些基本的组成或涉及的基本内容。由于涉及内容较多,一些部分可能不会详细讲解。
变量
学习Shell脚本离不开变量,根据生效范围变量主要分为;
局部变量:只对当前shell进程有效;对当前shell之外的其它shell进程包括其子shell均无效。在函数中用local声明的局部变量只能用在函数中。
环境变量:当前shell进程及其子进程有效。
变量赋值
局部变量 VAR_NAME=value 或declare VAR_NAME=value
环境变量 export name=value 或declare -x name=value
显示所有环境变量可以用命令:export或env
只读变量 readonly name=value 或 delcare -r name=value
声明后不能修改和删除。
删除变量 unset name
显示已定义的所有变量 set或declare
变量引用 $name;${name}
shell 会对双引号内的美元符号后的变量执行变量扩展,对于单引号内的则不会。
命令替换
Linux命令的输出可以被赋给一个变量,或者通过使用反引号引用命令,在一个字符串中使用该命令的输出。Bash还提供了另一中新语法:无需反引号,只将命令包含在由美元符号开始的一对圆括号中即可。即:
variable_name=`COMMAND`
variable_name=$(COMMAND)
范例
数组
Bash中可以创建一维数组。数组允许将一列词放到一个变量名中,例如一列数、一列名称或一列文件。
数组声明(赋值)
数组可以用内置命令decare -a来创建,或者直接给变量名一个下标来创建,如arry_name[0]=5。索引值是从0开始的整数。
如果declare命令带-a和-r选项,将创建一个只读数组。
也可以一次性给全部或部分元素赋值,如:
arry_name=("apple pie" banana cat )此时索引从0开始,按序关联。
arry_name=([0]="apple pie" [2]=cat )
declare,readonly,和local内置命令也可以带-a选项来声明一个数组,-a选项的read命令用来从标准输入读入一组词到数组元素中。
关联数组
数组没有上限,索引不必连续(稀疏格式)。还可以使用自定义的模式,如declare -A arry_name=([a]=apple [b]=banana),称为关联数组,此时必须用decare -A来声明赋值才能用。
数组引用
要取出数组中的一个元素:${arry_name[index]}。
引用数组中的所有元素:${arry_name[*]}或${arry_name[@]}
数组切片 ${arry_name[@]:offset:num}。
eg. ${arry1[@]:1:3} 跳过第一个,取之后的3个元素。
数组长度(元素个数)
${#arry_name[*]}或${#arry_name[@]}
数组删除
删除整个数组:unset arry_name
删除单个元素:unset arry_name[index]
Shell读取用户输入之read
read是一个内置命令,用于从终端或文件读取输入。read命令读取一个输入行,直至遇到换行符。如果read后未跟变量,读入的行将会赋给内置变量REPLY。也可以用read命令来中断程序的运行,直至用户输入一个回车键。read常用方法与选项为:
read answer |
从终端读入一行赋值给变量answer |
read first…last |
把用户键入的词依次赋给赋给变量fist…,剩余的部分全部保存到变量last中 |
read -p promt |
打印提示符,并等待用户输入 |
-a arryname |
读入一组词,以此赋值给数组arryname |
-t timeout |
等待用户输入时间,如果在timeout(sec)时间没有输入一个完整的行则退出并返回一个失败状态值。 |
范例
位置参量与命令行参数
用户可以通过命令行向脚本传递参数,我们用位置参量(或称为位置变量)来引用。例如:$1代表第一个位置参量。位置参量有:
$0 |
脚本名(脚本路径) |
$1…${10} |
单独的位置参量 |
$# |
位置参量个数 |
$* |
所有参数,全部参数合为一个字符串 |
$@ |
所有参数,全部参数分为单独字符串;$*与$@在加双引号时才有区别 |
带参数的set命令将重置位置变量,且原来的参量列表就丢失了。要清除所有位置参量,可使用set –,$0始终代表脚本名。
范例:脚本如下
#!/bin/bash
echo $1
echo $2
echo $3
echo ====
for i in $*;do
echo $i
done
echo ====
for i in $@;do
echo $i
done
echo ====================
for i in "$*";do
echo $i
done
echo ====
for i in "$@";do
echo $i
done
set 1 2 3
echo $1
echo $2
echo $3
set —
echo \$1 is $1
执行结果
说明:输入3个脚本参数"blue sky”green white给脚本test。
算术运算
Shell脚本少不了算术运算。
可以用declare -i定义一个整型变量,如果把一个整型变量赋值给一个字符串,则bash把变量值变为0。
整型变量可以进行直接进行算术运算;内置let命令也允许进行算术操作,且带有丰富的类C操作符,双括号(( ))是let的另一种可选形式。
范例:
说明:整型变量直接运算时,中间不能有空格,或者用双引号括起来。
let命令是shell的内置命令,可以用来进行整数运算和数值表达式测试。
范例:
说明:let执行算术运算时,不需要用美元符号$展开变量;如果参数包含空格则需要加引号;双括(( ))可以代替let变量,此时操作符之间可以有空格。let操作符有:
+ – * / |
加(正号)、 减(负号)、 乘、 除 |
% |
余数(取模) |
! |
逻辑非 |
~ |
按位取反 |
<< >> |
按位左移 、 按位右移 |
< > <= >= |
关系运算符,小于、 大于、 小于等于、 大于等于 |
== != |
相等 、不相等 |
& | ^ |
按位与 、按位或、按位异或 |
&& || |
逻辑与 、逻辑或 |
= *= /= %= += -= <<= >>= &= ^= |= |
赋值、快捷赋值运算符 |
算术扩展
Shell通过对一个算术表达式求值并替换结果来进行算术扩展。表达式可以像在双引号中一样来处理,并且可以嵌套。求值算术表达式有以下两种格式:
$[ expression ]
$(( expression ))
范例:
因也可以把$[ ]当作算术运算的另一种方法。
当执行变量、命令、算术表达式和路径名的扩展时,shell被设计按照指定的顺序来扫描命令行。假设变量没有被引用,处理就将按下面的顺序进行。
1、花括号扩展2、代字符号扩展3、参量扩展4、变量替换5、命令替换6、算术扩展7、词分离8、路径名扩展
expr命令
算术运算还可以采用一种方法:expr命令。expr命令用来求表达式的值,其中便包括部分算术运算。
范例
说明:用expr时各符号间需要空格隔开,乘号需要加反斜杆转义。
浮点运算
Bash只支持整型运算,但也可以一些其他工具如bc,awk来处理更复杂的运算。
范例
说明:用echo将表达式通过管道传递给bc,scale变量等于3表示小数点后面为3位小数。
条件测试
Bash可以测试两种类型的条件:命令成功或失败,表达式为真或为假。在任何一种类型的测试中都要使用退出状态。退出状态为0表示命令执行成功或表达式为真,非0表示命令执行失败或表达式为假。状态变量“?”中保存的是退出状态值。
条件测试之test与let
单方括号的test命令 通常内置的test命令来测试表达式的值,test命令也可以链接到方括号上。这样,既可以使用单独的test命令,也可以通过把表达式用单括号括起来,来测试表达式的值。在用test命令或单方括号来测试表达式时,表达式中的shell元字符不会被扩展。由于要对变量进行单词分离,因此包含空白字符的字符串必须用引号括起来。
双方括号的test命令 用双方括号[[ ]](内置的test复合命令)来测试表达式的值时,对变量不进行单词分离,但可以通过元字符扩展进行模式匹配。包含空白字符的字符串必须用引号括起来。此时逻辑操作符&&(与)和||(或)代替了与test命令一起使用的-a和-o选项。
test命令操作符(除&&和||外,其他操作符两边及中括号两边都须有空格):
操作符 |
测试内容 |
字符串测试 |
|
[ string1 = string2 ] |
string1等于string2 |
[ string1 == string2 ] |
string1等于string2(可用=代替) |
[ string1 != string2 ] |
string1不等于string2 |
[ string ] |
string不为空 |
[ -z string ] |
string的长度为0 |
[ -n string ] |
string的长度不为0 |
逻辑测试 |
|
[ string1 -a string2 ] |
string1和string2都为真 |
[ string1 -o string2 ] |
string1或string2至少有一个为真 |
[ ! string ] |
字符串不匹配 |
逻辑测试(复合命令) |
|
[[ pattern1 && pattern2 ]] |
pattern1和pattern2都为真 |
[[ pattern1 || pattern2 ]] |
pattern1和pattern2至少有一个为真 |
[[!pattern ]] |
模式不匹配 |
[[ pattern1 =~ pattern2 ]] |
pattern1是否与pattern2匹配(正则表达式匹配) |
整数测试 |
|
[ int1 -eq int2 ] |
Int1等于int2 |
[ int1 -ne int2 ] |
Int1不等于int2 |
[ int1 -gt int2 ] |
Int1大于int2 |
[ int1 -ge int2 ] |
Int1大于或等于int2 |
[ int1 -lt int2 ] |
Int1小于int2 |
[ int1 -le int2 ] |
Int1小于或等于int2 |
文件测试 |
|
[ file1 -nt file2 ] |
文件file1比file2新则为真 |
[ file1 -ot file2 ] |
文件file1比file2老则为真 |
[ file1 -ef file2 ] |
文件file1和file2有相同的设备数或i节点数则为真 |
[ -e file ] |
文件是否存在(同-a);此外还有许多文件测试,如 -r/-w/x;-g/-u/-k;-t/-N/-O/-G; -b/-c/-d/-f/-h(-L)/-p/-s/-S(socket) |
范例
说明:单方括号是test的另一种表示方法,test命令不允许使用通配符,所以单方括号中[LI]不会被展开,linux与[Ll]inux不相等,返回状态为1;当使用复合test命令时可以使用通配符,返回值为真。
说明:双方括号的复合test,此时支持通配符,不进行变量分离;等号加波浪符(=~),则为正则表达式模式匹配。逻辑操作符用&&代替-a。
let命令和带双圆括号的算术表达式测试
虽然test命令可以测试算术表达式,但由于let命令丰富的类C操作符,我们更愿意使用let命令,此时将表达式放在双圆括号来表示不同含义。
范例
说明:双圆括号代替let命令来测试算术表达式里的值,此时括号里的变量不需要使用美元符号$展开。
条件语句
条件语句之if
条件语句能够根据某个特定的条件是否满足,来选择执行相应的任务。if是最简单的决策形式。if/else语句提供双分支,而if/elif/else语句则提供多分支。
if语句后加一条后面跟一条或一组命令引用或测试表达式,如果该命令或测试表达式的退出状态为0,则执行then到fi之间的语句;如果退出状态为非0,则shell直接跳到fi后面执行命令。双路决策则为退出状态为0则执行then到else之间的语句;否则执行else到fi之间的语句。同理多分支则再次提供一次或多次测试。
if语句格式
详细为:
单分支
if condition;then
COMMANS(命令组)
fi
双分支
if condition;then
COMMANS(命令组)
else
COMMANS(命令组)
fi
多分支
if condition;then
COMMANS(命令组)
elif condition2;then
COMMANS(命令组)
…
else
COMMANS(命令组)
fi
条件conditon可为命令、test测试表达式或则双圆括号的算术表达式(( ))。
关键字之间的命令采用缩进格式,是一种惯例,以使程序便于阅读和调试。
if语句可以嵌套,如果嵌套使用if语句,fi总是与最近的if语句配套。同理最好对嵌套if语句进行缩进。
范例:
说明:反向单引号引用grep命令在/etc/passwd文件中查找$name,由于不需要看grep命令的输出结果,所以用标准错误输出和标准输出都重定向到linux的位容器/dev/null中。
grep命令若找到了$name则退出状态返回0,就执行then后面的语句,否则执行else后面的语句。exit 1显示退出状态1,说明查找失败。
exit命令和变量"?"
exit命令用于终止脚本并返回命令行。传递给exit的参数是一个0~255之间的整数。如果程序返回退出状态0,则表示程序成功退出。否则表示遇到了某种失败。传递给exit的参数保存在变量"?"中。脚本程序默认的退出状态是程序最后一条命令的执行状态。
null命令
冒号(:)代表的null命令是shell的一个内置命令,它不做任何工作,只返回退出状态0。null命令有时被放在if后作为一个占位符,这时if命令没有什么事可做,却需要有一条命令放在then后面,否则程序会产生报错信息。null命令常常被用作循环命令的参数,作用是让循环无限执行下去。
条件语句之case
case命令是一个多路分支命令,可用来代替if/elif命令。case变量的值与value1,value2等的值逐一比较,知道找到与之匹配的值。如果某个值与case变量匹配,程序就执行该值后面的命令,直到遇到双分号,然后跳到esac后面继续往下执行。
如果没有找到与case便来那个匹配的值,程序就执行默认值"*)"后面的命令,然后跳出。case的表达式里可以用shell通配符,还可以用竖杆将两个值相或。
格式
详细
case 变量 in
value1)
COMMANDS
;;
value2)
COMMANDS
;;
…
*)
COMMANDS
;;
esac
范例
说明:提示用户输入一个选择存入变量choice中,如果choice的值与下面的选项某项匹配则执行相应的命令。
用here文档和case语句生成菜单
here文档经常与case命令结合起来使用。我们可以用here文档生成一个选项菜单显示在屏幕上,要求用户选择一个菜单项,然后用case命令对照选项测试用户的输入以执行相应的命令。
说明:cat生成here文档,read读入用户的输入保存在choice变量中,其值与那个选项匹配便执行相应的命令。
原创文章,作者:beyond,如若转载,请注明出处:http://www.178linux.com/40785