闇の光

读书笔记 经验感受

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

Command Execution

我们在写shell脚本时,总想要得到运行此脚本中的命令后的结果。也就是,我们想要命令运行之后能够将结果输出到一个变量中保存起来。而要达到此目的,我们可以用以前所提过的$(command)语法,当然还有与之相对应的旧的`command`模式,依旧是可以达到我们所要的效果。如果我们想要良好的可移植性,使用旧的`command`模式会是你最好的选择。相反,新的脚本写法中都提倡使用$(...)模式,这样可以避免字符$,`和\同时都在反引号内的复杂规则的发生。假使反引号被使用在`...`结构里,那么就必须在之前使用转义字符\,而这些相对难懂的字符必然会混淆程序员,甚至有时候即使是经验丰富的shell程序员也不得不在被反引号所封装的命令中尝试获得正确的引用。

$(command)的结果是来自于command的输出,不过要注意到,这不是command的返回状态,而是command的字符串输出,举例如下:

#!/bin/sh

echo The current directory is $PWD
echo The current users are $(who)

exit 0

因为当前目录是一个shell系统变量,所以脚本的第一行不需要使用命令执行构造。而下面who命令的结果,在此脚本中如果想要有效运行的话,则需要使用命令执行构造。如果你还想将它的结果保存在一个变量中的话,只需将之赋值给变量即可,如下所示:

whoisthere=$(who)
echo $whoisthere

将一个命令的结果保存在一个shell变量中是十分有用的,它可以轻易地得到运行脚本中存在的命令之后的结果。如果你曾经想过要转换一个命令在标注输出中所输出的一系列参数,并抓获这些参数作为一个程序的参数的话,也许你会发现可以使用xargs命令来达到此效果。若想更深入的了解,请阅读相关的手册指南。

另外,有时候当我们想要调用一个命令的输出,但该输出前有一些空格,或者这样说,输出超出了你所预想的要求。这这种情况下,我们可以使用set命令。

Arithmetic Expansion

我们使用expr命令来运行一些简单的算术运算,但当一个新的shell来调用expr命令时,所运行的状况实在是够慢的。因此,一个更新更好的替代出现了,它就是$((...))扩展。将你想要估算的表达式放入$((...))扩展中,就可以更高效地运行此算术运算。示例如下:

#!/bin/sh

x=0
while [ "$x" -ne 10 ]; do
    echo $x
    x=$(($x+1))
done

exit 0

注意这同x=$(...)完全不同。双括号被用来替换做算术运算,而单括号则是先前所讲的用来运行命令并捕获相关的输出。

Parameter Expansion

你应该已经看过有关于参数赋值以及参数扩展的最简单的形式,例如:

foo=fred
echo $foo

但是当你想要在一个变量的结尾处,扩展额外的字符时会有一个问题会发生。假设你想要写一个简短的脚本来运行名叫1_tmp和2_tmp的文件,有可能你会这样写:

#!/bin/sh

for i in 1 2
do
    my_secrect_process $i_tmp
done

但是在每次循环中,你将会得到如下提示:

my_secret_process: too few arguments

为什么会出现这样的错误呢?

原来这个问题就是当shell尝试去替代变量$i_tmp的值时,才发现所要替代的i根本不存在。但shell并不认为这是一个错误,只是没有替代任何东西而已,因此也就没有任何参数被传递到my_secret_process。所以为了扩展这个变量中的$i,你就需要将i用花括号像下面这样给封装起来:

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

这样处理之后,在每次循环时,i的值才能够被用来替换${i},从而得到我们所想要的文件名。所以,我们现在已经知道怎么样来替换当参数是一个字符串的一部分时参数的值。

我们可以在shell中执行很多种的参数替代,而这些参数替代又提供了一种很高明的解决方案来处理许多关于参数载入方面的问题。下面的表格为我们展示了几种普通的参数替代:

Parameter ExpansionDescription
${param:-default}如果param为空,则设置其为default的值
${#param}给出param的长度
${param%word}从结尾开始,移除param中匹配word最小的部分并将剩余的输出
${param%%word}从结尾开始,移除param中匹配word最长的部分并将剩余的输出
${param#word}从开头开始,移除param中匹配word最小的部分并将剩余的输出
${param##word}从开头开始,移除param中匹配word最长的部分并将剩余的输出

这些替代在你使用字符串时是很有用的。接下来的实例将展示四个字符串所移除的部分,这些字符串是特别用来载入文件名以及路径。实例如下:
下面脚本的每一部分都阐明了参数匹配运算符。

#!/bin/sh

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},得到的结果是bar,这是因为当此指令在执行时foo是没有任何值的。因此变量foo的值没有改变,依旧保持unset。

在此,我们还有一些需要注意的情况:

${foo:=bar}将设置变量到$foo,此字符串操作符将查看foo是否存在并且非空。如果它为空,则返回它的值,否则就设置foo为bar,并返回此替代。
${foo:?bar}将输出foo: bar,并且如果foo不存在或者被设置为空的话就中止此命令。
最后,${foo:+bar}在foo存在并非空的情况下将返回bar。

指令{bar#*/}匹配并只移除左边的/(*表示匹配零个或多个字符)。而{foo##*/}则匹配并移除尽可能多的/,因此它移除了最右边的/以及在/前面的字符。

指令{bar%local*}从右边开始匹配字符直到出现第一个local被匹配到,而{bar%%local*}则是从右边开始尽可能的匹配字符直到它发现最左边的local。

自从UNIX和Linux都严重的依赖于过滤器的观念,所以一个运算的结果都必须手动地设置重定向。比如说,你使用cjpeg程序想要将一个GIF文件转换为JPEG文件:

$ cjpeg image.gif > image.jpg

有时你可能会想将这种运算方式应用于大量的文件上,那我们怎样让它自动地重定向?我们只需这样简单地处理:

#!/bin/sh

for image in *.gif
do
    cjpeg $image > ${image%%gif}jpg
done

这个脚本giftojpeg将为每个GIF文件在当前目录下创建一个JPEG文件。

posted on 2008-03-15 23:14  taizi  阅读(278)  评论(0编辑  收藏  举报