shell历险之——引用的迷途 - Shell - ChinaUnix.net

来源:百度文库 编辑:神马文学网 时间:2024/04/29 01:25:12
shell历险之——引用的迷途
上篇:引用,奇怪的反斜线

我们知道在shell中有两类字符,一类是普通字符(literal),在shell中没有任何特殊意义;另一类是所谓“元字符”(meta),在shell中有特殊的含义或用法。

当我们需要去掉元字符的特殊含义而恢复其字面意义时就必须使用“引用”(quoting)。通常有三种引用方式,他们是转义(Escape,使用反斜杠字符\,即backslash),强引用(使用单引号',即single quote)和弱引用(使用双引号", 即double quote)。

转义:是用反斜杠放在需要转义的一个字符前,表示那个字符要看作一个普通字符。
强引用:是用单引号把要转义的字符串括起来,其中任何字符都看作普通字符,除了单引号自身。所以你无法在两个单引号之间包含单引号,用\转义也不行。
弱引用:是用双引号把要转义的字符串括起来,除了双引号"本身,其中的大部分字符都看作普通字符。例外的还有\,$,`三个特殊字符。因为\在""中是特殊字符,所以你可以在其中包含"本身,前提是必须转义。$是特殊字符,这表示你可以使用变量$var/${var}及其它,在ksh/bash中可以引用算术表达式的结果$((...)),还可以作命令替换$()。由于`是特殊字符在bsh中也可以作命令替换,但只能使用`...`的语法(这个在ksh/bash中也可以使用)。

关于三种引用,网中人的“shell十三问”之第四问已经讲得很清楚了,我这里就不重复了。

今天我们单独研究一下反斜线\。这是一个有魔力的字符,它可以用来对任何字符转义,也包括它自己。但是在不同的shell实现中它的表现似乎不尽相同,有时结果让你会大吃一惊。

先来看一个简单的例子,假定我们要输出单独一个\,先用bash:

[Copy to clipboard] [ - ]CODE:$ echo \\
\
$ echo "\\"
\
$ echo '\'
\
第一个echo,因为\是元字符,所以必须对它进行转义,所以我们必须用两个\。
第二个echo,因为""是弱引用,其中的\仍然是特殊字符,所以同样必须转义。
第三个echo,''是强引用,\在单引号之中是普通字符,这样就不用再转义了,所以只用一个\。

OK,假如我们要输出连续两个\,怎样呢?看一下:

[Copy to clipboard] [ - ]CODE:$ echo \\\\
\\
$ echo "\\\\"
\\
$ echo '\\'
\\
嗯,好像没有什么奇怪的事发生,我们简单地使用双倍的\就搞定了。

那再让我们用ksh来试试来做同样的事:

[Copy to clipboard] [ - ]CODE:$ echo \\
\
$ echo "\\"
\
$ echo '\'
\
这个容易,简单的重复罢了。再来两个\:

[Copy to clipboard] [ - ]CODE:$ echo \\\\
\
$ echo "\\\\"
\
$ echo '\\'
\
等等,怎么搞的?为什么只输出了一个\?
再加一个\会如何?

[Copy to clipboard] [ - ]CODE:$ echo \\\\\
>
\
$ echo \\\\\\
\\
$ echo "\\\\\"
>
\
$ echo "\\\\\\"
\\
$ echo '\\'
\\
第一个echo没有立即执行,ksh给出了一个>(PS2提示符),等待我们继续输入,回车后echo仍然只输出了一个\。
第二个echo后面是六个\,呼~,这回终于输出两个\了。
双引号的情形也是类似,我们仍然需要六个\。
第五个echo,好在单引号的结果还算不错,只要两个\就行了。

你可以继续做这个试验,最后会发现在不用单引号时,我们需要2个或4个\输出1个\,需要6个或8个\输出2个\,10个或12个\来输出3个\,......真是又臭又长!

[Copy to clipboard] [ - ]CODE:输出        命令行需要\的个数
\        2/4
\\       6/8
\\\      10/12
\\\\     14/16
\\\\\   18/20
...       ...
n个\    4n-2/4n
厌倦了吗?其实ksh下除了用',还有其它方法剪断这“懒婆娘的裹脚布”。窍门就在于echo命令的-E选项:

[Copy to clipboard] [ - ]CODE:$ echo -E \\
\
$ echo -E \\\\
\\
$ echo -E \\\\\\
\\\
很好!这样与bash下的情况就一样了,-E选项是不是很神奇呀!为什么呢?

原来bash和ksh的echo命令缺省的表现是不同的。我们知道echo命令可以接受一些转义字符序列来表示特殊的字符,如\n表示换行,\a表示蜂鸣,\t表示水平制表符等等。显然在解释转义序列时\是一个特殊字符。

在bash下echo缺省的设置是不解释这些转义序列,为了告诉它解释转义序列我们必须使用-e选项。

而在ksh下echo的缺省设置就会解释这些转义序列,我们可以用-E选项让它不解释转义序列。所以ksh下在不使用-E选项时实际上会发生两次转义的过程,第一次发生在ksh处理命令行时,第二次发生在echo命令处理它的参数时。让我们看下面这个简单的例子:

[Copy to clipboard] [ - ]CODE:$ set -x
$ echo \\\\
+ echo \\
\
先用set -x让ksh显示命令行处理的结果,我们看到,第一个\转义第二个\,去掉它的特殊含义,同样第三个\转义第四个\去掉它的特殊含义,这样命令行处理完毕以后传给echo的参数是\\。echo然后将\\解释成了\并输出。于是我们只得到一个\。

bsh中echo的缺省表现与ksh中类似,解释转义序列,不过可惜无法关掉这一功能。:(我们还是可以使用外部命令来不解释转义序列原样输出,例如在linux下可以用
/bin/echo -E(可以省略)来做。

下来看一个从本论坛来的例子,提问者的意图是将dos格式的路径中的\变成\\,但他的shell好像工作得不好:

[Copy to clipboard] [ - ]CODE:$echo C:\\tmp | sed 's/\\/&\\/'
C:        mp

$echo 'C:\abc'|sed 's/\\/\\\\/'
C:bc
在看过上面我们对引用和echo的讨论之后,您能为他解释一下其中的原因吗?
对了,这两条命令的语法都没有错,不过提问者使用的shell八成是ksh(也可能是bsh,但可能性较小),问题是出在echo上。在ksh下echo默认解释转义序列,所以命令行的
echo c:\\tmp部分先做命令行解释,\\变成\,于是执行:
echo 'c:\tmp'
而\t是一个转义序列,它代表水平制表符,所以echo最后输出
c:<水平制表符>mp
同样
echo 'c:\abc'
会输出
c:<蜂鸣字符>bc
你应该会听到你的终端发出“嘟”的一声。
这下清楚了吧?echo的输出就是错误的,后面sed的替换根本就没有匹配执行,当然不会有正确的结果。
上面两个命令在bash下是正确的,为什么?
那在ksh下怎样修改让它正确工作呢?如果是bsh呢?改法可能不止一种,不过这个还是留给亲爱的读者您作为一个练习好了。