下篇:命令替换 -- 另一种引用?
命令替换(command
substitution),是指在命令行获取另一个命令的标准输出,换句话说,它是将一个命令的标准输出代换到另一个命令的命令行。在bsh/ksh
/bash和csh中的语法都是`command`。注意这里的`是“反引号”(键盘上位于1键的左边)。例如:
/root#echo we are now in `pwd`
shell会将pwd命令的输出替换`pwd`,然后打印出we are now in /root
在ksh和bash中除了可以使用上面的语法外,又引入了一种新的语法,$(command)。于是上面的例子可以改写为下面等价的形式:
\root#echo we are now in $(pwd)
在ksh/bash环境下,使用$()显然比``优越,这是因为:
其一,前者更易读,不会产生歧义。而反引号`常常被初学者当成单引号';
其二,前者嵌套时更简单,直接使用就行。而后者嵌套时内部的反引号必须用\转义;
其三,它们对反斜杠\的处理不一样,在$()中可以减少转义的麻烦。而这一点与第二点是前因后果的关系。正是因为$()嵌套时不需转义,所以\在$()中就不需要作为一个特殊字符了。而``中的\必须是特殊字符,否则就无法嵌套使用了。
那么我们是否可以完全抛弃``的语法,只用$()就好了呢?这在大多数环境下是可行的,但如果过分强调这个的话,我认为还是太理想化了。事实上``还是有
它存在的理由的,因为它的兼容性最好,在bsh/ksh/bash/csh统统可以执行。在大部分的传统unix上是没有安装bash的,ksh是不是都
有安装我不能确定(100%正宗的ksh好像是专有软件),还有在一些嵌入式的linux或者高度裁剪的linux上会没有安装bash或者ksh。那么
在这些环境下进行shell编程时我们就可能不得不使用最古老的bourne shell语法,这时优秀的$()语法就可能完全派不上用场。
扯远了,回到我们的主题。考察命令替换的执行机制,``或$()里面的内容原则上应该作为一个整体传递,交给子shell执行。所以其中的大部分字符在传
递给子shell之前都是普通字符。那么实际上它们在本来命令替换的功能之外似乎也起到了一种引用的效果(也算是一种副产品吧),所以我们不妨把它看作上
篇里面讲到的三种引用之外的另一种引用形式。于是我们自然会问一个问题,在这两种“引用”中有没有例外的特殊字符呢?当然会有,据我目前所知(不全面的地
方欢迎大家指正):
``中的特殊字符有美元符$,反斜杠 \,双引号 ",单引号',还有自身`。
$()中的特殊字符有美元符$, 双引号"和单引号'。
既然命令替换可以看作是一种特殊的引用,我们不妨拿上篇里反斜杠的例子再来研究一下。假定我们要用两次echo,并且要做一次命令替换。先来看比较典型的ksh:
我们先来简单解释一下这个命令的处理过程。
第一步,shell处理整个命令行,由于``中的\是特殊字符,所以处理之后传给内部的echo的参数就变成了\\;
第二步,在子shell中执行echo \\,因为ksh的echo缺省处理转义序列,所以\在echo看来还是特殊字符,于是内部echo输出一个\;
第三步,将内部echo输出的\代入外部的echo的命令行,执行echo '\',最终输出一个\。
现在请大家猜一猜,ksh下为了要输出两个\,我们需要多少个\在命令行呢?大胆一点,使劲猜!
答案是20个(在bsh下也差不多,是18个)!
$ echo `echo \\\\\\\\\\\\\\\\\\\\`
\\
吃惊吗?你会说:“这太离谱了!这条裹脚布比前面那条长了几倍!”是的,太离谱了,难怪有人把\(英文为backslash)的自身冗长又似乎没有规律可循的转义叫作:“backslashit!”;-D。不过在抱怨过后还是让我们来看看有什么方法来简化一下吧。
ksh下:
第一件武器:强引用。防止在第一次命令行处理时解释\。
$ echo `echo '\\\\\\\\\\'`
\\
好刀!不错,10个\就够用了!
第二件武器:echo的-E选项。防止echo解释\。
$ echo -E `echo -E '\\\\'`
\\
连环双刀!只剩4个\了。
终极武器:$()。防止执行内部的echo时在子shell的命令行解释\。
$ echo -E $(echo -E '\\')
\\
枝枝杈杈砍精光,剩下两个对一双!这次终于达到最简了。
其他的shell情况如何呢?
bsh不支持$(...)的语法,`...`的情况与ksh类似,只是echo的处理与ksh稍有一点不同。
bash的情况类似,注意echo缺省不解释转移序列(等价于ksh的echo -E)。
tcsh不支持$(...)语法,`...`中情况也稍简单些:
$ echo $0
csh
$ echo `echo \\`
\
$echo `echo \\\\`
\\
$ echo `echo '\\'`
\\
tcsh与这个例子有关的引用方面的特点是:
单引号和双引号中的\为普通字符。
echo中\默认为普通字符。
``中\为特殊字符。
详细内容请参看csh的相关文档。
最后,让我们继续讨论上面那个dos路径的实际问题作为一个练习,为了叙述方便,对原问题略加改动:
#!/bin/ksh
dospath='c:\tmp'
escaped=`echo $dospath|sed 's/\\/&&/'`
echo $escaped
在ksh下执行上面的脚本报错:
sed:-e 表达式 #1,字符 7:unterminated `s' command
你能找出所有的错误吗?
根据出错信息,首先容易知道sed的命令行有问题。对了,哪里有问题?还是经常捣乱的\吧?又对了,怎样修改呢?
兼容性最好的语法:
escaped=`echo $dospath|sed 's/\\\\/&&/'`
OK,执行通过!
再来,最优雅的的语法:
escaped=$(echo $dospath|sed 's/\\/&&/')
很好!也通过。
重新执行脚本,输出:
还不对,那么哪里还有错误呢?嗯,对了,是echo的问题。如何修改?
escaped=`echo -E $dospath|sed 's/\\\\/&&/'`
不错。再次执行脚本,输出:
仍然不是我们想要的结果。哪里还有问题呢?对了,别忘了最后一个echo!
再次执行脚本,输出:
大功告成!
OK!通过这上下两篇关于引用和反斜杠\的讨论,相信您对shell的引用和转义有了更加深入的理解。让我们小结一下,帮助您强化记忆:
关于引用:
三种公认的引用方式:
强引用('...'),特殊字符:单引号'自身
弱引用("..."),特殊字符:双引号"自身,反斜杠\,美元符$,还有反引号`
转义(\.),没有例外,连\自身也可以被转义。
woodie自己加入的两种“类引用”方式(一家之言,姑妄言之,姑妄听之吧。欢迎拍砖!^_^):
``,bsh/ksh/bash/csh中可用,特殊字符:反引号自身`,美元符$,反斜杠\,还有单、双引号'和”。
$(),ksh/bash中可用,特殊字符有美元符$,还有单、双引号'和”。
关于echo:
bsh:解释转义字符序列,且不能关掉
ksh:缺省解释转义字符序列,可以用-E选项关掉
bash:缺省不解释转义字符序列,可以用-e选项打开
说明,我的测试环境:
Centos 4.2 x86-64
bash: 3.00.15(1)
ksh pdksh-5.2.14-30.3
bsh ash-0.3.8-20
csh tcsh-6.13-9
[
本帖最后由 woodie 于 2006-1-11 14:46 编辑 ]