单引号
在Shell
中需要使用引号的因素有很多。其中最常见的就是要使包含空白字符的字符序列成为一个整体。
如何将包含空白字符的参数传递给程序。解决方法就是将整个参数放到一对单引号中:
grep 'Susan Goldberg' phonebook
当Shell
看到第一个单引号时,它会忽略随后的所有特殊字符,直到碰到下一个与之匹配的的封闭单引号。
只要 Shell 碰到第一个'
,在遇到用于封闭的'
之前,它不会再解释任何特殊字符。因此,Susan和Goldberg之间通常用作分隔参数的空格就被Shell
忽略了。Shell然后将命令行分割成两个参数,第一个参数是Susan Goldberg
(其中包含了空格字符),另一个参数是 phonebook。然后调用grep
,为其传入这两个参数。注意,引号会被Shell
删除,并不会传入程序中。无论引号中有多少个空格,它们都会被Shell
所保留。
只要是出现在单引号中的特殊字符,Shell 会将其全部忽略。
$ echo '$file' $没有被 Shell 解释
file
$ echo '*'
*
如果在给变量赋值时,其中包含空白字符或其他特殊字符,也需要使用引号,例如:
$ message='I must say, this sure is fun'
$ echo $message
I must say, this sure is fun
$ text='* means all files in the directory'
$ echo $text
names nu numbers phonebook stat means all files in the directory
双引号
双引号的作用类似于单引号,除了它对于内容的保护要弱于后者:单引号告诉Shell
忽略引用的所有特殊字符,而双引号则忽略引用的大部分特殊字符。具体来说,下面的3个字符在双引号中不会被忽略:
- 美元符号
- 反引号
- 反斜线
美元符号不会被忽略意味着Shell
会在双引号中完成变量名替换。
如果你想获得变量被替换后的值,但是不希望 Shell 随后再解析替换后出现的特殊字符,可以将变量放进双括号里。例如:
$ address="39 East 12th Street
> New York, N. Y. 10003"
$ echo $address
39 East 12th Street New York, N. Y. 10003
$ echo "$address"
39 East 12th Street
New York, N. Y. 10003
反斜线
除了少数几处例外,反斜线(作为前缀使用)在功能上相当于在单个字符周围放置单引号。反斜线可以对紧随其后的字符进行转义。一般形式为:
\c
其中,c是你想要转义的字符。该字符的所有特殊含义都会被移除。下面是一个例子:
$ echo >
syntax error: 'newline or ;' unexpected
$ echo \>
>
在第一种用法中,Shell
看到了>
,认为你是想将echo
的输出重定向到文件中,因此它预计接下来应该是一个文件名。但是却没有,因此Shell
发出了错误信息。在接下来的用法中,反斜线去除了>
的特殊含义,因此传给echo
显示的只是一个普通的字符。
使用反斜线续行
当反斜线作为一行最后的一个字符时:
$ lines=one'
> 'two 单引号使得 Shell 忽略了换行符
$ echo "$lines"
one
two
$ lines=one\ 换用\
> two
$ echo "$lines"
onetwo
如果反斜线是输入行的最后一个字符,Shell
将其视为续行符。它会删除随后的换行符,也不会将该换行符作为参数分隔符(就好像这个字符从没出现过)。在输入跨多行的长命令时经常使用这种方法。
双引号中的反斜线
我们先前提到过反斜线是在双引号中会被Shell
解释的3个字符之一。这意味着你可以在双引号中使用反斜线来去除会被Shell
解释的那些字符的特殊含义(也就是说,包括其他的反斜线、美元符号、反引号、换行符以及其他的双引号)。如果反斜线出现在双引号中其他字符之前,Shell
会将该反斜线忽略,继续往后处理:
$ echo "\$x"
$x
$ echo "\ is the backslash character"
\ is the backslash character
$ x=5
$ echo "The value of x is \"$x\""
The value of x is "5"
命令替换
命令替换指的是 Shell 能够在命令行中的任意位置使用命令的输出来替换特定的命令。Shell
执行命令替换的方式有两种:将命令放在反引号
中或放在$(...)
中。
反引号
反引号(也经常称为反撇号
)不像之前碰到过的那些引号,因为它的目的不在于避免字符被Shell
解释,而是告诉Shell
将其中的命令使用命令输出代替。反引号的一般使用格式为:
`command`
其中,command
是待执行的命令名,命令输出会被插入到它的当前所在位置上。
这里是一个例子:
$ echo The date and time is: `date`
The date and time is: Wed Aug 28 14:28:43 EDT 2002
当Shell
扫描命令行时,它识别出了反引号
,于是期望接下来的是一个命令。在本例中,Shell
发现了date
命令,因此执行该命令并使用date
的输出替换命令行中的date
序列。接下来,Shell
将处理过的命令行分割成参数,然后全部传给echo
命令。
$(...)
结构
所有的现代 UNIX、Linux 以及其他 POSIX 兼容的 Shell 都支持一种更新、更可取的命令替换写法:$(...)
。其一般形式为:
$(command)
和反引号一样,command
是命令名,它会被命令的标准输出内容所替换。例如:
$ echo The date and time is: $(date)
The date and time is: Wed Aug 28 14:28:43 EDT 2002
这种写法要比反引号
更好。原因如下:首先,使用了单引号和反引号的复杂命令会很难阅读,尤其是当你所使用的字符无法从视觉上区分两者的时候;其次,$(...)
易于嵌套,能够在命令替换中再进行命令替换。尽管使用反引号也可以实现命令替换的嵌套,但是得花点心思。有一点需要重点强调:括号中并不是只能调用一个命令。可以将需要执行的多个命令用分号隔开,而且还可以使用管道。
例如:
$ namelist=$(cat names)
$ echo "$namelist"
Charlie
Emanuel
Fred
Lucy
Ralph
Tony
Tony
记住,Shell
是在替换过命令输出后才执行文件名替换的。将命令放在双引号
中能够阻止Shell
针对命令的输出再做文件名替换。
命令替换常用来修改 Shell 变量中的值。例如,如果变量name中包含了某人的名字,你想将名字中的字母全部转换成大写,那么可以使用 echo
将变量内容作为tr
的输入并执行转换,然后将结果再保存到变量中:
$ name="Ralph Kramden"
$ name=$(echo $name | tr '[a-z]' '[A-Z]') 转换成大写
$ echo $name
RALPH KRAMDEN
expr
命令
尽管标准Shell
有内建的整数算术,但老一代Shell
没有这种功能。在这种情况下,可以使用数学等式解算器expr
来代替:
$ expr 1 + 2
3
用起来很简单,但是 expr 并不擅长解析等式,因此操作数和操作符之间必须用空格分隔,这样它才能正确理解。这也解释了下面的情形:
$ expr 1+2
1+2
expr
能够识别常用的算术操作符:+、-、/、*和%
,分别对应于加法、减法、除法、乘法和求模(取余)。
$ expr 10 + 20 / 2
20
Shell
会将*
替换成当前目录下的所有文件名,expr
不知道如何对其作出处理!尤其是在乘法中,交给expr
的表达式必须放在引号中,避免受到Shell
的干扰,但是又不能作为单个参数。
$ expr "17 * 6"
17 * 6
记住,expr
必须看到操作符和操作数是以单纯的参数出现,上面的例子是将整个表达式作为了单个参数,这并不能得到想要的结果。
$ expr 17 \* 6
102
expr
的一个或多个参数自然也可以放到Shell
变量中,因为Shell
会首先进行替换:
$ i=1
$ expr $i + 1
2
这是一种对 Shell 变量执行算术运算的老方法,在效率上不如Shell
内建的$(...)
。如果要增加或修改变量,你可以使用命令替换机制将 expr
的输出再保存到变量中:
$ i=1
$ i=$(expr $i + 1) 将变量 i 加 1
$ echo $i
2
在传统的Shell
程序中,你很可能会碰到带有反引号的expr
:
$ i=`expr $i + 1` 将变量 i 加 1
$ echo $i
3
类似于
Shell
内建的算术功能,expr
只能够求值整数算术表达式。如果要进行浮点数运算的话,那么可以使用awk
或bc
。
另外要注意的是,expr
还有其他操作数。其中用得最多的一个就是:
操作符,它可以针对出现在第二个操作数中的正则表达式来匹配第一个操作数中的字符。默认会返回匹配到的字符数。例如:
expr "$file" : ".*"
会返回变量file中所包含的字符数,因为正则表达式.*
能够匹配字符串中的所有字符。