9.7 控制结构
TC Shell所用的许多控制结构与Bourne Again Shell中的相同。虽然每种结构的语法不一样,但是它们的作用是相同的。本节将总结两种shell中控制结构之间的差异。更多信息请参见第11.1节。
9.7.1 if
if控制结构的语法如下:
if(expression) simple-command
if控制结构只能用于简单的命令,而不能用于命令管道或者列表。可以使用if...then控制结构来执行更加复杂的命令。
tcsh $ cat if_1
#!/bin/tcsh
# Routine to show the use of a simple if control structure.
#
if ( $#argv == 0 ) echo "if_l: there are no arguments"
脚本if_1检查调用时是否没有带任何参数。如果括在圆括号中的表达式的值为true(也就是说这个命令行上没有任何参数),if结构显示一条消息。
除了像if_1脚本使用的逻辑表达式之外,还可以使用返回值的表达式,这个值是根据文件的状态计算出来的。这类表达式的语法如下:
-n filename
其中,n的取值如表9-8所示。
表9-8 n的取值
|
n |
含 义 |
|
b |
块设备文件 |
|
c |
字符设备文件 |
|
d |
目录文件 |
|
e |
文件存在 |
|
f |
普通文件或者目录文件 |
|
g |
文件设置了set-group-ID位 |
|
k |
文件设置了粘滞位 |
|
l |
文件是一个符号链接 |
|
o |
文件为用户所有 |
|
p |
文件为命名管道 |
|
r |
用户具有读访问权限 |
|
s |
文件非空(所占空间不为0) |
|
S |
套接字设备文件 |
|
t |
文件描述符(用于代替filename的单个数字)已经打开并且已经连接到屏幕 |
|
u |
文件设置了set-user-ID位 |
|
w |
用户具有文件的写访问权限 |
(续表)
|
n |
含 义 |
|
x |
用户具有文件的执行访问权限 |
|
X |
文件要么是内置命令,要么是在$path所含的目录中搜索到的某个可执行文件 |
|
z |
文件长度为0 |
如果测试结果为true,那么该表达式的值为1;如果结果为false,那么这个表达式的值为0。如果指定文件不存在或者不可访问,tcsh将该表达式的结果设为0。下面的示例检查命令行上执行的文件是否是一个普通文件或者目录文件(而不是设备文件或者其他特殊文件):
tcsh $ cat if_2
#!/b-in/tcsh
-if -f $1 echo "Ordinary or Directory file"
如果有意义,那么还可以合并操作符。举例来说,如果用户是文件filename的所有者,并且具有执行访问权限,那么-ox filename就为true。这个表达式等同于-o filename && -x filename。
有些操作符可以返回文件相关的有用信息,而不是仅仅报告true或者false。操作符的格式与-n filename的格式相同,其中n为表9-9所示的值之一。
表9-9 n的取值
|
n |
含 义 |
|
A |
文件最后被访问的时间* |
|
A: |
按照便于人类阅读的格式显示文件最后被访问的时间 |
|
M |
文件最后被修改的时间* |
|
M: |
按照便于人类阅读的格式显示文件最后被修改的时间 |
|
C |
文件inode最后被修改的时间* |
|
C: |
按照便于人类阅读的格式显示文件inode最后被修改的时间 |
|
D |
文件的设备编号,这个编号能够唯一标识文件所在的设备(比如磁盘分区) |
|
I |
文件的inode编号,inode编号唯一标识特定设备上的某个文件 |
|
F |
形如device:inode的字符串,该串能够唯一标识系统上任何地方的文件 |
|
N |
到文件的硬链接的数目 |
|
P |
文件的访问权限,按照八进制显示,开头没有0 |
|
U |
文件所有者的用户ID数字 |
|
U: |
文件所有者的用户名 |
|
G |
文件所属用户组的组ID数字 |
|
G: |
文件所属用户组的名称 |
|
Z |
文件的大小,单位为字节 |
|
*标记的时间为自纪元(通常是从1970年1月1日开始)以来的秒数 |
|
对于表9-9中的这些操作符,在一次测试中只能使用其中的某一个,而且该操作符还必须作为多操作符序列的最后一个操作符。因为0可能是某些操作符有效的返回值(例如某个文件的长度可能为0),所以在测试失败时返回-1,而不是逻辑表达式在失败时返回的0。一个例外是F,如果它不能判断某个文件的设备或者inode,那么它将返回一个冒号。
如果希望在控制结构表达式之外使用这些操作符,那么可以使用内置命令filetest来进行文件测试并报告结果:
tcsh $ filetest -z if_l
0
tcsh $ filetest -F if_l
2051:12694
tcsh $ filetest -Z if_l
131
9.7.2 goto
goto语句的语法如下:
goto label
内置命令goto将控制转移到以label开头的语句,下面的脚本片段说明了goto的用法:
tcsh $ cat goto_l
#!/bin/tcsh
#
# test for 2 arguments
#
#if ($#argv == 2) goto goodargs
echo "Usage: goto_l arg1 arg2"
exit 1
goodargs:
...
当调用goto_1脚本的时候,如果参数个数多于或者少于2个,该脚本将显示用法信息。
9.7.3 中断处理
当用户中断shell脚本时,onintr语句转移控制。onintr语句的格式如下:
onintr label
当用户在shell脚本执行期间按下中断键时,shell将控制转移到label开始的语句。使用这条语句可以让脚本被中断时“恰当”地终止。举例来说,当用户中断某个shell脚本时,使用这个语句可以确保在将控制权返还给父shell之前删除临时文件。
下面的脚本说明了onintr。它将不断地循环,直到用户按下中断键,此时将显示一条消息并将控制权转移给本shell:
tcsh $ cat onintr_l
#!/bin/tcsh
# demonstration of onintr
onintr close
while ( 1 )
echo "Program is running."
sleep 2
end
close:
echo "End of program."
如果某个脚本创建了一些临时文件,那么就可以使用onintr来删除它们。
close:
rm -f /tmp/$$*
模糊文件名引用/tmp/$$*匹配/tmp中以当前shell的PID编号开头的所有文件。参见第11.3.3节以获取临时文件命名技术的描述。
9.7.4 if...then...else
if...then...else控制结构有3种形式。第1种是简单if结构的扩展,如果expression为true,那么将执行更复杂的commands,或者一系列commands。这种形式仍然属于单向分支。
if (expression) then
commands
endif
第2种形式属于双向分支。如果expression为true,那么第1组commands将被执行。如果expression为false,那么紧接着else的一组commands将会被执行。
if (expression) then
commands
else
commands
endif
第3种形式类似于if...then...elif结构。它将执行测试直到它发现某个为true的expression,然后执行相应的commands:
if (expression) then
commands
else if (expression) then
commands
...
else
commands
endif
下面的程序根据第1个命令行参数的值,将值0、1、2或者3指派给变量class。为了清晰起见,在程序开始处声明了变量class。其实在第1次使用之前,没有必要声明这个变量。同样为了清晰起见,脚本将第1个命令行参数的值指派给number。
tcsh $ cat if_e1se_l
#!/bin/tcsh
# routine to categorize the first
# command line argument
set class
set number = $argv[l]
#
if ($number < 0) then
@ class = 0
else if (0 <= $number && $number < 100) then
@ class = 1
else if (100 <= $number && $number < 200) then
@ class = 2
else
@ class = 3
endif
#
echo "The number $number is in class ${c1ass}."
第1个if语句测试number是否小于0。如果小于0,那么该脚本将0指派给class并将控制转移到endif后面的那条语句;如果不小于0,那么第2个if将测试这个数字是否介于0~100之间。&&是布尔AND操作符,如果表达式两边的值均为true,那么这个表达式的值为true。如果这个数字介于0~100之间,那么class将被指派值1,同时控制转移到endif后面的那条语句。类似的测试判断数字是否介于100~200之间,如果不是,那么最后的else将3指派给class。endif结束了if控制结构。最后一条语句使用花括号({})将变量class与后面的句点隔开。花括号隔离句点是为了清晰起见;shell并不将这样的标点符号作为变量名的一部分。如果希望在变量后面紧接着加上一些字符,那么也需要花括号。
9.7.5 foreach
foreach结构与bash中的for...in等价。其语法如下:
foreach loop-index (argument-list)
commands
end
这个结构循环执行commands。第1次执行该循环时,这个结构将argument-list中的第1个参数指派给loop-index。当控制到达end语句时,shell从argument-list中取出下一个参数,将其指派给loop-index,并再次执行这些命令。shell将重复上面这个过程,直到取完argument-list为止。
下面的tcsh脚本使用foreach结构遍历工作目录中文件名中包含指定字符串的文件,同时还修改这个字符串。举例来说,用户可以使用该脚本将文件名中所含的memo修改为letter,例如文件名memo.1、dailymemo和memories将被修改为letter.1、dailyletter和letterries。
tcsh $ cat ren
#!/bin/tcsh
# Usage: ren arg1 arg2
# changes the string arg1 -in the names of files
# in the working directory into the string arg2
if ($#argv != 2) goto usage
foreach i ( *$1* )
mv $i `echo $i | sed -n s/$l/$2/p`
end
exit 0
usage:
echo "Usage: ren arg1 arg2"
exit 1
这个脚本需要两个参数:将要被修改的字符串(旧字符串)和新字符串。foreach结构的argument-list使用模糊文件引用循环遍历所有包含第1个参数的文件名。对于每个匹配该模糊文件引用的文件名,使用mv工具进行修改。echo和sed命令出现在反引号(`)中,指出进行命令替换:反引号中的命令的执行结果将代替反引号以及所有出现在它们中间的内容。参见第8.9.4节中的“命令替换”以获取更多信息。sed工具用第2个参数替换出现在文件名中的第1个参数。$1和$2分别是$argv[1]和$argv[2]的简记形式。
选读
下面的脚本使用foreach循环将命令行参数指派给名为buffer数组的每个元素:
tcsh $ cat foreach_l
#!/b1n/tcsh
# routine to zero-fill argv to 20 arguments
#
set buffer =(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
set count = 1
#
if ($#argv > 20) goto toomany
#
foreach argument ($argv[*])
set buffer[$count] = $argument
@ count++
end
# REPLACE command ON THE NEXT LINE WITH
# THE PROGRAM YOU WANT TO CALL.
exec command $buffer[*]
#
toomany:
echo "Too many arguments given."
echo "Usage: foreach^l [up to 20 arguments]"
exit 1
foreach_1脚本通过一个命令行来调用另一个名为command的程序,该命令行确保包含20个参数。如果调用foreach_1时参数个数小于20,那么它将使用0来填充,从而为command提供20个参数。如果参数个数超过20,则将显示一条错误消息,并且退出状态为1。
对于命令行中的每个参数,foreach结构都要执行一次这些命令。在每次循环时,foreach从命令行中取下一个参数并将其指派给buffer数组中的某个元素。变量count维护了数组buffer的索引。后缀操作符使用@将count变量递增(@ count++)。内置命令exec调用command,这样就不会启动一个新的进程。(一旦调用了command,就不再需要运行这个例程的进程,因而没有必要创建新的进程。)
9.7.6 while
while结构的语法如下:
while (expression)
commands
end
当expression为true时,这个结构将一直循环执行commands。如果expression第1次计算时为false,那么这个结构就不会执行commands。
tcsh $ cat whi1e_l
#!/bin/tcsh
# Demonstration of a while control structure.
# This routine sums the numbers between 1 and n,
# with n being the first argument on the command # line.
#
set limit = $argv[l]
set index = 1
set sum = 0
#
while ($index <= $limit)
@ sum += $index
@ index++
end
#
echo "The sum is $sum"
这个程序用来计算从1~n(包含n)的所有整数的和,其中n是该命令行的第1个参数。+=操作符将sum+index的值指派给sum。
9.7.7 break和continue
使用break或者continue语句可以中断foreach和while结构。在转移控制之前,这些语句将执行剩余语句。break语句中断循环执行,并将控制转移到end之后的语句。continue语句将控制转移到end语句,它将继续执行循环。
9.7.8 switch
switch结构与bash下面的case语句类似:
switch (test-string)
case pattern:
commands
breaksw
case pattern:
commands
breaksw
...
default:
commands
breaksw
endsw
breaksw将控制转移到endsw语句之后的语句。如果忽略了breaksw语句,控制将直接转移到下一条命令。用户可以在pattern中使用表11-2中列出的除管道符号(|)之外的所有特殊字符。
tcsh $ cat switch_l
#!/bin/tcsh
# Demonstration of a switch control structure.
# This routine tests the first command line argument
# for yes or no in any combination of uppercase and
# lowercase letters.
#
#
# test that argv[l] exists
if ($#argv != 1) then
echo "Usage: switch_l [yes|no]"
exit 1
else
# argv[l] exists, set upswitch based on its value
switch ($argv[l])
# case of YES
case [yY][eE][sS]:
echo "Argument one is yes."
breaksw
#
# case of NO
case [nN][oO];
echo "Argument one is no."
breaksw
#
# default case
default:
echo "Argument one is neither yes nor no."
breaksw
endsw
endif






