结构化命令
有一类命令会基于变量值或其他命令的结果等条件使脚本跳过或循环执行命令。这样的命令通常称为结构化命令。
结构化命令允许改变程序的执行顺序,在某些条件下执行一些命令而在其他条件下跳过另一些命令。
11.1 使用 if - then 语句
结构化命令中,最基本的类型就是if-then语句。if-then语句有如下格式:
if command
then commands
if
bash shell 的 if语句会运行if行 定义的那个命令,如果该命令的退出状态码 是0(该命令成功运行),位于then 部分的命令就会被执行。如果该命令的退出状态码是其他什么值,那then部分的命令就不会被执行。bash shell会继续执行脚本中的下一个命令。
[root@centos1 bash_dir]# cat struc
#!/bin/bash
if date
then
echo "it worked"
fi
[root@centos1 bash_dir]# ./struc
Sat Dec 9 16:21:49 CST 2017
it worked
[root@centos1 bash_dir]#
[root@centos1 bash_dir]# cat struc2
#!/bin/bash
if asdfg
then
echo "it did not work"
fi
echo "we are outside of the if statement"
[root@centos1 bash_dir]# ./struc2
./struc2: line 2: asdfg: command not found
we are outside of the if statement
[root@centos1 bash_dir]#
运行if语句中的那个命令所生成的错误消息依然会显示在脚本的输出中。 如果不想这种情况发生,14章 将会讨论如何避免这种情况。
在 then 部分,可以使用多个命令。bash shell会将这部分命令当成一个块。
[root@centos1 bash_dir]# cat struc3
#!/bin/bash
testuser=abc
if grep $testuser /etc/passwd
then
echo "the bash files for user $testuser are"
ls -a /home/$testuser/.b*
fi
[root@centos1 bash_dir]# ./struc3
[root@centos1 bash_dir]#
使用grep命令来在/etc/passwd文件中查找某个特定用户名是否在当前系统上使用。如果有用户使用了那个登录名,脚本会列出该用户主目录的 bash 文件。 如果你将testuser变量设置为系统上不存在的用户,则什么都不会显示。
11.2 if - then - else 语句
if command
then
commands
else
commands
if
11.3 嵌套 if (elif)
if command1
then
commands
elif command2
then
commands
if
if command1
then
command set 1
elif command2
then
command set 2
elif command3
then
command set 3
elif command4
then
command set4
fi
11.7节将了解如何使用case命令, 而不用嵌套多个 if - then 语句。
11.4 test 命令
目前为止,锁了解到的if语句中的命令都是普通shell命令。if-then 语句是否能测试跟命令的退出状态码无关的条件。
答案是不能。 但是bash shell中有个好用的工具可以帮你通过if-then 语句测试其他条件。
test命令提供了在if-then 语句中测试不同条件的途径。如果test命令中列出的条件成立,test命令就会退出并返回退出状态码0,这样 if -then 语句就与其他编程语言中的if -then 语句以类似方式工作了。如果条件不成立,test命令就会退出并返回退出状态码1 。if - then 语句就会失效。
test命令: test condition
if test condition
then
commands
fi
bash shell 提供了英译中在if-then语句中声明test命令的方法:
if[ condition ]
then
commands
fi
方括号定义了test 命令中用到的条件。注意:你必须在左括号右侧和有括号左侧各加一个空格,否则会报错。
test命令可以判断三类条件:
- 数值比较
- 字符串比较
- 文件比较
后续将会介绍如何在if-then语句汇总使用这三类条件测试。
11.4.1 数值比较
[root@centos1 bash_dir]# cat struc4
#!/bin/bash
var1=10
var2=11
if [ $var1 -gt 5 ]
then
echo "the var1val $var1 is greater than 5"
fi
if [ $var1 -eq $var2 ]
then
echo "equals"
else
echo "values are different"
fi
[root@centos1 bash_dir]# ./struc4
the var1val 10 is greater than 5
values are different
[root@centos1 bash_dir]#
方括号定义了test命令中用到的条件 , 注意:必须在左括号右侧 和 右括号左侧各加一个空格,否则会报错。if 与方括号之间必须有空格,否则会报错。
但是测试数值条件也有个限制,看下面脚本:
[root@centos1 bash_dir]# cat struc5
#!/bin/bash
var1=`bc << EOF
scale=4
10/3
EOF
`
var2=`echo "scale=4;9/3" | bc`
if [ $var1 -gt $var2 ]
then
echo "The result is larger than 3"
fi
[root@centos1 bash_dir]# ./struc5
./struc5: line 8: [: 3.3333: integer expression expected
[root@centos1 bash_dir]#
这个例子使用bash计算器来生成一个浮点数3.3333,然后用test判断,出现问题。
test命令无法处理val1变量中存储的浮点值。
记住:bash shell能处理的数仅有整数。当你使用bash计算器时,你可以让shell将浮点值作为字符串值存储进一个变量。如果只是要通过echo语句来显示这个结果,那它可以很好地工作;但它无法再基于数字的函数总工作。 不能在test命令中使用浮点数
11.4.2 字符串比较
1.字符串相等性
[root@centos1 bash_dir]# cat test7
#!/bin/bash
testuser=root
if [ $USER != $testuser ]
then
echo "This is not $testuser"
else
echo "This is $testuser"
fi
[root@centos1 bash_dir]# sh test7
This is root
[root@centos1 bash_dir]#
比较字符串的相等性时,test的比较会将所有的标点和大写也考虑在内。
2.字符串顺序
注意:
- 大于小于符号必须转义,否则shell会把它们当做重定向符号而把字符串值当做文件名;
- 大于小于顺序和sort命令采用的不同
[root@centos1 section10]# cat gt
#!/bin/bash
var1=baseball
var2=hockey
if [ $var1 > $var2 ]
then
echo "$var1 is greater than $var2"
else
echo "$var1 is lower than $var2"
fi
[root@centos1 section10]# sh gt
baseball is greater than hockey
[root@centos1 section10]# ls
gt hockey
[root@centos1 section10]#
这个脚本中只用了大于号,没有错误出现,但是结果是错的。脚本把大于号解释成输出重定向。因此,它重建了一个名为hockey的文件,重定向完毕后,test命令返回了退出状态码0,而if语句则以为命令成功结束了。
解决这个问题,需要正确的转移大于号
[root@centos1 section10]# cat gt
#!/bin/bash
var1=baseball
var2=hockey
if [ $var1 \> $var2 ]
then
echo "$var1 is greater than $var2"
else
echo "$var1 is lower than $var2"
fi
[root@centos1 section10]# sh gt
baseball is lower than hockey
[root@centos1 section10]# ls
gt
[root@centos1 section10]#
现在已经得到正确的结果了。
sort命令处理大写字母的方法刚好跟test命令相反:
[root@centos1 section10]# cat Up
#!/bin/bash
var1=Testing
var2=testing
if [ $var1 \> $var2 ]
then
echo "$var1 is greater than $var2"
else
echo "$var1 is lower than $var2"
fi
[root@centos1 section10]# sh Up
Testing is lower than testing
[root@centos1 section10]#
[root@centos1 section10]# cat Upb
Testing
testing
[root@centos1 section10]# sort Upb
testing
Testing
[root@centos1 section10]#
在test命令中大写字母会被当成小于小写字母的,但当你将同样的字符串放进文件中并用sort命令排序的时候,小写字母会先出现。
3.字符串大小
-n 和 -z 参数用来检查一个变量是否含有数据:
- -n str1 检查str1的长度是否是非0
- -z 检查str1的长度是否为0
[root@centos1 section10]# cat empty
#!/bin/bash
val1=testing
val2=''
if [ -n $val1 ]
then
echo "The string '$val1' is not empty"
else
echo "The string '$var1' is empty"
fi
if [ -z $val2 ]
then
echo "The string '$val2' is empty"
else
echo "The string '$var2' is not empty"
fi
if [ -z $val3 ]
then
echo "The string '$val3' is empty"
else
echo "The string '$var3' is not empty"
fi
[root@centos1 section10]# sh empty
The string 'testing' is not empty
The string '' is empty
The string '' is empty
[root@centos1 section10]#
这个例子创建了两个字符串变量。val1包含字符串,val2变量位空字符串
if [ -n $val1 ] 判断val1变量是否长度非0 ; 而它的长度正好非零,所以then 部分被执行了。
if [ -z $val2 ] 判断长度是否为0 , 而它正好长度为零, 所以then部分被执行了。
空的和未初始化的变量对shell脚本测试来说可能有灾难性的影响。如果你不是很确定一个变量的内容,最好在数值或字符串比较中, 使用它之前先通过-n或-z来测试下变量是否含有值。
11.4.3 文件比较
test命令允许你测试Linux文件系统上文件核目录的状态。
1.检查目录 -d
-d测试会检查指定的文件名是否在系统上以目录形式存在:
[root@centos1 section10]# cat testdir
#!/bin/bash
if [ -d $HOME ]
then
echo "Your HOME directory exists"
cd $HOME
ls -a
else
echo "There is a problem with your HOME directory"
fi
[root@centos1 section10]# sh testdir
Your HOME directory exists
. bash_dir .cache Downloads .gnote .hivehistory .local Public Videos
.. .bash_history .config .esd_auth .gnupg .ICEauthority .mozilla .pulse .viminfo
.abrt .bash_logout .cshrc .gconf .gtk-bookmarks .imsettings.log Music .pulse-cookie .Xauthority
anaconda-ks.cfg .bash_profile .dbus .gconfd .gvfs install.log .nautilus .ssh .xsession-errors
apache-tomcat-7.0.82 .bashrc Desktop .gnome2 hadoop-2.7.1 install.log.syslog .oracle_jre_usage .tcshrc
apache-tomcat-7.0.82.tar.gz .beeline Documents .gnome2_private hadoop-2.7.1.tar.gz .lesshst Pictures Templates
[root@centos1 section10]#
2 检查对象是否存在 -e
-e 比较允许你再脚本中使用对象前检查文件或目录对象是否存在:
[root@centos1 section10]# cat testfile
#!/bin/bash
if [ -e $HOME ]
then
echo "OK on the directory . now to check the file"
if [ -e $HOME/testing ]
then
echo "Appending date to existing file"
date >> $HOME/testing
else
echo "Creating new file"
date > $HOME/testing
fi
else
echo "Sorry . you do not have a HOME directory"
fi
[root@centos1 section10]# cat ~/testing
Mon Dec 11 09:46:17 CST 2017
[root@centos1 section10]# sh testfile
OK on the directory . now to check the file
Appending date to existing file
[root@centos1 section10]# cat ~/testing
Mon Dec 11 09:46:17 CST 2017
Mon Dec 11 09:47:25 CST 2017
[root@centos1 section10]#
3. 检查文件是否存在 -f
[root@centos1 section10]# cat file
#!/bin/bash
if [ -f $HOME ]
then
echo "$HOME is a file"
else
echo "$HOME is not a file"
fi
[root@centos1 section10]# sh file
/root is not a file
[root@centos1 section10]#
4.检查是否可读 -r
在尝试从文件中读取数据前,最好先测试下是否能读取文件
5. 检查空文件 -s
应该用-s 来检查文件是否为空,尤其是在删除文件时。
6. 检查是否可写 -w
-w 会检查文件是否有可写权限:
7. 检查是否可执行 -x
-x 检查文件是否有可执行权限
8. 检查所属关系 -0
-0 测试你是否是文件的属主
9. 检查默认数组关系 -G
-G 会检查文件的默认组
11.5 符合条件测试
if-then 语句允许你使用布尔逻辑来组合测试。有两种布尔运算符可用:
- [ condition1 ] && [ condition2 ]
- [ condition1 ] || [ condition2 ]
- 第一个布尔运算符使用AND 布尔运算符来组合两个条件,让then 部分的命令执行,连个条件必须满足。
- 第二个布尔运算使用OR布尔运算符来组合两个条件,如果任何一个条件最后都能得到一个真值,then部分命令就会执行。
[root@centos1 section10]# cat double
#!/bin/bash
if [ -d $HOME ] && [ -w $HOME/testing ]
then
echo "The file exists and you can write to it"
else
echo "I cannot write to the file"
fi
[root@centos1 section10]# sh double
The file exists and you can write to it
[root@centos1 section10]#
11.6 if-then 高级特性
bash shell 有两项交新的扩展,提供了可在if-then语句中使用的高级特性:
- 用于数学表达式的双尖括号;
- 用于高级字符串处理功能的双方括号;
11.6.1 使用双尖括号
双尖括号命令允许你将高级数学表达式放入比较中,test命令只允许在比较中进行简单的算术操作。双尖括号命令的格式如下:
(( expression ))
术语expression可以是任意的数学复制或比较表达式。除了test命令使用的标准数学运算符。下面列出了双尖括号命令中会用到的其他运算符:
你可以在if语句中用双尖括号命令,也可以在脚本中的普通命令里使用来赋值。
[root@centos1 section10]# cat test23
#!/bin/bash
var1=10
if (( $var1 ** 2 > 90 ))
then
(( var2=var1 ** 2 ))
echo "The square of $var1 is $var2"
fi
[root@centos1 section10]# sh test23
The square of 10 is 100
[root@centos1 section10]#
注意,你不需要将双尖括号中表达式的大于号转义,这是双尖括号命令提供的另一个高级特性。
11.6.2 使用双方括号
使用双方括号命令提供了针对字符串比较的高级特性。双方括号命令的格式如下:
[[ expression ]]
双方括号里的expression使用了test命令中采用的标准字符串进行比较。但它提供了test命令未提供的另一个特性——模式匹配(pattern matching)
在模式匹配中你可以定义一个正则表达式(19章中详细讨论) 来匹配字符串值:
[root@centos2 bash_shell]# cat test1
#!/bin/bash
if [[ $USER == r* ]]
then
echo "Hello $USER"
else
echo "Sorry . I do not known you "
fi
[root@centos2 bash_shell]# sh test1
Hello root
[root@centos2 bash_shell]#
11.7 case 命令
你可以使用case命令 ,而不用写那么多elif语句来不断检查相同变量值。case命令会检查单个变量列表格式的多个值:
case variable in
pattern|pattern2) commands1;;
pattern3) commands2;;
*) default commmands;;
esac
[root@centos2 ~]# cat case
#!/bin/bash
case $USER in
root|barbara )
echo "Welcome . $USER"
echo "Please enjoy your visit";;
testing )
echo "Special testing account";;
jessica )
echo "Do not forget to log off";;
* )
echo "Sorry. you are not allowed here";;
esac
[root@centos2 ~]# sh case
Welcome . root
Please enjoy your visit
[root@centos2 ~]#
case 命令提供了一个更清晰的方法来为变量每个可能的值指定不同的选项。
11.8 小结