- 尽管还没有正式介绍引号的使用规则,但之前的脚本程序已经大量使用了引号(不过也只是双引号而已)。现在弥补这个空缺还来得及。在 Shell 脚本中可以使用的引号有如下3种。
-
双引号:阻止 Shell对大多数特殊字符(例如#)进行解释。但
$
、` 和 ” 仍然保持其特殊含义。 -
单引号:阻止 Shell 对所有字符进行解释。
-
倒引号:`,这个符号通常位于键盘上Esc键的下方。当用倒引号括起一个 Shell命令时,这个命令将会被执行,执行后的输出结果将作为这个表达式的值。倒引号中的特殊字符一般都被解释。
下面的脚本quote显示这3个引号的不同之处。
[root@localhost ~]# vim quote
#! /bin/bash
log=Saturday
#双引号会对其中的 “$” 字符进行解释
echo “Today is $log”
#单引号不会对特殊字符进行解释
echo ‘Today is $log’
#倒引号会运行其中的命令,并把命令输出作为最终结果
echo “Today is ‘date’”
- 以下是该脚本的运行结果。注意脚本的最后一行,双引号也会对 ` 做出解释。
5.运算符
-
运算符是类似于
+
、-
这样的符号,用于告诉计算机执行怎样的运算。 -
Shell定义了一套运算符,其中的大部分读者应该已经非常熟悉了。和数学中一样,这些运算符具有不同的优先级,优先级高的运算更早被执行。
Shell 中用到的运算符
| 运算符 | 含义 |
| — | — |
| -,+ | 单目负,单目正 |
| !,~ | 逻辑非,按位取反 |
| *,/,% | 乘,除,取模 |
| + | 加,减 |
| <<,>> | 按位左移,按位右移 |
| <,>,<=,>= | 大于,小于 ,大于等于,小于等于 |
| ==,!= | 等于,不等于 |
| & | 按位与 |
| ^ | 按位异或 |
| |
| 按位或 |
| && | 逻辑与 |
| ||
| 逻辑或 |
| =,+=,-=,*=,/=,%=,&=,^=
,|=
,<<=,>>= | 赋值,运算并赋值 |
-
这里无法对其中的每一个运算符做详细解释。Shell完全复制了C语言中的运算符及其优先级规则。在日常使用中,只需要使用其中的一部分就可以了,数学运算并不是Shell 的强项。
-
运算符的优先级并不需要特别地记忆。如果使用的时候搞不清楚,只要简单地使用括号就可以了,就像小学里学习算术时一样。
( 7 + 8 ) / ( 6 − 3 ) (7 + 8) / (6 - 3) (7+8)/(6−3)
- 值得注意的是,在Shell中表示相等时,
==
和=
在大部分情况下不存在差异,大家将会在后文中逐渐熟悉如何进行表达式的判断。
- 之所以单独列出这一节,因为这是让很多初学者感到困惑的地方。Shell中进行表达式求值有和其他编程语言不同的地方。首先来看一个例子,这个例子可以“帮助”读者产生困惑。
- 为什么结果不是2?原因很简单,Shell脚本语言是一种“弱类型”的语言,它并不知道变量num中保存的是一个数值,因此在遇到num=$num+1这个命令时,Shell 只是简单地把Snum和“+1”连在一起作为新的值赋给变量 num(在这方面,其他脚本语言——例如PHP似乎表现得更“聪明”一些)。为了让 Shell得到“正确”的结果,可以试试下面这条命令。
num= [ [ [num+1]
- $[]这种表示形式告诉 Shell应该对其中的表达式求值。如果上面这条命令不太容易能看清楚的话,那么不妨对比一下下面这两条命令的输出。
-
$[]
的使用方式非常灵活,可以接受不同基数的数字(默认情况下使用十进制)。可以采用[base#]n来表示从二到三十六进制的任何一个n值,例如2#10就表示二进制数10(对应于十进制的2)。 -
下面的几个例子显示了如何在
$[]
中使用不同的基数求值。
- expr命令也可以对表达式执行求值操作,这个命令允许使用的表达式更为复杂,也更为灵活,这里无法介绍 expr的高级用法。下面的例子是用expr计算1+2的值,注意expr会同时把结果输出。
-
注意:在
1
、+和2
之间要有空格,否则expr会简单地将其当做字符串输出。 -
另一种指导Shell进行表达式求值的方法是使用let命令。更准确地说,let命令用于计算整数表达式的值。下面这个例子显示了let命令的用法。
- 本节将介绍Shell脚本中的执行命令以及控制语句。在正常情况下,Shell 按顺序执行每一条语句,直至碰到文件尾。但在多数情况下,需要根据情况选择相应的语句执行,或者对一段程序循环执行。这些都是通过控制语句实现的。
1.if选择结构
- if命令判断条件是否成立,进而决定是否执行相关的语句。这也许是程序设计中使用频率最高的控制语句了。
最简单的if结构如下:
if test-commands
then
commands
fi
-
上面这段代码首先检查表达式test-commands是否为真。如果是,就执行commands所包含的命令——commands可以是一条,也可以是多条命令。如果test-commands为假,那么直接跳过这段if结构(以fi作为结束标志),继续执行后面的脚本。
-
下面这段程序提示用户输入口令。如果口令正确,就显示一条欢迎信息。
#! /bin/bash
echo “Enter password:”
read password
if [“$password” = “mypassswd”]
then
echo “Welcome!!”
fi
- 注意:这里用于条件测试的语句[ $password = “mypasswd” ],在[、Spassword、=、"mypasswd"和]之间必须存在空格。条件测试语句将在随后介绍,大家暂时只要能“看懂”就可以了。该脚本的运行效果如下:
- if 结构的这种形式在很多时候显得太过“单薄”了,为了方便用户做出“如果……如果……否则……”这样的判断,if结构提供了下面这种形式。
if test-command-1
then
commands-1
elif test-command-2
then
commands-2
elif test-command-3
then
commands-3
…
else
commands
fi
- 上面这段代码依次判断test-command-1、test-command-2、test-command-3……如果上面这些条件都不满足,就执行else语句中的commands。注意这些条件都是“互斥”的。也就是说,Shell依次检查每一个条件,其中任何一个条件一旦匹配,就退出整个if结构。现在修改上面刚才的脚本,根据不同的口令显示不同的欢迎信息。
- 下面显示了这个脚本的运行结果。在输入fjn之后,Shell 发现 if语句的第一个条件成立,于是Shell就执行命令echo “Hello, 方加你”,然后跳出if语句块结束脚本,而不会继续去判断"Spassword" = "mike"这个条件。从这个意义上,if-elif-else语句和连续使用多个if语句是有本质区别的。
2.case多选结构
- Shell中另一种控制结构是case语句。casc用于在一系列模式中匹配某个变量的值,这个结构的基本语法如下:
case word in
pattern-1)
commands-1
;;
pattern-2)
commands-2
;;
…
pattern-N)
commands-N
;;
esac
-
变量word逐一同从pattern-1到pattern-2的模式进行比较,当找到一个匹配的模式后,就执行紧跟在后面的命令commands(可以是多条命令)﹔如果没有找到匹配模式,case语句就什么也不做。
-
命令
;;
只在case结构中出现,Shell一旦遇到这条命令就跳转到case结构的最后。也就是说,如果有多个模式都匹配变量word,那么Shell 只会执行第一条匹配模式所对应的命令。与此类似的是,C语言提供了break语句在switch结构中实现相同的功能,Shell只是继承了这种书写“习惯”。区别在于,程序员可以在C程序的switch结构中省略break语句(用于实现一种几乎不被使用的流程结构),而在Shell的case结构中省略;;
则是不允许的。 -
相比较if语句而言,case语句在诸如
a=b
这样判断上能够提供更简洁、可读性更好的代码结构。在Linux 的服务器启动脚本中,case结构用于判断用户究竟是要启动、停止还是重新启动服务器进程。下面是从 openSUSE 中截取的一段控制SSH 服务器的脚本(/etc/init.d/sshd) 。
- 在这个例子中,如果用户运行命令
/etc/init.d/sshd start
,那么Shell将执行下面这段命令:通过startproc启动SSH守护进程。
echo -n “Starting SSH daemon”
Start daemon with startproc(8). If this fails
the echo return value is set appropriate.
startproc -f -p $SSHD_PIDFILE $SSHD_BIN S S H D O P T S − o " P i d F i l e = SSHD_OPTS -o "PidFile= SSHDOPTS−o"PidFile=SSHD_PIDFILE"
#Remember status and be verbose
rc_status -v
- 值得注意的是最后使用的
*)
,星号(*)
用于匹配所有的字符串。在上面的例子中,如果用户输入的参数不是start、stop或是restart 中的任何一个,那么这个参数将匹配*)
,脚本执行下面这行命令,提示用户正确的使用方法。
echo “Usage: $0 {start|stop|restart|}”
- 由于case语句是逐条检索匹配模式,因此
*)
所在的位置很重要。如果上面这段脚本将*)
放在case 结构的开头,那么无论用户输入什么,脚本只会说Usage: $o{start/stopprestart}
这一句话。
- 几乎所有初学Shell编程的人都会对这部分内容感到由衷的困惑。Shell和其他编程语言在条件测试上的表现非常不同。读者在C/C++积累的经验甚至可能会帮倒忙。理解本节对顺利进行Shell编程至关重要,因此如果读者是第一次接触的话,请耐心地读完这冗长的一节。
1.if判断的依据
- 和大部分人的经验不同的是,if 语句本身并不执行任何判断。它实际上接受一个程序名作为参数,然后执行这个程序,并依据这个程序的返回值来判断是否执行相应的语句。如果程序的返回值是0,就表示“真”,if语句进入对应的语句块;所有非0的返回值都表示“假”,if语句跳过对应的语句块。下面的这段脚本 testif很好地显示了这一点。
#!/bin/bash
if ./testscript -1
then
echo “test exit -1”
fi
if ./testscript 0
then
echo “test exit 0”
fi
if ./testscript 1
then
echo “test exit 1”
fi
- 脚本的运行结果如下:
- 这段脚本依次测试返回值-1、0和1,最后只有返回值为0所对应的echo语句被执行了。脚本中调用的testscript 接受用户输入的参数,然后简单地把这个参数返回给其父进程。testscript脚本只有两行代码,其中的exit语句用于退出脚本并返回一个值。
#!/bin/bash
exit $@
- 现在大家应该能够大致了解if语句(包括后面将要介绍的 while、until等语句)的运行机制。也就是说,if语句事实上判断的是程序的返回值,返回值0表示真,非0值表示假。
2.test命令和空格的使用
- 既然if语句需要接受一个命令作为参数,那么像
"$password" = "john"
这样的表达式就不能直接放在 if语句的后面。为此需要额外引入一个命令,用于判断表达式的真假。test命令的语法如下:
test expr
- 其中 expr是通过test命令可以理解的选项来构建的。例如下面这条命令用于判断字符串变量password是否等于"john"。
test “$password” = “john”
- 如果两者相等,那么test命令就返回值0;反之则返回1。作为test的同义词,用户也可以使用方括号
[
进行条件测试。后者的语法如下:
[ expr ]
-
必须提醒读者注意的是,在Shell编程中,空格的使用绝不仅仅是编程风格上的差异。
-
现在来对比下面3条命令:
password=“john”
test “$password” = “john”
[ “$password” = “john”]
-
第一条是赋值语句,在password、= 和"john"之间没有任何空格;第2条是条件测试命令,在test、“Spassword”、=和"john"之间都有空格;第3条也是条件测试命令(是test命令的另一种写法),在[、“$password”、=、"john"和]之间都有空格。
-
一些C程序员喜欢在赋值语句中等号“=”的左右两边都加上空格,因为这样看上去会比较清晰,但是在 Shell中这种做法会导致语法错误。
password = “john”
bash: password: 找不到命令
-
同样地,试图去掉条件测试命令中的任何一个空格也是不允许的。去掉“[”后面的空格是语法错误,去掉等号(=)两边的空格会让测试命令永远都返回0(表示真)。
-
之所以会出现这样的情况是因为Shell首先是一个命令解释器,而不是一门编程语言。空格在Shell这个“命令解释器”中用于分隔命令和传递给它的参数(或者用于分隔命令的两个参数)。使用whereis命令查找test和“[”可以看到,这是个存放在/usr/bin目录下的“实实在在”的程序文件。
- 因此在上面的例子中,“Spassword”、=和"john"都是test命令和[命令的参数,参数和命令、参数和参数之间必须要使用空格分隔。而单独的赋值语句password="john"不能掺杂空格的原因也就很明显了。password是变量名,而不是某个可执行程序。
test和[
命令可以对以下3类表达式进行测试;
-
字符串比较
-
文件测试
-
数字比较
字符串比较
- test和[命令的字符串比较主要用于测试字符串是否为空,或者两个字符串是否相等。
用于字符串比较选项
| 选项 | 描述 |
| — | — |
| -z str | 当字符串 str 长度为 0 时返回真 |
| -n str | 当字符串 str 长度大于 0 时返回真 |
| str1 = str2 | 当字符串 str1 和 str2 相等时返回真 |
| str1 != str2 | 当字符串 str1 和 str2 不相等时返回真 |
- 下面这段脚本用于判断用户的输入是否为空。如果用户什么都没有输入,就显示一条要求输入口令的信息。
#!/bin/bash
read password
if [ -z “$password”]
then
echo “Please enter the password”
fi
- 注意,在$password两边加上了引号(“”),这在Bash中并不是必要的。Bash 会自动给没有值的变量加上引号,这样变量看上去就像是一个空字符串一样。但有些Shell并不这样做,如果 Shell简单地把空的password变量替换为一个空格,那么上面的判断语句就会变成这样。
if [ -z ]
-
毫无疑问,在这种情况下Shell就会报错。从清晰度和可移植性的角度考虑,为字符串变量加上引号是一个好的编程习惯。
-
用于比较两个字符串是否相等的操作在上文中已经作了介绍。不过需要注意的是,Shell对大小写敏感,只有两个字符串
完全相等
才会被认为是相等
的。下面的例子说明了这一点。
#!/bin/bash
if [ “ABC” = “abc” ]
then
echo “ABC”==“abc”
else
echo “ABC”!=“abc”
fi
if [ “ABC” = “ABC” ]
then
echo “ABC”==“ABC”
else
echo “ABC”!=“ABC”
fi
- 运行结果显示,ABC和ABC是相等的,而ABC和 abc 则是不相等的。
文件测试
- 文件测试用于判断一个文件是否满足特定的条件。
| 选项 | 描述 |
| — | — |
| -b file | 当file是块设备文件时返回真 |
| -c file | 当file是块设备文件时返回真 |
| -d pathname | 当file是块设备文件时返回真 |
| -e pathname | 当file是块设备文件时返回真 |
| -f file | 当file是块设备文件时返回真 |
| -g pathname | 当file是块设备文件时返回真 |
| -h file | 当file是块设备文件时返回真 |
| -p file | 当file是块设备文件时返回真 |
| -r pathname | 当file是块设备文件时返回真 |
| -s file | 当file是块设备文件时返回真 |
| -u pathname | 当file是块设备文件时返回真 |
| -w pathname | 当file是块设备文件时返回真 |
| -x pathname | 当file是块设备文件时返回真 |
| -o pathname | 当file是块设备文件时返回真 |
- 文件测试选项的使用非常简单。下面的例子取自系统中的 rc脚本。如果/sbin/unconfigured.sh文件存在并且可执行,就执行这个脚本。否则什么也不做。
if [ -x /sbin/unconfigured.sh ]
then
/sbin/unconfigured.sh
fi
数字比较
- test和[命令在数字比较方面只能用来比较整数(包括负整数和正整数)。其基本的语法如下:
test int1 option int2
- 或者
[ int1 option int2 ]
- 其中的option表示比较选项。
用于数字比较的选项
| 选项 | 对应的英语单词 | 描述 |
| — | — | — |
| -eq | equal | 如果相等,返回真 |
| -ne | not equal | 如果不相等,返回真 |
| -lt | lower than | 如果 int1 小于 int2,返回真 |
| -le | lower or equal | 如果 int1 小于或等于 int2,返回真 |
| -gt | greater than | 如果 int1 大于 int2,返回真 |
| -ge | greater or equal | 如果 int1 大于或等于 int2,返回真 |
- 下面这段代码取自Samba服务器的启动脚本。脚本使用-eq选项测试变量status是否等于0。如果是,就调用log_success_msg 显示Samba已经运行的信息,否则就调用log_failure_msg 显示Samba没有运行。
if [ $status -eq 0 ]; then
log_success_msg “SMBD is running”
else
log_failure_msg “SMBD is not running”
fi
复合表达式
- 到目前为止,所有的条件判断都是只有单个表达式。但在实际生活中,人们总是倾向于组合使用几个条件表达式,这样的表达式就被称为复合表达式。test和[命令本身内建了操作符来完成条件表达式的组合。
| 操作符 | 描述 |
| — | — |
| ! expr | “非” 运算,当expr为假时返回真 |
| expr1 -a expr2 | “与” 运算,当expr1和expr2同时为真时才返回真 |
| expr1 -o expr2 | “或” 运算,expr1或expr2为真时返回真 |
- 下面这段脚本接受用户的输入,如果用户提供的文件存在,并且vi编辑器存在,就先复制(备份)这个文件,然后调用vi编辑器打开;如果用户文件不存在,或者没有vi编辑器,就什么都不做。
#!/bin/bash
if [ -f $@ -a -x /usr/bin/vi ]
then
cp $@ $@.bak
vi $@
fi
- 具体来说,该if语句依照下面的步骤执行。
- 首先执行
-f $@
测试命令,如果S@
变量(也就是用户输入的参数)对应的文件存在,那么该测试返回真(0)﹔否则整条测试语句返回假,直接跳出if语句块。
- 如果第一个条件为真,就执行
-x /usr/bin/vi
测试命令。如果/usr/bin/vi文件可执行,那么该测试返回真(0),同时整条测试语句返回真(0)。否则整条测试语句返回假,直接跳出if语句块。
- 如果整条测试语句返回真,那么就执行if语句块中的两条语句。
- 再来看一个使用-o(或)和!(非)运算的例子。下面这段脚本在变量password非空,或者密码文件.private key存在的情况下向父进程返回0。否则提示用户输入口令。
if [ ! -z “$password” -o -f ~/.public_key ]
then
exit 0
else
echo “Please enter the password:”
read password
fi
- 该if语句依照下面的步骤执行。
- 首先执行
! -z "Spassword"
测试命令,如果字符串 password 不为空,那么该测试语句返回真(0),同时整条测试语句返回真(0),不再判断-f~/.public_key
。
- 如果第一个条件为假,就执行
-f ~/.public_key
测试命令。如果主目录下的.public_key文件存在,那么该测试返回真(0),同时整条测试语句返回真(0)﹔否则整条测试语句返回假,直接跳出if语句块。
- 如果整条测试语句返回真,那么就执行
exit 0
﹔否则执行else语句块中的语句。
-
提示:注意-a (与)和-o(或)在什么情况下会判断第2条语句。前者在第1条语句为真的时候才判断第2条语句(因为如果第1条语句就不成立,那么整条测试语句一定不会成立)﹔后者在第1条语句为假的情况下才判断第2条语句(因为如果第1条语句为真,那么整条测试语句一定成立)。记住这一点,在后面的“复合命令”中还会碰到。
-
Shell的条件操作符
&&
和||
可以用来替代test和[命令内建的-a
和-o
。如果选择使用Shell的条件操作符,那么上面的第一个例子可以改写成这样:
if [ -f $@ ] && [ -x /usr/bin/vi ]
then
cp $@ $@.bak
vi $@
fi
- 注意,
&&
连接的是两条[(或者test)命令,而-a操作符是在同一条[(或者test)命令中使用的。类似地,上面使用-o操作符的脚本可以改写成这样:
if [ ! -z “$password” ] || [ -f ~/.public_key ]
then
exit
else
echo “Please enter the password:”
read password
fi
- 究竟是使用Shell的条件操作符(&&、||)还是test/[命令内建的操作符(-a、-o),并没有“好”与“不好”的差别,这只是“喜欢”和“不喜欢”的问题。一些程序员偏爱“&&”和“I”是因为这样可以使条件测试看上去更清晰。而另一方面,由于-a和-o只需要用到一条test语句,因此执行效率会相对高一些。鱼和熊掌不可兼得,谁说不是呢?
- 循环结构用于反复执行一段语句,这也是程序设计中的基本结构之一。Shell 中的循环结构有3种: while、until和 for。下面逐一介绍这3种循环语句。
1.while语句
- while语句重复执行命令,直到测试条件为假。该语句的基本结构如下。注意, commands可以是多条语句组成的语句块。
while test=commands
do
commands
done
- 运行时, Shell首先检查test-commands是否为真(为0),如果是,就执行命令commands。commands执行完成后, Shell再次检查test-commands,如果为真,就再次执行commands……这样的“循环”一直持续到条件test-commands为假(非0)。为了更好地说明这一过程,下面这个脚本让 Shell 做一件著名的体力活:计算1+2+3+……+100。
#!/bin/bash
sum=0
number=1
while test $number -le 100
do
sum=$[ $sum + $number ]
let number=$number+1
done
echo “The summary is $sum”
-
简单地分析一下这段小程序。在程序的开头,首先将变量 sum和 number初始化为О和1,其中变量sum保存最终结果,number则用于保存每次相加的数。测试条件
$number-le 100
告诉Shell 仅当number 中的数值小于或等于100的时候才执行包含在do和done之间的命令。注意,每次循环之后都将number 的值加上1,循环在number达到101的时候结束。 -
保证程序能在适当的时候跳出循环是程序员的责任和义务。在上面这个程序中,如果没有
let number=$number+1
这句话,那么测试条件将永远为真,程序就陷在这个死循环中了。 -
while语句的测试条件未必要使用test(或者[ ])命令。在 Linux 中,命令都是有返回值的。例如,read 命令在接收到用户的输入时就返回0,如果用户用Ctrl+D快捷键输入一个文件结束符,那么read命令就返回一个非0值(通常是1)。利用这个特性,可以使用任何命令来控制循环。下面这段脚本从用户处接收一个大于0的数值n,并且计算1+2+3+……+n。
#!/bin/bash
echo -n “Enter a number(>0):”
while read n
do
sum=0
count=1
if [ $n -gt 0 ]
then
while [ $count -le $n ]
do
sum=$[ $sum + $count ]
let count=$count+1
done
echo “The summary is $sum”
else
echo “Please enter a number greater than zero”
fi
echo -n “Enter a number(>0):”
done
- 这段脚本不停地读入用户输入的数值,并判断这个数是否大于0。如果是,就计算从1一直加到这个数的和。如果不是,就显示一条提示信息,然后继续等待用户的输入,直到用户输入快捷键Ctrl+D(代表文件结束)结束输入。下面显示了这个脚本的执行效果。
2.until语句
- until 是 while语句的另一种写法——除了测试条件相反。其基本语法如下:
until test-commands
do
commands
done
-
单从字面上理解,whilc说的是
当test-commands为真(值为0),就执行commands
。而until 说的是执行commands,直到 test-commands为真(值为0)
,这句话顺过来讲可能更容易理解。当test-commands为假(非0值),就执行commands
。 -
但愿大家没有被上面这些话搞糊涂。下面这段脚本麻烦Shell 再做一次那个著名的体力劳动,不同的是这次改用until语句。
#! /bin/bash
sum=0
number=1
until ! test $number -le 100
do
sum=$[ $sum + $number ]
let number=$number+1
done
echo “The summary is $sum”
- 注意,下面这两句话是等价的
while test $number -le 100
和
until ! test $number -le 100
3.for语句
- 使用while语句已经可以完成Shell编程中的所有循环任务了。但有些时候用户希望从列表中逐一取一系列的值(例如取出用户提供的参数),此时使用while和until就显得不太方便。Shell提供了for语句,这个语句在一个值表上迭代执行。for 的基本语法如下:
for variable [in list]
do
commands
done
- 这里的
值表
是一系列以空格分隔的值。Shell每次从这个列表中取出一个值,然后运行do/done之间的命令,直到取完列表中所有的值。下面这段程序简单地打印出1~9之间(包括1和6)所有的数。
#! /bin/bash
for i in 1 2 3 4 5 6 7 8 9
do
echo $i
done
- 每次循环开始的时候,Shell 从列表中取出一个值,并把它赋给变量i,然后执行命令块中的语句(即echo $i)。下面显示了这个脚本的运行结果,注意 Shell是按顺序取值的。
-
用于存放列表数值的变量并不一定会在语句块中用到。如果某件事情需要重复N次的话,只要给for 语句提供一个包含N个值的列表就可以了。不过这种“优势”听上去有些可笑,如果N是一个特别大的数,难道需要手工列出所有这些数字吗?
-
Shell 的简便性在于,所有已有的工具都可以在Shell 脚本中使用。Shell本身带了一个叫做seq的工具,该命令接受一个数字范围,并把它转换为一个列表。如果要生成1~9的数字列表,那么可以这样使用seq。
seq 9
- 这样,上面这个程序就可以改写成下面这样:
#! /bin/bash
for i in ‘seq 9’
do
echo $i
done
-
这里使用了倒引号,表示要使用Shell 执行这条语句,并将运行结果作为这个表达式的值。
-
用户也可以指定seq输出的起始数字(默认是1),以及“步长”。seq命令将在后续文章中详细讨论。
-
for语句也可以接受字符和字符串组成的列表,下面这个脚本统计当前目录下文件的个数。
#! /bin/bash
count=0
for file in ‘ls’
do
if ! [ -d $file ]
then
let count=$count+1
fi
done
echo “There are $count files”
- 这段脚本每次从ls生成的文件列表中取出一个值存放在file变量中,并给计数器增加1。下面是这段脚本的执行效果。
-
Shell程序并不经常和用户进行大量的交互,但有些时候接受用户的输入仍然是必须的。read 命令提供了这样的功能,从标准输入接收一行信息。在前面的几节中,读者已经在一些程序中使用了read命令,这里将进一步解释其中的细节。
-
read命令接受一个变量名作为参数,把从标准输入接收到的信息存放在这个变量中。如果没有提供变量名,那么读取的信息将存放在变量 REPLY中。下面的例子说明了这一点。
- 可以给read命令提供多个变量名作为参数。在这种情况下,read命令会将接收到的行“拆开”分别赋予这些变量。当然,read需要知道怎样将一句话拆成若干个单词,默认情况下,Bash只认识空格、制表符和换行符。下面这个脚本将用户输入拆分为两个单词分别放入变量first 和 second 中。
#! /bin/bash
read first second
echo $first
echo $second
- 下面是输入Hello World!后该脚本的输出。
- 下面介绍另两条用于控制脚本行为的命令exit和 trap。前者退出脚本并返回一个特定的值,后者用于捕获信号。合理地使用这两条命令,可以使脚本的表现更为灵活高效。
1.exit命令
- exit命令强行退出一个脚本,并且向调用这个脚本的进程返回一个整数值。例如:
#! /bin/bash
exit 1
- 在一个进程成功运行后,总是向其父进程返回数值0。其他非零返回值都表示发生了某种异常。这条规则至少被广泛地应用,因此不要轻易去改变它。至于说父进程为什么需要接受这样一个返回值,这是父进程的事情——可以定义一些操作来处理子进程的异常退出(通过判断返回值是什么),也可以只是简单地丢弃它。
2.trap命令
- trap命令用来捕获一个信号。信号是进程间通信的一种方式。可以简单地使用trap命令捕捉并忽视一个信号。下面这个脚本忽略INT信号,并显示一条信息提示用户应该怎样退出这个程序(INT信号当用户在Shell 中按Ctrl+C快捷键时被发送)。
#! /bin/bash
trap ‘echo “Type quit to exit”’ INT
while [ “$input” != ‘quit’ ]
do
read input
done
- 下面是这段脚本的执行效果。
- 有时候忽略用户的中断信号是有益的。某些程序不希望自己在执行任务的时候被打断,而要求用户依照正常手续退出。trap还可以捕捉其他一些信号,下面这段脚本在用户退出脚本的时候显示“Goodbye!”,就像ftp客户端程序做的那样。
#! /bin/bash
trap ‘echo “Goodbye”; exit’ EXIT
echo “Type ‘quit’ to exit”
while [ “$input” != “quit” ]
do
read input
done
- 注意,在 trap命令中使用了复合命令
echo "Goodbye"; exit
,即先执行echo"Goodbye"
显示提示信息,再执行exit退出脚本。这条复合命令在脚本捕捉到EXIT信号的时候执行。EXIT信号在脚本退出的时候被触发。下面是该脚本的执行效果。
- Linux中还有很多其他信号,用于执行不同的操作。并不是所有的信号都可以被捕捉(比如 kill信号就不能被操纵或者忽略)。
- test命令的-a和-o参数执行第2条测试命令的情况是不同的。这一点同样适用于Shell内建的
&&
和||
。事实上,&&
和||
更多地被用来创建命令表,命令表可以利用一个命令的退出值来控制是否执行另一条命令。下面这条命令取自系统的rc脚本。
[ -d /etc/rc.boot ] && run-parts /etc/rc.boot
- 这条命令首先执行
[ -d /etc/rc.boot ]
,判断目录/etc/rc.boot是否存在。如果该测试命令返回真,就继续执行run-parts /etc/rc.boot
调用run-parts命令执行/etc/rc.boot 目录中的脚本。如果测试命令[ -d /etc/rc.boot ]
返回假(即/etc/rc.boot目录不存在),那么run-parts命令就不会执行。因此上面这条命令等价于:
if [ -d /etc/rc.boot ]
then
run-parts /etc/rc.boot
fi
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

线程、数据库、算法、JVM、分布式、微服务、框架、Spring相关知识
一线互联网P7面试集锦+各种大厂面试集锦
学习笔记以及面试真题解析
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!
的操作。并不是所有的信号都可以被捕捉(比如 kill信号就不能被操纵或者忽略)。
- test命令的-a和-o参数执行第2条测试命令的情况是不同的。这一点同样适用于Shell内建的
&&
和||
。事实上,&&
和||
更多地被用来创建命令表,命令表可以利用一个命令的退出值来控制是否执行另一条命令。下面这条命令取自系统的rc脚本。
[ -d /etc/rc.boot ] && run-parts /etc/rc.boot
- 这条命令首先执行
[ -d /etc/rc.boot ]
,判断目录/etc/rc.boot是否存在。如果该测试命令返回真,就继续执行run-parts /etc/rc.boot
调用run-parts命令执行/etc/rc.boot 目录中的脚本。如果测试命令[ -d /etc/rc.boot ]
返回假(即/etc/rc.boot目录不存在),那么run-parts命令就不会执行。因此上面这条命令等价于:
if [ -d /etc/rc.boot ]
then
run-parts /etc/rc.boot
fi
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-wOS5aPHQ-1712487927035)]
[外链图片转存中…(img-b2XulgDP-1712487927036)]
[外链图片转存中…(img-5CCPEcS2-1712487927036)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

线程、数据库、算法、JVM、分布式、微服务、框架、Spring相关知识
[外链图片转存中…(img-8Df8W6xX-1712487927037)]
一线互联网P7面试集锦+各种大厂面试集锦
[外链图片转存中…(img-DuakWuB3-1712487927037)]
学习笔记以及面试真题解析
[外链图片转存中…(img-vRcDqvRO-1712487927037)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!