shell程序设计(三)

  • Post author:
  • Post category:其他




shell脚本程序内部执行的两类命令:


1、可以在命令提示符中执行的“普通”命令,也称为外部命令(external command)


2、“内置”命令,也称为内部命令(internalcommand)



内置命令是在shell内部实现的,不能作为外部程序被调用,然而大多数的内部命令同时也提供了独立运行的程序版本(POSIX规范的需求)。通常情况下,内部命令的执行效率更高。




【命令】



1、break命令




在控制条件未满足之前,跳出for、while或until循环,可以为break命令提供一个额外的数值参数来表明需要跳出的循环层数,在默认情况下,break只跳出一层循环。


#!/bin/bash
rm –rf fred*
echo > fred1
echo > fred2
mkdir fred3
echo > fred4
 
for file in fred*
do
if [ -d “$file” ]
then
  break
fi
done
echo “first directory starting fred was$file”
rm –rf fred*
exit 0






2、:命令


冒号(:)命令是一个空命令,可被用于简化条件逻辑,相当于true的一个别名,由于是内置命令,所以运行的比true快。


while :实现了一个无限循环,代替了更常见的whiletrue。


:结构用在变量的条件设置中(暂不明):


: ${var:=value}



例子:


#!/bin/bash
rm –f fred
if [ -f fred]
then
  :
else
  echo ‘file fred did not exist’
fi
exit 0



3、continue命令


使for、while或until循环跳到下一次循环继续执行,循环变量取循环列表中的下一个值。


#!/bin/bash
rm –rf fred*
echo > fred1
echo > fred2
mkdir fred3
echo > fred4
for file in file*
do
if [ -d “$file” ]
then
  echo “skipping directory $file”
  continue
fi
echo “file is $file”
done
rm –rf fred*
exit 0



continue可以带一个可选的参数以表示希望继续执行的循环嵌套层数:


for x in 1 2 3
do
  echo before $x
  continue 1
  echo after $x
done



输出:


before 1
before 2
before 3




4、.命令



点(.)命令用于在当前shell中执行命令:


. ./shell_script


通常,当一个脚本执行一条外部命令或脚本程序时,它会创建一个新的环境(一个子shell),命令将在这个新环境中执行,在命令执行完毕后,这个环境被丢弃,留下退出码返回给父shell。但外部的source命令和点命令(同义)在执行脚本程序中列出的命令时,使用的是调用该脚本程序的同一个shell。


因为在默认情况下,shell脚本程序会在一个新创建的环境中执行,所以脚本程序对环境变量所作的任何修改都会丢失。而点命令允许执行的脚本程序改变当前环境。当要把一个脚本当作“包裹器”来为后续执行的一些其他命令设置环境时,通常使用这个命令。


在shell脚本程序中,可以使用点命令将变量和函数定义结合进脚本程序。



使用环境设置文件设置不同的开发环境:


##classic_set
#!/bin/bash
version=classic
PATH=/usr/local/old_bin:/usr/bin:/bin:.
PS1=’classic> ’
##latest_set
#!/bin/bash
version=latest
PATH=/usr/local/new_bin:/usr/bin:/bin:.
PS1=’latest version> ’



使用:


$ . ./classic_set
classic> echo $version
classic
classic> . /latest_set
latest version> echo $version
latest
latest version>


脚本程序使用点命令执行,所以每个脚本程序都是在当前shell中执行。这使得脚本程序可以改变当前shell中的环境设置,即使脚本程序执行结束后,改变仍然有效。




5、echo命令



去掉输出的换行符:


echo –n ‘string to output’



或者:


echo –e ‘string to output\c’


第二种方法echo –e确保启用了反斜线转义字符(\c——去掉换行符,\t——制表符,\n——回车)的解释。老版本bash默认启用,新版本默认不启用。



如果需要脚本兼容Unix系统并且需要删除换行符,最好使用printf命令。




6、eval命令


eval允许对参数进行求值,是shell的内置命令,通常不会以单独命令的形式存在。


foo=10
x=foo
y=’$’$x
echo $y



输出:


$foo



而:


foo=10
x=foo
eval y=’$’$x
echo $y



输出:


10


eval命令像一个额外的$,它给出一个变量的值的值,允许代码被随时生成和运行。




7、exec命令


exec的典型用法是将当前shell替换为一个不同的程序:


exec wall ‘paramters’


使用wall命令替换当前的shell,exec命令后面的代码都不会执行,因为执行这个脚本的shell已经不存在。


exec第二种用法是修改当前文件描述符:


exec 3< afile


这使得文件描述符3被打开以便从文件afile中读取数据(用法少见)。




8、exit n命令


exit命令使脚本程序以退出码n结束运行,在交互式命令提示符中使用则退出系统。如果脚本程序在退出时不指定一个退出状态,则脚本的最后一条被执行命令的状态将被用作返回值。


在shell脚本编程中,退出码0表示成功,退出码1~125是脚本程序可以使用的错误代码,其余数字具有保留含义。



退出码



说明


126


文件不可执行


127


命令未找到


128及以上


出现一个信号




.profile在当前目录下存在就返回0表示成功:


#!/bin/bash
if [ -f .profile ]
then
  exit 0
fi
exit 1



如果需要更简洁,则可以结合AND和OR列表进行重写:


[ -f .profile] && exit 0 || exit 1



9、export命令


export命令将作为它参数的变量导出到子shell中,并使之在后续的子shell中有效。在默认情况下,在一个shell中被创建的变量在这个shell调用的下级(子)shell中是不可用的。export命令把自己的参数创建为一个环境变量,而这个环境变量可以被当前程序调用的其他脚本和程序可见,技术上说,被导出的变量构成从该shell衍生的任何子进程的环境变量。



导出变量:


##export2
#!/bin/bash
echo “$foo”
echo “$bar”
##export1
foo=’The first meta-syntactic variable’
export bar=’The second meta-syntacticvariable’
bash export2



执行输出:


$ ./export1
 
The second meta-syntactic variable
$


一旦一个变量被shell导出,它就可以被该shell调用的任何脚本使用,也可以被后续依次调用的任何shell使用。如果脚本export2调用了另一个脚本,bar的值对新脚本来说仍然有效。



set –a或set –allexport命令将导出它之后声明的所有变量。




10、expr命令


expr命令将它的参数当作一个表达式来求值。



简单数学运算:


x=`expr $x+1`


反引号(“)字符使x取值为命令expr $x+1的执行结果。



可以使用语法$()替换反引号:


x=$(expr $x+1)



expr可以完成很多表达式求值计算:



表达式求值



说明


expr1 | expr2


如果expr1非零,则等于expr1,否则等于expr2


expr1 & expr2


只要有一个表达式为零,则等于零,否则等于expr1


expr1 = expr2


等于


expr1 > expr2


大于


expr1 >= expr2


大于等于


expr1 < expr2


小于


expr1 <= expr2


小于等于


expr1 != expr2


不等于


expr1 + expr2


加法


expr1 – expr2


减法


expr1 * expr2


乘法


expr1 / expr2


整除


expr1 % expr2


取余



在较新的脚本程序中,expr命令通常被替换为更有效的$((…))语法。




11、printf命令


只有新版本的shell才提供printf命令。


printf “format string” parameter1parameter2 …


printf命令不支持浮点数,因为shell中所有的算术运算都是按照整数来进行计算的,格式字符串由各种可打印字符、转义序列和字符转换限定符组成,除了%和\之外的所有字符都将按原样输出。




12、return命令


使函数返回,如果有数值参数,则在调用该函数的脚本程序里被看做是函数返回值,如果没有指定参数,return默认返回最后一条命令的退出码。




13、set命令


为shell设置参数变量,因为许多命令的输出结果是以空格分隔的值,使用set便于使用结果中的某个域。



获取当前月份:


#!/bin/bash
echo the date is $(date)
set $(date)
echo The month is $2
exit 0


把date命令的输出设置为参数列表,通过位置参数$2获取月份。


date命令输出受本地语言影响,可以使用date+%B来提取月份名字。


可以通过set命令和其参数来控制shell的执行方式,比如set –x允许脚本程序跟踪显示当前执行的命令。




14、shift命令


把所有参数变量左移一个位置($2变成$1,$3变成$2……,其中原来$1值被丢弃,$0任然保持不变)。


如果调用shift命令时指定数值参数,则所有的参数将左移指定次数,左移过程中,$*、$@和$#等其他变量也将根据参数变量的新安排做相应的变动。



扫描所有位置参数:


#!/bin/bash
while [ “$1” != ‘’ ]
do
  echo “$1”
  shift
done
exit 0




15、trap命令


用于指定在接收到信号后将要采取的行动,一种常见用途是在脚本程序被中断时完成清理工作。其中,信号是指那些被异步发送到一个程序的事件。


新版的bash中,使用信号名时SIG前缀是可选的,可以在命令提示符下输入命令trap –l来查看信号编号及其关联的名称。


trap命令有两个参数:第一个参数是接收到指定信号时将要采取的行动,第二个参数是要处理的信号名:


trap command signal


如果要重置某个信号的处理方式到其默认值,只需将command设置为-。如果要忽略某个信号,就把command设置为空字符串’’。一个不带参数的trap命令将列出当前设置的信号及其行动的清单。




信号



说明


HUP(1)


挂起,通常因终端掉线或用户退出而引发


INT(2)


中断,通常因按下Ctrl+C组合键而引发


QUIT(3)


退出,通常因按下Ctrl+\组合键而引发


ABRT(6)


中止,通常因某些严重的执行错误而引发


ALRM(14)


报警,通常用来处理超时


TERM(15)


终止,通常在系统关机时发送




信号处理:


#!/bin/bash
trap “rm –f /tmp/my_tmp_file_$$” SIGINT
echo “creating file /tmp/my_tmp_file_$$”
date > /tmp/my_tmp_file_$$
echo ‘press interrupt (CTRL-C) to interrupt…’
while [ -f /tmp/my_tmp_file_$$ ]
do
  echo ‘File exists’
  sleep 1
done
echo ‘The file no longer exists’
trap SIGINT
echo “creating file /tmp/my_tmp_file_$$”
date > /tmp/my_tmp_file_$$
echo ‘press interrupt (CTRL-C) to interrupt…’
while [ -f /tmp/my_tmp_file_$$ ]
do
  echo ‘File exists’
  sleep 1
done
echo ‘we never get here’
exit 0


第一个循环正常退出,第二个循环因指定INT信号出现时不执行任何命令而采取默认处理方式,即立即终止脚本程序。因为脚本程序被立即终止了,所以最后的echo和exit语句永远不会被执行。




16、unset命令


从环境中删除变量或函数,但是不能删除shell本身定义的只读变量(如IFS)。


#!/bin/bash
foo=’Hello World’
echo $foo
unset foo
echo $foo


第一次输出字符串,第二次输出换行符。




17、find命令


用于搜索文件,有选项、测试和动作类型的参数,其中一个参数的处理结果可能会影响后续参数的处理。



在本地机器上查找名为test的文件:


find / -name test –print


从根目录开始查找名为test的文件,并且输出文件的完整路径。



指定-mount选项,告诉find命令不要搜索挂载的其他文件系统的目录:


find / -mount –name test –print



find命令的语法格式:


find [path] [options] [tests] [actions]


path代表路径,可以使用绝对路径,也可以使用相对路径。




选项



含义


-depth


在查看目录本身之前先搜索目录的内容


-follow


跟随符号链接


-maxdepths N


最多搜索N层目录


-mount(或-xdev)


不搜索其他文件系统中的目录



可以提供给find命令的测试非常多,每种测试返回true或false。find命令开始工作时,它按照顺序将定义的每种测试依次应用到它搜索到的每个文件上,如果一个测试返回false,find命令就停止处理它当前找到的这个文件,并继续搜索,如果一个测试返回true,find命令将继续下一个测试或对当前文件采取行动。



测试



含义


-atime N


文件N天之前被最后访问过


-mtime N


文件N天之前被最后修改过


-name pattern


文件名(不包括路径名)匹配提供的模式pattern,为了确保pattern被传递给find命令而不是由shell来处理,pattern必须总是用引号括起


-newer otherfile


文件比otherfile文件要新


-type c


文件类型为c,c是一个特殊类型,最常见的是d和f。


-user username


文件的拥有者是指定的用户username



可以用操作符来组合测试。



短格式操作符



长格式操作符



含义


!


-not


测试取反


-a


-and


两个测试都必须为真


-o


-or


两个测试有一个必须为真



可以通过使用圆括号来强制测试操作符的优先级,由于圆括号对shell来说有特殊含义,所以必须使用反斜线来引用圆括号。



测试搜索的文件比文件X要新,或者文件名以下划线开头:


\( -newer X –o –name “_*”\)



在当前目录下搜索比文件while2要新的文件:


$ find . –newer while2 –print



结果中包括了当前目录,如果只需普通文件则增加额外测试:


$ find . –newer while2 –type f –print



查找以下划线开头的文件或比while2文件要新的普通文件:


$ find . \( -name “_*” –o –newer while2 \) –typef –print


-exec和-ok命令将命令行上后续的参数作为参数的一部分,直到被\;序列终止,魔术字符串{}是-exec或-ok命令的一个特殊类型的参数,它将被当前文件的完整路径取代。



动作



含义


-exec command


执行一条命令,动作必须使用\;字符对来结束


-ok command


与-exec类似,但在执行命令之前会针对每个要处理的文件,提示用户进行确认


-print


打印文件名


-ls


对当前文件使用命令ls –dils




例子:


$ find . –newer while2 –type f –exec ls –l{} \;



18、grep命令


grep,通用正则表达式解析器(GeneralRegular Expression Parser)。可以使用find命令在系统中搜索文件,而使用grep命令在文件中搜索字符串。一种常见的用法是在使用find命令时,将grep作为传递给-exec的一条命令。


grep命令使用选项、一个要匹配的模式和要搜索的文件:


grep [options] PATTERN [FILES]


如果没有提供文件名,则grep命令将搜索标准输入。



选项



含义


-c


输出匹配行的数目,而不是输出匹配的行


-E


启用扩展表达式


-h


取消每个输出行的普通前缀,即匹配查询模式的文件名


-i


忽略大小写


-l


只列出包含匹配行的文件名,而不输出真正的匹配行


-v


对匹配模式取反,即搜索不匹配行而不是匹配行




例子:


$ grep in words.txt
$ grep –c in words.txt words2.txt
$ grep –c –v in words.txt words2.txt


第一个例子文件名未输出是因为只在一个文件中进行搜索。




19、正则表达式


被广泛应用于Linux和许多其他开源编程语言中。


在方括号中使用的特殊匹配模式:[:alnum:]、[:alpha:]、[:ascii:]、[:blank:]、[:cntrl:]、[:digit:]、[:graph:]、[:lower:]、[:print:]、[:punct:]、[:space:]、[:upper:]、[:xdigit:]。



查找以字母e结尾的行:


$ grep e$ words2.txt



查找以字母a结尾的单词:


$ grep a[[:blank:]] words2.txt



查找以Th开头的由3个字母组成的单词:


$ grep Th.[[:space:]] words2.txt



使用扩展grep模式来搜索只有10个字符长的全部由小写字母组成的单词:


$ grep –E [a-z]\{10\} words2.txt



【命令的执行】


执行一条命令,并把命令的输出放在一个变量中,可以使用$(command)语法来实现,也可以使用一种比较老的语法形式`command`。


所有的新脚本程序都应该使用$(…)形式,目的是为了避免在使用反引号执行命令时,处理其内部的$、`、\等字符所需要应用的相当复杂的规则。


$(command)的结果是命令的输出,不是命令的退出状态,而是字符串形式的输出结果。


#!/bin/bash
echo “The current directory is $PWD”
echo “The current users are $(who)”
exit 0


因为当前目录是一个shell环境变量,所以第一行不需使用这个命令执行结构。但是要获得who命令的输出结果,就需要使用这个结构。



把结果放在一个变量中:


whoisthere=$(who)
echo $whoisthere


如果需要把一条命令在标准输出上的输出转换为一组参数,并且将它们作为另一个程序的参数,需使用xargs命令(用法要参照手册)。




1、算术扩展


expr命令可以处理一些简单的算术命令,但执行起来相当慢,因为需要调用一个新的shell来处理expr命令。


更好的办法是使用$((…))扩展,把准备求值的表达式括在$((…))中能够更有效地完成简单的算术运算。


#!/bin/bash
x=0
while [ “$x” –ne 10 ]
do
  echo $x
  x=$(($x+1))
done
exit 0


这与x=$(…)命令不同,两对圆括号用于算术替换,一对圆括号用于命令的执行和获取输出。




2、参数扩展


处理名为1_tmp和2_tmp的两个文件:


#!/bin/bash
for i in 1 2
do
  my_secret_process $i_tmp
done


shell试图替换变量$i_tmp的值,而这个变量其实并不存在,shell并不会认为这是一个错误,仅仅会将它替换为一个空值,因此根本不会有参数被传递给处理函数。


必须把i放在花括号中:


#!/bin/bash
for i in 1 2
do
  my_secret_process ${i}_tmp
done


可以在shell中采用多种参数替换方法。



参数扩展



说明


${param:-default}


如果param为空,就将其设置为default的值


${#param}


给出param的长度


${param%word}


从param的尾部开始删除与word匹配的最小部分,然后返回剩余部分


${param%%word}


从param的尾部开始删除与word匹配的最长部分,然后返回剩余部分


${param#word}


从param的头部开始删除与word匹配的最小部分,然后返回剩余部分


${param##word}


从param的头部开始删除与word匹配的最长部分,然后返回剩余部分




参数的处理:


#!/bin/bash
unset foo
echo ${foo:-bar}
foo=fud
echo ${foo:-bar}
foo=/usr/bin/X11/startx
echo ${foo#*/}
echo ${foo##*/}
bar=/usr/local/etc/local/networks
echo ${bar%local*}
echo ${bar%%local*}
exit 0



输出:


bar
fud
usr/bin/X11/startx
startx
/usr/local/etc/
/usr/



如果语句是${foo:=bar},那么变量$foo会被赋值,它检查变量foo是否存在且不为空,如果它不为空,就返回它的值,否则就把变量foo赋值为bar并返回那个值。


${foo:?bar}语句将在变量foo不存在或它设置为空的情况下,输出foo:bar并异常终止脚本程序,最后${foo:+bar}语句将在变量foo存在且不为空的情况下返回bar。



因为Unix和Linux系统都非常依赖过滤器的思想,所以一个操作的结果常常必须手工重定向。



使用cjpeg程序将一个GIF文件转换为一个JPEG文件:


$ cjpeg image.gif > image.jpg



对大量文件执行自动重定向:


#!/bin/bash
for image in *.gif
do
  cjpeg $image > ${image%%gif}jpg
done


脚本为当前目录中的每个GIF文件创建一个对应的JPEG文件。




【here文档】


here文档允许一条命令在获得输入数据时就好像在读取一个文件或键盘一样,而实际上是从脚本程序中得到输入数据。


here文档以两个连续的小于号<<开始,紧跟着一个特殊的字符序列,该序列将在文档的结尾处再次出现。<<是shell的标签重定向符,这里它强制命令的输入是一个here文档。这个特殊字符序列的作用就像一个标记,它告诉shell here文档结束的位置。因为这个标记序列不能出现在传递给命令的文档内容中,所以应该尽量使它既容易记忆又相当不寻常。



给cat命令提供输入数据:


#!/bin/bash
cat <<!FUNKY!
hello
this is a here
document
!FUNKY!
exit 0


<<与!之间的空格可选。


here文档功能可以用来调用交互式的程序,比如一个编辑器,并向其提供一些事先定义好的输入。


更常见的用途是在脚本程序中输出大量的文本(如上述例子),从而可以避免用echo语句来输出每一行,可以在标识符两端都使用感叹号(!)来确保不会引起混淆。


如果想按预定的方式处理一个文件中的几行,可以使用ed行编辑器,并在脚本程序中通过here文档向其提供命令。



假设a_text_file文件内容为:


That is line 1
That is line 2
That is line 3
That is line 4



结合here文档和ed编辑器来编辑:


#!/bin/bash
ed a_text_file << !FunkyStuff!
3
d
.,\$s/is/was/
w
q
!FunkyStuff!
exit 0



文件内容变为:


That is line 1
That is line 2
That was line 4


脚本程序调用ed编辑器并向其传递命令,先让其移动到第三行,然后删除该行,再把当前行的is替换为was。




【调试脚本程序】


脚本程序的调试并没有特定的工具,出现错误时,shell一般都会打印出包含错误的行的行号。如果错误不是非常明显,可以添加一些额外的echo语句来显示变量的内容,也可以通过在shell中交互式地输入代码片段来对它们进行测试。


因为脚本程序是解释执行的,所以在脚本程序的修改和重试过程中没有编译方面的额外开支。跟踪脚本程序中复杂错误的主要方法是设置各种shell选项——可以在调用shell时加上命令行选项,或是使用set命令。



命令行选项



set选项



说明


sh –n <script>


set –o noexec


set –n


只检查语法错误,不执行命令


sh –v <script>


set –o verbose


set –v


在执行命令之前回显它们


sh –x <script>


set –o xtrace


set –x


在处理完命令之后回显它们


sh –u <script>


set –o nounset


set –u


如果使用了未定义的变量,就给出出错消息



可以使用-o选项启用set命令的选项标志,用+o选项取消设置,对简写版本也一样。


在shell中,还可以通过捕获EXIT信号,从而在脚本程序退出时查看到程序的状态:


trap ‘echo Exiting: critical variable =$critical_variable’ EXIT