1. if/else 语句
语法:
if condition; then
commands;
elif condition; then
commands;
else
commands;
fi
示例:
需求:脚本需要1个参数才能正确运行,而在脚本执行时,如果指定的参数个数不等于1,则shell脚本就应该打印出一个错误信息,告知用户指定的参数个数不对,然后结束脚本的执行。
#!/bin/bash
if [ $# -ne 1 ];then
echo "Error! Arguments are not correct."
echo "Usage:$0 parameter"
exit 1
fi
echo "My shell script beging running."
注意:
1)如果then 和if[ $# -ne 1 ]写在同一行,then的前面需要加分号
2)elif不能写成else if.
3)如果使用elif语句,elif后面也要有then
4)不要忘记if语句的结束标志fi
5)内建测试命令[ ]方括号的两侧都要有空格。
2. case语句
case语句的语法格式为:
case word in
pattern1 )
command-list1
;;
pattern2 )
command-list2
;;
pattern3 | pattern4 )
command-list3
;;
...
esac
需求:
虽然使用前面介绍的if/elif/else语句可以实现多分支结构,但是当分支较多时,嵌套多个elif语句还是比较麻烦的
解决方案:除了if/else语句以外,还可以使用case语句实现多分支结构
#!/bin/bash
#首先清除屏幕
clear
#显示带单项,从而使用户可以选择期望的操作
echo " File Operration List"
echo " ----- --------- ----"
echo "Choose one of the following operations:"
echo
echo "[O]pen File"
echo "[D]elete File"
echo "[R]ename File"
echo "[M]ove File"
echo "[C]opy File"
echo "[P]aste File"
echo
#等待用户的输入
echo -n "Please enter your operation:"
read operation
#根据用户输入的操作执行相应的操作
case "$operation" in
#同时接受大、小写字母
"O"|"o")
#用户选择了打开文件的操作
echo "Opening File..."
;;
"D"|"d")
#用户选择了删除文件的操作
echo "Deleteing File..."
;;
"R"|"r")
#用户选择了重命名文件的操作
echo "Rename File1 to File2"
;;
"M"|"m")
#用户选择了移动文件的操作
echo "Moving File1 to File2"
;;
"C"|"c")
#用户选择了复制文件的操作
echo "Copying File..."
;;
"P"|"p")
#用户选择了粘贴文件的操作
echo "Paste File..."
;;
*)
#用户的输入不匹配上面的任何一种操作
#因此是未知操作,以错误状态1退出脚本
echo "Error,Unknown operation."
echo "Progam exit!"
exit 1
;;
esac
exit 0
3. while循环
while 循环的标准语法格式为:
while test-commands;do consequent-commmands;done
只要while循环测试的条件为真,他就会不停的执行循环体中的代码。
需求:
假设想测试一下192.168.1.30到192.168.1.129网段内的所有机器的网络是否可以通信,这时在脚本中需要重复执行某一部分的代码,或者在满足某些条件的情况下一直不停的执行循环体中的代码,直到条件不满足为止。
可以用while循环的条件测试功能来测试条件是否满足。
#!/bin/bash
#定义要测试的网络
NETWORK=192.168.1
#定义检测的起始地址
IP=30
#只要IP地址小于130,循环体就会被执行
while [ "$IP" -lt 130 ]
do
echo -en "Pinging ${NETWORK}.${IP}..."
#变量NETWORK和IP组合在一起构成IP地址
#执行命令ping检测IP地址的连通性,并且不显示任何输出
ping -c 2 ${NETWORK}.${IP} >/dev/null 2>&1
#根据命令ping的退出状态值是否等于0,判断网络是否连通
if [ $? -eq 0 ];then
#如果ping的退出状态等于0,打印OK
echo "OK"
else
echo "Failed"
fi
#IP 地址加1
let IP=$IP+1
done
exit 0
# sh 004.sh
Pinging 192.168.1.30...Failed
Pinging 192.168.1.31...OK
Pinging 192.168.1.32...Failed
Pinging 192.168.1.33...Failed
Pinging 192.168.1.34...OK
Pinging 192.168.1.35...Failed
Pinging 192.168.1.36...Failed
Pinging 192.168.1.37...OK
Pinging 192.168.1.38...Failed
Pinging 192.168.1.39...Failed
Pinging 192.168.1.40...Failed
Pinging 192.168.1.41...OK
重要提示
1)do 标明了循环体的开始;如果do和while[ ]写在同一行,则do前面应该有分号。
2)不要丢失循环体结束标志done。
3)while后面的test-commands还可以是由多个命令构成的一个list,由分号分开每一个命令,决定是否退出循环体的是最后一个命令的返回值。
4. until循环
需求:循环是在测试条件不满足的情况下执行循环体,而当测试条件满足以后就停止并结束循环。
解决方案
当然只要对测试条件进行修改,我们就还是可以使用while循环来实现这样的需求。不过bash为我们提供了更合适这种情况的语句:until语句
#!/bin/bash
QUIT_COMMAND=quit
#直到用户输入的字符串是quit时,until循环才会退出
until [ "$USER_INPUT" = "$QUIT_COMMAND" ] #=左右侧须空格
do
echo "Please input command:"
echo "(type command $QUIT_COMMAND to exit)"
read USER_INPUT
echo
echo ">>your command is $USER_INPUT"
#对用户的输入进行匹配
case $USER_INPUT in
"open")
echo ">>opening..."
;;
"quit")
echo ">>bye."
;;
*)
echo ">>Unknown command."
;;
esac
done
exit 0
在bash手册中,until的语法和while语句的语法一样,都是在循环的前面先测试一个条件,以此来决定是否要执行后面的循环体
语法:
until test-commands;do consequent-commands;done
如果把do和条件测试分别放到不同行,则可以省略分号:
语法
until test-commands
do
consequent-commands
done
上面的判断IP是否存活的例子,可以这样写
#!/bin/bash
NETWORK=192.168.1
IP=30
#可以写成until [ ! "$IP" -lt 130 ]
until [ "$IP" -ge "130" ]
do
echo -en "Testing machine at IP address:${NETWORK}.${IP}..."
ping -c 2 ${NETWORK}.${IP} >/dev/null 2>&1
if [ "$?" -eq 0 ];then
echo "OK"
else
echo "Failed"
fi
let IP=$IP+1
done
exit 0
重要提示
shell内建的测试命令[ 和]的两侧需要空格
bash中的until条件测试应该写在循环体的上部,和某些语言中的until不同
5. for循环
需求:
如果我们要对某个目录下大量的文件进行相同的操作,重复执行同样的命令行几十次甚至上百次是不切实际的,因此也应该用一个自动化脚本来执行这些相同的操作。
假设当前目录下有很多扩展名为.sh的脚本文件,我们需要把这些.sh文件都复制到主目录下的test子目录中,并且在复制的同时修改文件名,即在每一个文件名的前面都添加前缀test_,如何实现:
解决方案
直接使用cp *.sh的方式可以复制所有的.sh文件,但是不能为文件名添加前缀,因此只有在复制一个文件时,cp命令才能指定目标文件的名字。在这里,如果我们能够遍历.sh文件列表中的文件,就可以单独对每一个文件执行复制操作,并在目标文件的文件名添加前缀test_,因此我们所需要的就是一种遍历.sh文件列表的能力。前面介绍了while和until循环都是根据某一个条件是否满足来决定循环是否继续执行的,因此在本例中并不合适。对于遍历文件列表这种需求,使用for循环是最好的选择:
#!/bin/bash
#在主目录下创建测试目录
mkdir ${HOME}/test >/dev/null 2>&1
#如果创建目录不成功
if [ $? -ne 0 ];then
echo "Directory ${HOME}/test has already existed."
echo "Do Nothing,Abort!"
exit 1
else
echo "Create directory ${HOME}/test"
fi
echo "cp *.sh files into directory ${HOME}/test,and prefix \"test_\" befor each filename."
#遍历当前目录下的所有*.sh文件
for FILE in `ls *.sh`
do
#把文件复制到刚创建目录下,并重命名为test_*
cp $FILE ${HOME}/test/test_$FILE
#为每一个文件添加可执行权限
chmod +x ${HOME}/test/test_$FILE
done
exit 0
语法:
for 循环会让你对一个列表中的每一项都重复的执行相同的命令
for name in word1 word2 ...wordN
do
commands
done
name是一个变量的名字,扩展in后面的words为一个空格分隔的列表,也可以直接写出这个空格分隔的列表word1到wordN,其中
每一项都是一个字符串。每一次循环中,变量name都会取列表中的一个字符串的值,第一次进入for循环时,变量name被设置为word1的值,第二次,变量被设置为word2,以此类推,直到最后一次name等于wordN,然后结束循环。
6. select循环
需求:
如果想快速创建一个有若干个选项的菜单,并且每一个选项前面都安装顺序标好号,用户只要选择对应的数字标号即可,如何快速实现这一的菜单?例如写一个程序以菜单的形式列出一些系统目录,然后用户选择显示哪个系统目录下的文件。
解决方案
bash为我们提供理论一个快速创建菜单的工具,就是select循环
#!/bin/bash
echo
echo "Which directory do you want to list:"
echo "(Press \"Enter\" directly to show menu again)"
echo
#把要显示的菜单项写在in后面的列表里
select dir in /bin/ /usr/bin/ /usr/local/bin/ /sbin/ /usr/sbbin/ /usr/local/sbin/ /usr/share/ quit
do
if [ ! -z "$dir" ];then
if [ "$dir" = "quit" ];then
#如果用户选择quit,则退出脚本
echo "You entered quit,Byebye!"
break;
else
#用户选择了一个目录
echo "You selected directory \"$dir\",it contains following files:"
#显示所选目录中的内容
ls -l $dir
fi
else
echo "Error,invalid,chose agein!"
fi
echo
echo "Which directory do you want to list:"
echo "(Press \"Enter\" directly to show menu again)"
done
运行结果如下,选择对应的选项执行对应的行为
# sh 008.sh
Which directory do you want to list:
(Press "Enter" ddirectly to show menu again)
1) /bin/ 4) /sbin/ 7) /usr/share/
2) /usr/bin/ 5) /usr/sbbin/ 8) quit
3) /usr/local/bin/ 6) /usr/local/sbin/
#?
You entered quit,Byebye!
使用select结构可以快速生成选择菜单,他的语法结合for语句很类似
select name [in words ...];do commands;done
除了关键字由for替换成select以外,其他的几乎没变。
select为我们做了很多事情,其执行过程如下:
(1)首先把in后面列表中的每一项都显示出来,并且在每一项前面添加一个数字的标号
(2)然后显示一个提示符,等待用户的输入,提示符通常是#?,但是可以修改,下面会写到
(3)用户输入数字标号来选择相应的选项,无论用户输入什么,都会保存到变量$REPLY中
(4)如果用户输入的数字标号可以和一开始显示出来的菜单项的数字标号对应上,则变量name的值被设置为数字标号后面的选项,也就是用空格分隔的列表中的某一个字符串,如果不能匹配,则不设置变量name的值
(5)无论用户的输入与菜单项的数字标号是否能够匹配,都会执行do和done之间的循环体,在这里需要通过变量name的值来判断是否成功匹配,从而执行相应的操作;对于用户什么都没有输入,之间按Enter键的情况,不会执行do/done之间的循环体,而是与刚开始执行select语句一样,再次输出所有的菜单项和提示符,等待用户输入
(6)循环体执行完成以后,再次输入提示符等待用户输入,这也是select循环和其他循环最不同的地方,它没有专门用于判断是否应该退出循环的测试条件,因此需要我们在do/done之间的循环体内使用循环控制语句break来退出循环,或者直接使用exit退出shell脚本的执行。
通常可以把select语句和case语句一起使用,如上面的例子可以通过case语句修改为更简单的形式
#!/bin/bash
echo
echo "Which directory do you want to list:"
echo "(Press \"Enter\" directly to show menu again)"
#把要显示的菜单项写在in后面的列表里
select dir in /bin /usr/bin /usr/local/bin /sbin/usr/sbin /usr/local/sbin quit
do
case $dir in
quit)
#如果用户选择quit,则退出脚本
echo "You entered quit,Byebye!"
break
;;
/*)
#匹配所选择目录,$dir以/开始说明用户选择了某个目录
echo "You selected directory \"$dir\",it contains following files:"
echo
#显示所选目录中的内容
ls -l $dir
;;
*)
#所有其他的输入都为非法
echo "Error,invalid selection \"$REPLY\",chose again!"
;;
esac
echo
echo "Which directory do you want to list:"
echo "(Press \"Enter\" directly to show menu again)"
done
exit 0
select 循环默认的提示符是#? 如果想修改成自定义的提示符,可以在shell脚本的开头一行#!/bin/bash后面加入
PS3=">"
这一行,从而把select循环的提示符修改为 > ,由此可知,设置PS3这个变量可以指定selec循环的提示符
提示
忘记写退出select循环的语句,会导致循环无休止的运行下去
7. break语句
break语句的语法格式为:
break [n]
break语句的作用是从一个循环结构中退出,这个循环结构可以是for循环,while循环,until循环或select循环。如果没有指定参数n,则结束最内层的循环;如果指定了可选参数n,则跳出n层循环。n的值必须大于等于1.如果指定的参数n大于包围break语句的循环个数,就跳出所有循环语句
8. continue语句
用来结束当前循环的本次迭代,而继续循环下次迭代。
continue语句的语法和break语句的语法格式一样,为:
continue [n]
continue语句的作用是:继续执行包囊continue的for,while,until,或select循环语句的下一次迭代。如果指定了参数n,则继续执行包囊continue语句的第n层的循环语句的下一次迭代。参数必须大于等于1.只要n大于等于1,continue语句就返回0
重要提示
continue参数n的含义是:结束当前循环迭代,从包囊continue n语句的第n层循环继续执行,开始第n层循环的下一次迭代。很容易将其错误的理解成跳过最内层循环的n次迭代,继续执行最内层循环