使用 echo 命令
shell 都内置 echo 命令。如果你想看看 shell 如何解释元字符(meta-characters),在命令的前面加上“echo”。
例如,你想知道键入下面这一行命令会发生什么
我建议你先键入
看看结果如何。
shell 认为“*”是一个特殊字符,shell 看到“*”的时候,会更改“*”来匹配当前目录中的文件,然后调用“ls”命令,并传输这些参数给“ls”。“ls”命令永远看不到“*”字符。
假设你的目录中有一个文件名中带有“*”,你如何列出或删除该文件?你必须引用 “*”才能将它传送给 shell。
你可以使用三种引用机制(quoting mechanisms),它们分别是单引号(single quote)、双引号(double quote)和反斜杠(backslash)。
用反斜杠引用单个字符
你可以在字符前面放置一个反斜杠(“\”)来防止 shell 解释该字符。
你可以使用下面这条命令列出带有“*”的文件:
下面是一个 shell 脚本,它可以删除任何包含星号的文件:
echo contain an asterisk in the name
echo This script removes all files that
echo
echo Are you sure you want to remove these files\?
rm -i *\**
在问号的前面也需要反斜杠,因为它也是一个 shell 元字符。没有反斜杠,shell 将查找与模式“files?”匹配的所有文件。如果有文件“files1”和“files2”,脚本将打印出来
a very \
long line\!
This could be a very long line!
%
反斜杠会转义(escape)或引用行尾字符(end of line character),迫使它不再具有特殊含义。在上面的例子中,我还在感叹号前加了一个反斜杠。如果你使用的是将 “!” 当作一个特殊符号的 C shell,那么这么做是必需的。如果你使用的是其他 shell,则可能没有必要这么做。
单引号格式的强引用
当你需要一次引用多个字符时,你可以使用多个反斜杠:
% echo a\ \ \ \ \ \ \ b
('a' 和 'b' 之间有 7 个空格。)这个命令很丑陋但有效。使用成对的引号来指示要引用的字符的开始和结束会更容易:
% echo 'a b'
在单引号内,你可以包含几乎所有元字符:
% echo 'What the *heck* is a $ doing here???'
What the *heck* is a $ doing here???
上面的示例使用了星号、美元符号和问号元字符。当您希望文本保持原样时,应使用单引号。注意——如果你使用的是 C shell,则“!”字符之前可能需要一个反斜杠。这取决于它旁边的字符。如果它被空格包围,则不需要使用反斜杠。
双引号格式的弱引用
有时你需要一种较弱的引用类型:一种不扩展元字符(如“*”或“?”)但扩展变量并进行命令替换的引用。这可以用双引号来完成:
% echo "Is your home directory $HOME?"
Is your home directory /home/barnett?
% echo "Your current directory is `pwd`"
Your current directory is /home/barnett
# This next example won't work in the C shell and Bourne shell
% echo "Your current directory is $(pwd)"
Your current directory is /home/barnett
一旦了解了单引号和双引号之间的区别,你将掌握一项非常有用的技能。这并不难。单引号比双引号强。知道了吗?好的。反斜杠是最强的。
在文件名中包含空格和字符时使用引号
如果要处理文件名中包含空格或特殊字符的文件,则可能必须使用引号。例如,如果你想创建一个名称中有空格的文件,你可以使用以下方法:
% cp /dev/null 'a file with spaces in the name'
通常,shell 使用空格来确定每个参数的结尾。引用改变了这一点,上面的例子只有两个参数。你还可以在字符前使用反斜杠。下面的例子重命名文件名带有空格的文件,将空格替换为下划线:
% mv a\ file a_file
使用相同的技巧,你可以处理文件名中的任何字符:
% mv a 'a?'
在最坏的情况下,有空格的文件名中会增加它用作参数的难度。但是,在文件名中使用其他字符则非常危险。
一个例子是文件名以连字符(hyphen)作为文件名的第一个字符。如果你有一个名为“-i”的文件并且你想删除它,下面的命令
% rm "-i"
将不起作用,因为“rm”命令将“-”解释为元字符——并假定参数是一个选项。解决方法是使用真实的 Unix 路径,以及“.”是当前目录的路径。因此,解决办法是键入:
% rm "./-i"
当在文件名中有“?”和“*”时会出现其他问题。如果要删除文件“a?”你最终可能会删除多个文件。我强烈建议你养成使用 “echo”检查有元字符时将要发生什么的习惯。
多重引用
虽然有两种类型的引用(如果算上反斜杠,则为三种)可能看起来令人困惑,实际上它提供了几种解决相同问题的方法。你可以将一种引用放在另一种引用内。如果要使用单引号,请在它的外面使用双引号。要使用双引号,请在它的外面使用单引号。使用例子更容易演示这一点:
$ echo Don't do that
> '
Dont do that
% echo "Don't do that"
Don't do that
% echo 'The quote of the day is: "TGIF"'
The quote of the day is: "TGIF"
%
找出引用是否错误
在某些情况下,在不确定时你可能需要使用反斜杠。在其他情况下,反斜杠也会出差错。 你怎么知道你引用的东西是否正确?答案:使用 shell。
检查引用的一种简单方法是在命令的前面添加一个“echo”,这样你就可以看到发生了什么,或者将一个“ls”命令更改为一个“echo”命令:
echo scp gateway:\*.tar.Z .
ssh user@cruncher echo ls \*
ssh user@cruncher echo 'ls *'
如果你想在远程机器上做文件重定向,echo 是不够的。当你希望命令回显到你的终端时,下面的命令
ssh user@cruncher echo 'ls * >/tmp/file'
将回显结果发送到文件。你需要嵌套引号:
ssh user@cruncher "echo 'ls * >/tmp/file'"
ssh user@cruncher 'echo "ls * >/tmp/file"'
ssh user@cruncher "echo 'cd newdir;ls * >>/tmp/file'"
如果你正在调试 shell 脚本,并且想查看你的脚本在做什么,你可以复制脚本中的重要行之一,并在其中一个重复项的前面插入一个“echo”。在脚本中执行此操作一两次并不是很困难,但有时你会想要查看脚本的每一行。在这种情况下,只需要求 shell 显示你想要的内容。
Posix/Bourne Shell 变量
POSIX 和 Bourne shell 有两个变量,设置后,将帮助你跟踪复杂的变量和元字符扩展。你可以在执行脚本时通过键入以下命令启用对 POSIX 和 Bourne shell 脚本的变量扩展:
% sh -v script
% sh -x script
你还可以在脚本中打开和关闭此功能。要打开 verbose 标志,请使用
set -v
要打开 echo 变量,请使用:
set -x
如果你想关闭这些变量,请使用加号而不是减号:
set +x
set +v
在引号内包含相同的引号
人们遇到的一个问题是在引号中包含相同的引号。许多人期望下面的方法起作用:
echo "The word for today is \"TGIF\""
echo 'Don\'t quote me'
第一个示例适用于 Bourne shell,但不适用于 C shell。第二个例子对它们都不起作用。我敢打赌你们中的许多程序员对此感到困惑。我曾是。通常,我们描述一个字符串,开头是“字符串的开头”,结尾是“字符串的结尾”。这两者之间是字符串。简单的。当你使用 shell 时,这有几分是正确的。与大多数编程语言不同,引号可以打开和关闭替换。它们不用于指示字符串的开始和结束。考虑下面这组引号:
echo 'a'b'c'
这被拆分为三个单元。第一个和最后一个被引用,中间没有。引用和替换发生后,三个单元被合并。中间部分不被引用(即允许被视为元字符),并且可以是一个变量,例如,我们可以将“b”更改为“$HOME”:
echo 'a'$HOME'b'
这种技术是将 shell 变量放入 awk 脚本的典型方法。下面是一个简单的 shell 脚本来演示这一点。请研究这个,因为它很重要:
#!/bin/sh
# this is a shell script that acts like a filter,
# but in only prints out one column.
# the value of the column is the argument
# to the script
#
# uncomment the next line to see how this works
#set -x
#
# example:
# printcol 1
# printcol 3
# the value of the argument is $1
# Here comes the tricky part -
awk '{print $'$1'}'
# I told you!
在此示例中,shell 将 awk 的参数分成三部分:
{print $ | Quoted |
$1 | Evaluated |
} | Quoted |
你可以取消注释命令“set -x”并使用下面的命令来尝试脚本
printcol 2 </etc/hosts
shell 脚本的参数是 2,因此计算“$1”并返回值“2”。这使得 awk 的参数成为字符串“print {$2}”并且第二列被打印出来。
请注意,此技术适用于使用任一种类型引号的任何 shell。
当你想在引号中引用引号时,你必须了解这一点。事实上,你不想将引号放在引号中,而是想将多个单元组合或连接成一个参数。
让我换个说法。如果要在以单引号开头的参数中包含单引号,则必须关闭以单引号开头的机制,并使用不同的引用方法。请记住,反斜杠是所有引用机制中最强大的。你可以用反斜杠引用任何内容。此示例引用所有三个引号字符:
% echo \'\"\\
结果是这样的
'"\
你始终可以使用反斜杠来引用字符。但是,在单引号机制中,“\'”并不“引用引号”。正确的方法如下:
% echo 'Don' \' 't do that'
Don ' t do that
我放了一些额外的空格,这样你就可以了解发生了什么。这里没有多余的空格:
% echo 'Don'\''t do that'
Don't do that
当你在脑海中解析 shell 脚本时,请记住将引号匹配在一起。这也适用于双引号:
% echo "The quote for today is "\"TGIF\"
The quote for today is "TGIF"
或者,如果你想在“TGIF”周围加上引号
% echo "The quote for today is "\""TGIF"\"
The quote for today is "TGIF"
引用长行
大多数 Unix 程序也使用反斜杠来转义特殊字符。Unix 实用程序通常将行尾的反斜杠解释为续行符——也就是说,行尾字符被引号或转义,因此无法使用其标准含义。
当你引用超出行尾标记的行时,Bourne shell 和 C shell 的行为不同。C shell 不会将引号扩展到该行之外,除非最后一个字符是反斜杠:
% echo "A quote \
on two lines"
A quote
on two lines
Bourne shell 允许引号超出行:
$ echo "A quote
> on two lines"
A quote
on two lines
请注意 Bourne shell 如何在引用未结束时用“>”提示你。你们可以自己争论哪种行为是正确的。我可以理解 C shell 默认情况下不允许多行引用的原因。前面的例子:
echo 'Don\'t do that'
如果你使用 C shell,则会产生错误。但是,如果你使用 Bourne shell,你将得到一个“>”提示,并且该提示将一直持续到你键入单引号为止。这可能会让新的 Unix 用户感到困惑。
当我编写作为 awk 脚本的多行引号时,我发现 Bourne shell 更易于使用:
#!/bin/sh -x
#This script counts how many people
# are in the group specified as the first argument
grp=${1:?"Missing argument"} # get group ID number
# If missing, report an error and exit.
awk -F: '
# Awk script starts here
BEGIN {
# set total to zero
# before we start
total=0;
}
$3 ~ /^'$grp'$/ {total++;}
END {
# end of file, print total
printf("Total: %d\n", total);
}' </etc/passwd
此示例在 awk 脚本的中间使用了“$grp” shell 变量。这是将 shell 变量传递到 awk 脚本中间的常用方法。有些人更喜欢使用其他机制在字符串中使用动态变量。例如,awk 提供了另一种机制,但我鼓励你理解这种技术,因为它可以与其他实用程序一起使用。