0%

Shell中的引号

shell 中的引号

shell 能够识别四种不同的引用字符串的方式:

  • 单引号 '
  • 双引号 "
  • backslash 反斜线 \
  • back quote 反引号 ```

前两个和最后一个必须成对出现,第三个则可以在命令中出现多次,对于 shell 来说每个引号都有特殊的含义。

The Single Quote 单引号

shell 中有很多地方要用到引号,最常使用的是连续出现的多个空格都当作单一字符处理,而不忽略多余的空格。

grep Susan Goldberg phonebookgrep 'Susan Goldberg' phonebook,前者会报错。当 shell 遇到第一个单引号时,它忽略第一个单引号之后的任何有特殊意义字符直到遇到与之匹配的结束(closing quote)单引号。

shell 解析完成后会忽略引号,然后传递给命令。

echo one two three and echo 'one two three'

值得强调的是任何包含在单引号中的特殊字符都将失去意义,当作普通字符处理。

1
2
3
4
5
6
7
8
9
10
11
$ file=/bin/prog
$ echo $file
/bin/prog
$ echo '$file'
$file
$ echo *
f1.text f2.txt
$ echo '*'
*
$ echo '< > | ; ( ) { } >> " &'
< > | ; ( ) { } >> " &

如果用单引号括起来,甚至 Enter 键也会保留为命令参数的一部分:

1
2
3
4
$ echo 'How are you today,
> melon'
How are you today,
melon

在解析完第一行后,shell 发现引号还没匹配到,所以 shell 使用 > 来提示用户输入结束的引号。

> 叫做 secondary prompt 辅助提示符,每当 shell 等待用户完成输入多行命令时就会显示该字符。

将包含空格或特殊字符的值分配给 shell 变量时也需要引号,尽管存在细微差别,如下所示:

1
2
3
4
5
6
7
$ message='I must say, this is fun'
$ echo $message
I must say, this is fun

$ text='* means all files in the directory'
$ echo $text
names nu numbers phonebook stat means all files in the directory

在第一个例子中引号是需要保留的,因为值中包含了空格。

第二个例子说明 shell 在变量替换后进行了文件名替换,在变量替换后和 echo 执行前进行文件名替换。

这非常令人讨厌,怎么解决呢?使用双引号。

The Double Quote 双引号

双引号的工作方式与单引号类似,但双引号对内容的保护程度较低:单引号告诉 shell 忽略所有包含的字符的特殊意义,双引号则告诉 shell 忽略大部分,即有些字符不要忽略。

以下三个字符不会被 ignore:

  • Dollar signs
  • Back quotes
  • Backslashes

美元符号不被忽略的事实意味着变量名称替换是由 shell 在双引号内完成的。

1
2
3
4
5
6
7
$ filelist=*
$ echo $filelist
addresses intro lotsaspaces names nu numbers phonebook stat
$ echo '$filelist'
$filelist
$ echo "$filelist" # 双引号不会忽略 `$`,意味着会进行替换
*

现在能看到没有引号、单引号、双引号之间的主要差别了,第一个例子中,shell 看到了 * 会进行文件名替换,第二个例子中,shell 忽略单引号中的内容,直接打印,最后一个例子中,双引号告诉 shell,变量名替换依然要执行,只不过是在双引号中执行,所以,shell 使用 * 替换了 $filelist,但是文件名替换不会在双引号中执行,所以 * 安全的传递给了 echo

如果想要替换变量的值,但不希望 shell 专门解析替换后的字符,请将变量括在双引号内。

双引号和没有引号的区别

1
2
3
4
5
6
7
$ address="39 East 12th
> New York"
$ echo $address
39 East 12th New York
$ echo "$address"
39 East 12th
New York

注意在这个例子中,分配给 address 变量的值是包裹在单引号还是双引号中的并没有什么区别。无论哪种情况,shell 都会显示辅助命令提示符,以表明它正在等待相应的结束引号。

第一个例子其实和 echo one two three 一样,shell 从命令中移除空格、tabs 和 newlines(whitespace characters),然后分隔成多个参数传递给对应的命令。

args

echo "$address",shell 首先替换变量,然后放到双引号中,shell 则会把整个字符串当作参数处理。

args2

有点奇怪的是双引号可以用来隐藏单引号,反之亦然:

1
2
3
4
5
6
$ x="' Hello,' he said"
$ echo $x
'Hello,' he said
$ article=' "Keeping the Logins from Lagging," Bell Labs Record'
$ echo $article
"Keeping the Logins from Lagging," Bell Labs Record

The Backslash

其实就是转义字符,从功能上来讲,backslash 反斜杠(用作前缀)和使用单引号包裹单个字符效果一样,也有一些例外情况(continue line)。反斜杠转义了紧跟它的单个字符。

echo > 会报错,而 echo \> 则会正常显示,前者会认为要重定向,则它期望有文件名参数,后者使用反斜杠取消了 > 的特殊含义。

x=* and echo \$x shell 忽略了 $ 的功能,不会进行变量替换。

echo \\echo '\' 效果一样。

Using the Backslash for Continuing Lines

之前提到 \c'c' 等价,但是有个区别是当 \ 作为一行中最后一个字符时:

1
2
3
4
5
6
7
8
9
10
# 单引号告诉 shell 忽略(保留)换行的特殊意义
$ lines=one'
> 'two
$ echo "$lines"
one
two
$ lines=one\
>two
$ echo "$lines"
onetwo

当反斜线作为一行中最后一个字符出现时,shell 会把它当作 line continuation character,会移除换行就像没有敲过换行一样,通常用来输入很长的命令时使用。

1
2
3
4

Longinput="The shell treats a backslash that's the \
last character of a line of input as a line \
continuation. It removes the newline too."

The Backslash Inside Double Quotes

之前提到反斜线是在双引号中被 shell 解释的三个字符之一(不会被忽略)。这意味着可以使用这些引号内的反斜杠来删除在双引号内解释的字符的含义(即其他反斜杠、美元符号、反引号、换行符和其他双引号)。

如果反斜杠位于双引号内的任何其他字符之前,则 shell 会忽略反斜杠后字符的特殊意义并将其传递给程序:

1
2
3
4
5
6
7
$ 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 会忽略美元符号,删除反斜杠,并将结果传递给 echo。在第二个示例中,反斜杠位于空格之前,双引号内的 shell 不会解释该空格。因此 shell 会忽略反斜杠并将其传递给 echo 命令。最后一个示例显示了用于将双引号括在双引号字符串内的反斜杠。

exercise

怎么打印 <<< echo $x >>> displays the value of x, which is $x

  1. echo "<<< echo \$x >>> displays the value of x, which is $x"
  2. echo '<<< echo $x >>> displays the value of x, which is' $x

第一个例子中,反斜线用来防止 shell 替换第一个 $x,第二个例子是存在风险的,如果变量 x 包含文件替换或者空白字符呢?此时就会被 shell 解释,更安全的使用 echo 方式是:

echo '<<< echo $x >>> displays the value of x, which is' "$x"

Command Substitution

命令替换 Command Substitution 是指 shell 在命令行中的任意位置用该命令的输出替换指定命令的能力。 shell 中有两种执行命令替换的方法:

  • 将命令括在反引号中
  • $(...) 结构将其括起来

The Back Quote

反引号与之前遇到的任何类型的引号不同,因为它的目的不是保护 shell 中的字符,而是告诉 shell 用其输出替换所包含的命令。

现在使用反引号通常不推荐,老的 shell 通常使用反引号,更好的方式是使用 $()

1
2
echo The date and time is: `date`
echo Your current working directory is `pwd`

shell 能够识别反引号并期待一个命令包在反引号中,然后执行该命令并把输出替换到反引号的位置,然后在继续处理。

The $(...) Construct

现在绝大多数的系统都支持这种语法。

echo The date and time is: $(date),这种写法比反引号好的原因:

  • 首先,使用正引号和反引号组合的复杂命令可能难以阅读,特别是如果使用的字体在视觉上无法区分单引号和反引号
  • $(...) 结构可以轻松嵌套,从而允许命令替换中的命令替换。虽然嵌套也可以使用反引号来执行,但这比较棘手

强调一些重要的事情:并不局限于调用括号之间的单个命令,多个命令之间用分号分隔可以执行;而且,更常见的是,还可以使用命令管道。

echo There are $(who | wc –1) users logged in 注意命令是由 shell执行的,echo 看到的是被替换后的内容了。

now=$(date)

filelist=$(ls) echo $filelist filelist 中是包含换行符的,是在 echo 的时候被替换了,可以使用 echo "$filelist" 来恢复,有引号和没引号的差距。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ name="John Smith"
$ name=$(echo $name | tr '[a-z]' '[A-Z]')
$ echo $name
JOHN SMITH

# 提取变量中第一个字符
$ filename=/user/home/
$ firstchar=$(echo $filename | cut -c1)
$ echo $firstchar

# 提取最后一个字符
$ file=exec.o
$ lastchar=$(echo $file | sed 's/.*\(.\)$/\1/')
$ echo $lastchar

命令可以嵌套

1
2
3
4
5
6
7
8
9
10
11
$ filename=/users/steve/memos
$ firstchar=$(echo $filename | cut -c1)
$ filename=$(echo $filename | tr "$firstchar" "^")
$ echo $filename
^users^steve^memos

# 可以使用嵌套
$ filename=/users/steve/memos
$ filename=$(echo $filename | tr "$(echo $filename | cut -c1)" "^")
$ echo $filename
^users^steve^memos

可以看到 echo 在 shell 中很常用。

The expr Command

尽管标准 shell 支持内置整数运算,但较旧的 shell 没有此功能。在这种情况下,可以使用数学方程求解器 expr 来代替:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ expr 1 + 2
3
$ expr 1+2
1+2

$ expr 17 * 6 # error * 有特殊含义
$ expr "17 * 6" # expr 必须把操作符当做单个参数,这里只有一个参数
17 * 6

$ expr 17 \* 6
102

$ i=1
$ expr $i + 1
# 效率没有 `$()` 高