前言
引用是 shell 脚本内部的一部分,因此专门设置本节来阐明它在所有 shell 中的用法。如果您经常遇到引用方面的语法错误,学习本节之后将确保您能够熟悉如何使用它们,特别是当脚本中包含有类似 grep、sed 和 awk 的命令时。
一、知识铺垫
反斜线
- 位于一个字符之前,转义该字符。
- 与将一个字符放在单引号之内相同。
单引号
- 必须匹配。
- 保护除以下之外的所有元字符免被解释。
a. 自身
b. 感叹号(仅 csh 适用)
c. 反斜线
双引号
- 必须匹配。
- 保护除以下之外的所有元字符免被解释。
a. 自身
b. 感叹号(仅 csh 适用)
c. 用于变量替换
的$(例如:$(command))
d. 用于命令替换
的反引号` `(例如:` command `)
反引号
shell 程序使用反引号进行命令替换。它们与单引号和双引号无关,但通常是问题之源。例如,当复制一个 shell 脚本时,如果用单引号替换了反引号,则程序将不会工作。(误将反引号当做单引号)
#! /bin/bash
# Scriptname: demo0
1 now=`date`
2 echo Today is $now
3 echo "You have `ls | wc -l` files in this directory"
4 echo 'You have `ls | wc -l` files in this directory'
[root@localhost tmp]
[root@localhost tmp]# ./demo0
2 Today is Thu Nov 25 11:15:21 CST 2021
3 You have 20 files in this directory
4 You have `ls | wc -l` files in this directory
[root@localhost tmp]#
说明:
- 将 UNIX/Linux date 命令的输出赋值为变量 now。反引号导致命令替换。反引号一般位于键盘上的代字符号建(~)上。
- 显示了变量 now 的值,也就是当前日期。
- 对 UNIX/Linux 管道使用反引号。ls 的输出通过管道成为 wc -l 的输入。结果是统计当前目录下文件的个数。双引号中的字符串将不会进行命令替换。输出嵌入到字符串中并被打印。
- 以单引号引用字符串,则其中的反引号将不会被解释,只作字面理解。
二、混合使用引号
混合使用引号是一个很有难度的问题。这一节将教您如何正确使用引号的步骤。我们将演示如何将一个 shell 变量嵌入到 awk 命令中并在不涉及 awk 字段指示符 $1 和 $2 的情况下使 shell 对变量进行扩展。
在此节,将借助 shell 调试手段来方便观察现象,使用 set -x 命令打开“执行命令的调试”,使用 set +x 命令将之关闭。
[root@localhost tmp]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@localhost tmp]#
[root@localhost tmp]# set -x
[root@localhost tmp]# echo $PATH
+ echo /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@localhost tmp]#
[root@localhost tmp]#
[root@localhost tmp]# set +x
+ set +x
[root@localhost tmp]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
使用 set -x 命令打开调试后,每次执行命令前,都会先执行变量替换并打印命令行,打印命令行的行首会有加号(+),之后再打印命令行的执行结果。有关 shell 调试的相关内容详情请看 shell —— 调试。
实例分析1
代码如下(示例):
示例
[root@localhost tmp]# name="Jacob Savage"
[root@localhost tmp]# cat datafile
Jacob Savage:408-298-7732:934 La Barbara Dr. , San Jose, CA:02/27/78:500000
[root@localhost tmp]#
[root@localhost tmp]# awk -F: '$1 ~ /^$name/{print $2}' datafile
+ awk -F: '$1 ~ /^$name/{print $2}' datafile
可以看到,单引号内的字符串'$1 ~ /^$name/{print $2}'
都只作字面理解,shell 无法对单引号内的内容做变量替换和命令替换,即 datafile 文件没有匹配到 $name 模式,故输出为空。
图解分析
从 awk 命令左边开始,将第一个引号保持原样,在 $name 的 shell 美元符前再放置一个单引号。现在第一个单引号匹配成功,这对引号中间的字符将不受 shell 干扰。而后面的变量 $name 不在引号内,因此 shell 会对 $name 进行变量替换。接着在 $name 中的字母 e 之后再加上一个单引号,于是又匹配成功一对单引号,这对单引号结束于 awk 右花括号之后。这一对引号中的所有字母也将不会被 shell 解释了。
测试验证
[root@localhost tmp]# awk -F: '$1 ~ /^'$name'/{print $2}' datafile
+ awk -F: '$1 ~ /^Jacob' 'Savage/{print $2}' datafile
awk: cmd. line:1: $1 ~ /^Jacob
awk: cmd. line:1: ^ unterminated regexp
[root@localhost tmp]#
可以看到,被单引号保护起来的左右两个字符串内容保持不变,$name 被 shell 进行了变量替换成 “Jacob Savage”。但是由于 $name 的内容带有空白符,因此 awk 命令被分割成了两部分,导致语法错误。因此,shell 变量最好使用双引号保护起来。
最终验证
[root@localhost tmp]# awk -F: '$1 ~ /^'"$name"'/{print $2}' datafile
+ awk -F: '$1 ~ /^Jacob Savage/{print $2}' datafile
408-298-7732
[root@localhost tmp]
[root@localhost tmp]# awk -F: '$1 ~ /^"'$name'"/{print $2}' datafile
+ awk -F: '$1 ~ /^"Jacob' 'Savage"/{print $2}' datafile
awk: cmd. line:1: $1 ~ /^"Jacob
awk: cmd. line:1: ^ unterminated regexp
[root@localhost tmp]#
如上所示,在第一条命令中,使用双引号保护 $name shell变量时,变量替换的结果中带有空白符,仍然保证了结果的正确。在第二条命令中,左边双引号 " 被单引号保护了起来,因此无法保护 $name,仍然导致命令错误。
实例分析2
代码如下(示例):
示例
[root@localhost tmp]# oldname="Ellie Main"
[root@localhost tmp]# newname="Eleanor Quigley"
[root@localhost tmp]# cat datafile
Ellie Main:408-298-7732:934 La Barbara Dr. , San Jose, CA:02/27/78:500000
[root@localhost tmp]#
[root@localhost tmp]# awk -F: '/^'$oldname'/{$1="'$newname'"; print $0}' datafile
+ awk -F: '/^Ellie' 'Main/{$1="Eleanor' 'Quigley"; print $0}' datafile
awk: cmd. line:1: /^Ellie
awk: cmd. line:1: ^ unterminated regexp
[root@localhost tmp]#
从第一个单引号左侧开始,向行右侧移动直至到达变量 $oldname,在这个美元符号前放置另外一个单引号。将另外一个单引号放置在 $oldname 变量最后一字母 e 之后,该单引号与 $newname 美元符号前面的单引号匹配。$newname 变量最后字母 e 的单引号与行尾的单引号匹配。
这样,$oldname 与 $newname 都将被 shell 进行变量替换。但是由于变量带有空白符,导致了 awk 语法错误。
测试验证
[root@localhost tmp]# cat datafile
Ellie Main:408-298-7732:934 La Barbara Dr. , San Jose, CA:02/27/78:500000
[root@localhost tmp]#
[root@localhost tmp]# awk -F: '/^'"$oldname"'/{$1='"$newname"'; print $0}' datafile
+ awk -F: '/^Ellie Main/{$1=Eleanor Quigley; print $0}' datafile
408-298-7732 934 La Barbara Dr. , San Jose, CA 02/27/78 500000
[root@localhost tmp]#
可以看到,$oldname 变量替换的结果是正确的,但是 $newname 变量替换的结果带有空白符,导致 $1=Eleanor Quigley 出现语法错误,应该使得 $1=“Eleanor Quigley” 才是正确执行。
[root@localhost tmp]#
[root@localhost tmp]# cat datafile
Ellie Main:408-298-7732:934 La Barbara Dr. , San Jose, CA:02/27/78:500000
[root@localhost tmp]#
[root@localhost tmp]# awk -F: '/^'"$oldname"'/{$1="'"$newname"'"; print $0}' datafile
+ awk -F: '/^Ellie Main/{$1="Eleanor Quigley"; print $0}' datafile
Eleanor Quigley 408-298-7732 934 La Barbara Dr. , San Jose, CA 02/27/78 500000
[root@localhost tmp]#