在Bash中转义字符
1. 简介
源代码、命令行和大多数最基本的计算机交互都由字符组成。另一方面,大多数字符不是由普通键盘上的键表示的,许多字符根本无法打印,还有一组是复杂的控制字符。
在本教程中,我们将讨论 Bash 中的字符转义。首先,我们简要描述机器如何表示字符。之后,我们探索 Bash 中的字符串类型。接下来详细讨论纯Bash中的字符转义。最后,我们看看涉及转义的具体情况。
我们使用 GNU Bash 5.1.4 在 Debian 11 (Bullseye) 上测试了本教程中的代码。它是 POSIX 兼容的,应该可以在任何这样的环境中工作。
2. 字符
通常,除了指针之外,用户还有另一种输入数据的方法——文本。为了表示文本,机器使用一系列字节。它们根据预定义的代码表对字符进行编码,通常是ASCII 或Unicode 。
字符类型大致 包括:
重要的是,我们必须有办法写出我们需要的任何字符,无论是 ASCII、Unicode 还是自定义编码。不幸的是,我们有一组最小的键盘键来表示许多不同的文本符号。
3. Bash 字符串
书写和存储字符是两个独立的操作,因为没有键盘可以对应所有可能的符号。
在 Bash 中,文本存储为字符串。事实上,所有 Bash 变量都只是字符串。它们通常是直接的、单引号或双引号的序列。
重要的是,这些方法之间的区别在于,我们在一种上下文中解释或插入某些字符组合,并在另一种上下文中按字面意思理解它们。
3.1. 单引号
在单引号内,我们不插入任何内容:
$ text='a $(echo b) c'
$ echo "${text}"
a $(echo b) c
注意单引号内的所有文本是如何被保留的。没有进行插值,但这意味着在任何情况下 ,我们也不能在单引号内直接使用单引号。
3.2. 双引号
使用双引号时,我们保留大多数字符的字面值:
$ text="a"
$ text="${text} $(echo "b") c"
$ echo "${text}"
a b c
首先,我们直接将 分配给文本,只使用 [a] 键。之后,我们通过*${text}的变量扩展来获取它的值。在这里,$* 美元符号组合被解释。最后,我们将这个值与一个表达式和另一个字符连接起来,分配回text。
3.3. 没有引号
只要字符串符合某些规则,我们就可以跳过引号:
$ text=a$(echo b)c
$ echo ${text}
abc
我们将在下一节讨论一些规则。
3.4. 特别引号
带有美元符号前缀的双引号文本会导致根据当前locale 翻译字符串。因此,字符串的最终翻译是双引号。重要的是,本文不涉及语言环境,并假定C作为所有示例的默认语言。
具有相同前缀的单引号文本被区别对待。在这种情况下,转义字符将被替换。
在下一节中,我们将阐明转义的含义。
4. Bash 字符转义
除了单引号之外,在 Bash 中具有特殊含义的字符必须被转义以保留它们的字面值。在实践中,这主要是通过转义字符 ** 反斜线来完成的。在某些情况下,我们可能不得不采用其他方法。
让我们看看我们何时以及如何使用哪种方法。
4.1. 双引号
我们通过在字符前加上反斜线来转义双引号字符串中的文本:
$ text1="a $(echo b) c"
$ text2="a \$(echo b) c"
$ echo "${text1}"
a b c
$ echo "${text2}"
a $(echo b) c
请注意,在text2的情况下,美元符号是如何被转义的,失去了它的特殊功能并保留了它的字面意义。
这些都是特殊字符,可能必须对其进行转义以在双引号中保留其字面意思:
- $ <美元符号>,例如*$()和${}*
- ` ,也称为反引号运算符
- ” ,当我们需要双引号内的双引号时
- newline ,相当于Linux下的
- \ <反斜杠>,当在此列表中添加前缀时,<感叹号>除外
- !,当在POSIX 模式 之外启用历史扩展 时,通常是这种情况
此外,反斜线前缀在上述所有字符()之前不存储在字符串中:
$ text="!event"
bash: !event: event not found
$ text="\a \$ \` \!event \\"
$ echo ${text}
\a $ ` \!event \
重要的是, 是一个特殊字符,其特殊含义可以通过以下方式忽略:
- 用反斜杠作为前缀(与 等普通字符相同)
- 在字符串末尾或空白字符之前使用它
- 将其括在单引号中以转义
- 通过set +o histexpand禁用历史扩展
- 处于 POSIX 模式
最后,组合反斜线和回车换行被忽略并从双引号字符串中删除。这仅仅意味着我们可以将一个字符串分散到多行而不添加换行符:
$ text="a \
> b"
$ echo "${text}"
a b
现在让我们探索 Bash 如何处理没有任何引号的序列。
4.2. 没有引号
正如我们已经展示的那样,我们可以完全放弃报价,但有一个价格。
也就是说,任何没有引号的序列都不会在不转义所有字符的情况下统一,这些字符不是字母数字或以下组的一部分:, , , , , < Commercial-at>、、、:
$ text=a\ \&\ b\ \&\ c
$ echo "${text}"
a & b & c
很少,如果有的话,最好不要使用引号。
4.3. ANSI-C 组合
使用*$‘STRING_TEXT’时,单引号内的序列扩展为字符串,并根据*ANSI-C 引用 替换转义字符:
$ echo $'\u0061'
a
\u转义序列将紧随其后的四位数字解释为 Unicode ISO/IEC 10646 表中的十六进制代码。
重要的是,在它们被识别的地方,我们可以使用*\u*、\U、\x和类似的序列来放置任何字符,而无需进一步转义。请注意,在这种情况下,转义会打开而不是关闭字符的特殊含义。这是避免键盘上的“按键短缺”的两种方法。
此外,许多其他工具使用ANSI-C 标准 。
5. 特殊情况
Bash 是一个具有内置命令和功能的 shell。许多使用 ANSI 标准,但一些功能也在字符串中使用自己的特殊控制字符。
请记住,我们通过 Bash 传递的任何字符串首先由 Bash 解释。这意味着上一节中的所有规则都适用,但我们可以在本节中建立在它们之上。
5.1. 重点提示
我们在使用 Bash 时看到的第一件事就是提示。它通常显示有关机器、用户、当前目录等的一些有用信息。所有这些都作为默认值存储在变量P0、P1、P2和P4中。
但是,我们可以修改这些变量。此外,我们可以使用终端控制字符 来自定义我们的提示:
$ echo "Current prompt: ${PS1}"
Current prompt: $
$ PS1='\t> '
00:00:10> echo "Current prompt: ${PS1}"
Current prompt: \t>
这些序列以 开头,就像我们已经看过的大多数一样。此外,还有*[和]*转义序列,它们添加了另一层编码。
5.2. ANSI 转义序列
在许多终端中,我们还可以使用其他转义码,例如标准 ANSI 转义序列 。例如,有一些方法 可以更改终端文本的颜色、光标位置、字体和其他选项。这些序列以 开头,因此得名:
$ PS1="TESTING\033[1K> "
> echo "Current prompt: ${PS1}"
Current prompt: TESTING\033[1K>
在这个例子中,所谓的控制序列引入器 < ESC>以参数 1开始K命令。因此,它清除了当前行开头的所有字符。因此,TESTING一词不会出现在提示中。
如前所述,ANSI 和 ANSI-C 转义序列用于整个 Linux 生态系统。例如,echo和printf 都可以识别它们。我们需要*-e参数来echo*,但printf默认使用 ANSI。
5.3. 打印
标准的内置printf(打印功能)命令也有自己的特殊字符。
回想一下我们关于编写不带引号的字符串的讨论。在该实例中我们需要转义的字符位于以下脚本的输出中:
$ for code in {0..127}; do
> printf -v chr '\\%o' "${code}"
> printf -v chr "${chr}"
> printf -v echr "%q" "${chr}"
> if [[ "${chr}" != "${echr}" ]]; then
> printf "%02X %-7s\n" "${code}" "${echr}"
> fi
> done
00 ''
01 $'\001'
[...]
07 $'\a'
08 $'\b'
09 $'\t'
0A $'\n'
[...]
上面的代码片段遍历了 ASCII 表中的前 128 个字符。对于每一个,它使用printf提取每个字符并将其与其转义形式进行比较。
首先,%o返回字符代码的八进制形式。接下来,在printf中使用 前缀重用此值以获取结果字符。之后,在*%q*格式修饰符的帮助下,我们得到了字符的转义版本。最后,我们比较普通版本和转义版本来判断是否需要转义这个字符,如果需要则输出结果。
请注意printf参数中的*%* 字符。*我们可以通过用另一个 : %%*转义它来忽略它的特殊含义。这保留了文字值。
5.4. 参数转换
从 4.4 版开始,Bash 支持参数转换。这个功能允许我们直接在 Bash中执行printf和其他内置函数的许多操作。
例如,我们可以使用 echo ${VAR@Q}代替printf和%q:**
$ text='\'
$ printf '%q\n' "${text}"
\\
$ echo "${text@Q}"
'\'
正如我们已经了解到的,*\和’'*是等价的。 当涉及到多级转义时,上述两种方法都非常有用:
$ text="6*6*6 equals 216"
$ text="$(printf '%q' "${text}")"
$ text="$(printf '%q' "${text}")"
$ echo "${text}"
6\\\*6\\\*6\\\ equals\\\ 216
实际上,如果没有自动执行此操作的方法,手动转义长行通常会导致许多错误。
5.5. 命令行参数
许多标准 Bash 内置程序使用*-* 参数名称前缀。此外,它们通常有**–参数,在此之后 的特殊解释跟随空格停止**。如果不使用*—*,我们就无法转义字符以防止这种行为。