在使用Bash时,哪些字符需要转义?

297

在Bash中,是否有任何需要转义的字符的综合列表?是否可以使用sed来检查?

特别是,我想知道是否需要转义%。我尝试了

echo "h%h" | sed 's/%/i/g'

并且正常工作,没有转义 %。这是否意味着不需要转义 %?这是检查必要性的好方法吗?

更一般地说,在 shellbash 中需要转义的字符是相同的吗?

7个回答

372

有两个简单而安全的规则,不仅适用于sh,还适用于bash

1. 把整个字符串放在单引号中

这适用于除单引号本身之外的所有字符。要转义单引号,请在其前面关闭引用,插入单引号,然后重新打开引用。

'I'\''m a s@fe $tring which ends in newline
'

sed命令:sed -e "s/'/'\\\\''/g; 1s/^/'/; \$s/\$/'/"

2. 使用反斜杠转义每个字符

这适用于除换行符外的所有字符。对于换行符,请使用单引号或双引号。空字符串必须仍然被处理-替换为""

\I\'\m\ \a\ \s\@\f\e\ \$\t\r\i\n\g\ \w\h\i\c\h\ \e\n\d\s\ \i\n\ \n\e\w\l\i\n\e"
"

sed命令: sed -e 's/./\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.

2b. 更易读的版本

有一组易于安全使用而且不必转义的字符,如[a-zA-Z0-9,._+:@%/-],可以使代码更易读。

I\'m\ a\ s@fe\ \$tring\ which\ ends\ in\ newline"
"

sed命令:LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@%/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'


请注意,在sed程序中,无法确定输入的最后一行是否以换行符结束(除非为空)。这就是为什么上面两个sed命令都假设它没有。您可以手动添加带引号的换行符。

请注意,shell变量仅针对POSIX意义上的文本进行定义。处理二进制数据未被定义。对于有影响的实现,二进制可以工作,但要注意NUL字节(因为变量使用C字符串实现,并且旨在用作C字符串,即程序参数),但应切换到“二进制”区域设置,例如latin1。


(您可以通过阅读sh的POSIX规范轻松验证规则。对于bash,请查看由@AustinPhillips链接的参考手册)。


2
请注意:#1 的良好变化可以在此处查看:https://github.com/scop/bash-completion/blob/233421469b12d3b60e7595822cc9166016abe384/bash_completion#L138。它不需要运行 sed,但需要bash - jwd
6
对于像我一样难以使这些命令正常工作的人,请注意......看起来在OSX上得到的sed版本无法正确运行这些sed命令。但在Linux上可以正常工作! - dalelane
@dalelane:这里无法测试,请在您有适用于两者的版本时进行编辑。 - Jo So
我不确定你的意思。使用那些sed命令时,输入字符串从stdin中获取。 - Jo So
1
对于没有GNU sed的macOS用户: @fd0有一个sed选项来转义每个字符:https://apple.stackexchange.com/a/363400/409134 我编写了一个解决方案,只使用perl转义控制字符:https://apple.stackexchange.com/a/458279/409134 - Nils
显示剩余17条评论

99

可作为Shell输入重复使用的格式

2021年2月修改: ${var@Q}

在bash下,您可以使用参数扩展(Parameter Expansion)中的@命令来存储变量内容以进行参数转换(Parameter transformation)

${parameter@operator}
       Parameter transformation.  The expansion is either a transforma‐
       tion of the value of parameter or  information  about  parameter
       itself,  depending on the value of operator.  Each operator is a
       single letter:

       Q      The expansion is a string that is the value of  parameter
              quoted in a format that can be reused as input.
...
       A      The  expansion  is  a string in the form of an assignment
              statement or declare command  that,  if  evaluated,  will
              recreate parameter with its attributes and value.

示例:

$ var=$'Hello\nGood world.\n'
$ echo "$var"
Hello
Good world.

$ echo "${var@Q}"
$'Hello\nGood world.\n'

$ echo "${var@A}"
var=$'Hello\nGood world.\n'

旧答案

有一个特殊的printf格式指令(%q)是为了这种请求而建立的:

printf [-v var] format [arguments]

 %q     causes printf to output the corresponding argument
        in a format that can be reused as shell input.

一些示例:

read foo
Hello world
printf "%q\n" "$foo"
Hello\ world

printf "%q\n" $'Hello world!\n'
$'Hello world!\n'

这也可以通过变量来使用:

printf -v var "%q" "$foo
"
echo "$var"
$'Hello world\n'

使用所有(128)个ASCII字节进行快速检查:

请注意,从128到255的所有字节都必须进行转义。

for i in {0..127} ;do
    printf -v var \\%o $i
    printf -v var $var
    printf -v res "%q" "$var"
    esc=E
    [ "$var" = "$res" ] && esc=-
    printf "%02X %s %-7s\n" $i $esc "$res"
done |
    column

这必须呈现出类似于:

00 E ''         1A E $'\032'    34 - 4          4E - N          68 - h      
01 E $'\001'    1B E $'\E'      35 - 5          4F - O          69 - i      
02 E $'\002'    1C E $'\034'    36 - 6          50 - P          6A - j      
03 E $'\003'    1D E $'\035'    37 - 7          51 - Q          6B - k      
04 E $'\004'    1E E $'\036'    38 - 8          52 - R          6C - l      
05 E $'\005'    1F E $'\037'    39 - 9          53 - S          6D - m      
06 E $'\006'    20 E \          3A - :          54 - T          6E - n      
07 E $'\a'      21 E \!         3B E \;         55 - U          6F - o      
08 E $'\b'      22 E \"         3C E \<         56 - V          70 - p      
09 E $'\t'      23 E \#         3D - =          57 - W          71 - q      
0A E $'\n'      24 E \$         3E E \>         58 - X          72 - r      
0B E $'\v'      25 - %          3F E \?         59 - Y          73 - s      
0C E $'\f'      26 E \&         40 - @          5A - Z          74 - t      
0D E $'\r'      27 E \'         41 - A          5B E \[         75 - u      
0E E $'\016'    28 E \(         42 - B          5C E \\         76 - v      
0F E $'\017'    29 E \)         43 - C          5D E \]         77 - w      
10 E $'\020'    2A E \*         44 - D          5E E \^         78 - x      
11 E $'\021'    2B - +          45 - E          5F - _          79 - y      
12 E $'\022'    2C E \,         46 - F          60 E \`         7A - z      
13 E $'\023'    2D - -          47 - G          61 - a          7B E \{     
14 E $'\024'    2E - .          48 - H          62 - b          7C E \|     
15 E $'\025'    2F - /          49 - I          63 - c          7D E \}     
16 E $'\026'    30 - 0          4A - J          64 - d          7E E \~     
17 E $'\027'    31 - 1          4B - K          65 - e          7F E $'\177'
18 E $'\030'    32 - 2          4C - L          66 - f      
19 E $'\031'    33 - 3          4D - M          67 - g      

第一个字段是字节的十六进制值,第二个字段包含 E,如果字符需要转义,第三个字段显示字符的转义表示。

为什么是,

你可能会看到一些字符不必总是被转义,比如 ,}{

所以不总是,但有时候:

echo test 1, 2, 3 and 4,5.
test 1, 2, 3 and 4,5.

或者

echo test { 1, 2, 3 }
test { 1, 2, 3 }

但要注意:

echo test{1,2,3}
test1 test2 test3

echo test\ {1,2,3}
test 1 test 2 test 3

echo test\ {\ 1,\ 2,\ 3\ }
test  1 test  2 test  3

echo test\ {\ 1\,\ 2,\ 3\ }
test  1, 2 test  3 

2
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Charles Duffy
2
感谢您添加关于,的特殊注释。我很惊讶地发现,内置的Bash printf -- %q ','会给出\,,但/usr/bin/printf -- %q ','会给出,(未转义)。其他字符也是如此:{|}~ - kevinarpe
1
根据本地配置,使用utf或iso作为默认设置,在128到255之间处理字节可能会导致奇怪的行为。 - F. Hauri - Give Up GitHub
1
那个新的 @Q 非常有用! - fedorqui
1
感谢您在2021年2月的编辑,@A正是我所需要的! - Lenormju
显示剩余6条评论

52

使用print '%q'技巧,我们可以运行循环来查找哪些字符是特殊字符:

#!/bin/bash
special=$'`!@#$%^&*()-_+={}|[]\\;\':",.<>?/ '
for ((i=0; i < ${#special}; i++)); do
    char="${special:i:1}"
    printf -v q_char '%q' "$char"
    if [[ "$char" != "$q_char" ]]; then
        printf 'Yes - character %s needs to be escaped\n' "$char"
    else
        printf 'No - character %s does not need to be escaped\n' "$char"
    fi
done | sort

它会输出以下内容:

No, character % does not need to be escaped
No, character + does not need to be escaped
No, character - does not need to be escaped
No, character . does not need to be escaped
No, character / does not need to be escaped
No, character : does not need to be escaped
No, character = does not need to be escaped
No, character @ does not need to be escaped
No, character _ does not need to be escaped
Yes, character   needs to be escaped
Yes, character ! needs to be escaped
Yes, character " needs to be escaped
Yes, character # needs to be escaped
Yes, character $ needs to be escaped
Yes, character & needs to be escaped
Yes, character ' needs to be escaped
Yes, character ( needs to be escaped
Yes, character ) needs to be escaped
Yes, character * needs to be escaped
Yes, character , needs to be escaped
Yes, character ; needs to be escaped
Yes, character < needs to be escaped
Yes, character > needs to be escaped
Yes, character ? needs to be escaped
Yes, character [ needs to be escaped
Yes, character \ needs to be escaped
Yes, character ] needs to be escaped
Yes, character ^ needs to be escaped
Yes, character ` needs to be escaped
Yes, character { needs to be escaped
Yes, character | needs to be escaped
Yes, character } needs to be escaped

其中一些结果,例如,看起来有点可疑。很有趣能够得到@CharlesDuffy的意见。


2
你可以在我的回答的最后一段阅读关于“看起来有点可疑”的*,问题的答案。 - F. Hauri - Give Up GitHub
3
请注意,%q 不知道您计划在 shell 中使用字符的位置,因此它将转义所有可能在任何可能的 shell 上下文中具有特殊含义的字符。, 本身对于 shell 没有特殊含义,但正如 @F.Hauri 在他的回复中指出的那样,在 {...} 大括号扩展中确实具有特殊含义:https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Brace-Expansion 这就像 ! 一样,只需要在特定情况下扩展,而不是在一般情况下扩展:echo Hello World! 可以正常工作,但 echo test!test 将失败。 - Mecki

52
为了让其他人不必阅读手册,以下是在bash中的技巧:
将字符用双引号括起来可以保留引号内所有字符的字面值,除了$、`、\和历史扩展启用时的!。
因此,如果你转义这些字符(当然也包括引号本身),你应该没问题。
如果你采取更为谨慎的“当有疑虑时,转义它”的方法,通过不转义标识符字符(即ASCII字母、数字或'_')应该可以避免获取具有特殊含义的字符。这些字符极不可能(即使在某些奇怪的POSIX-ish shell中)具有特殊含义,因此无需进行转义。

2
这是上面引用的手册链接:https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html - code_monk
1
这是一个简短、简洁且大部分正确的答案(+1),但也许使用单引号会更好——请参见我的更长的答案。 - Jo So

19

在 Bourne 或 POSIX shell 中需要转义的字符与 Bash 不同。 通常,Bash 是这些 shell 的超集,因此您在 shell 中转义的任何内容都应该在 Bash 中转义。

一个很好的通用规则是“如果不确定,请转义它”。但转义某些字符会赋予它们特殊含义,如 \n。 这些字符在 man bash 页面下的 Quotingecho 中列出。

除此之外,转义任何非字母数字的字符都更加安全。我不知道确切的列表。man 手册中某处列出了它们,但没有一个地方集中列出。学习语言才是确保正确的方法。

其中一个让我犯错的是 !。这是 Bash(和 csh)中的特殊字符(历史扩展),但不是 Korn shell 中的特殊字符。即使是 echo "Hello world!" 也会出问题。像往常一样使用单引号可以消除特殊含义。


1
我特别喜欢“如果不确定,就转义”的建议。仍然怀疑使用 sed 检查是否需要转义是否足够好。感谢您的回答! - fedorqui
2
@fedorqui:不需要使用sed进行检查,您可以使用几乎任何东西进行检查。 sed不是问题,bash才是。在单引号内没有特殊字符(除了单引号),甚至无法在其中转义字符。sed命令通常应该在单引号内,因为RE元字符与shell元字符有太多重叠,以至于不安全。例外情况是嵌入shell变量,这必须小心处理。 - cdarke
5
使用 echo 命令进行检查。如果输出与输入相同,则无需转义。 :) - Mark Reed

7
我假设你在谈论bash字符串。不同类型的字符串需要不同的转义要求,例如单引号字符串与双引号字符串不同。
最好的参考资料是bash手册的Quoting部分。
它解释了哪些字符需要转义。请注意,某些字符可能需要转义,这取决于启用了哪些选项,例如历史扩展。

3
所以这证实了逃跑是一个没有简单解决方案的"丛林",每种情况都需要仔细核实。谢谢! - fedorqui
@fedorqui 就像任何一种语言一样,都有一套需要遵循的规则。对于bash字符串转义来说,规则集非常小,如手册所述。最容易使用的字符串是单引号,因为没有什么需要转义的。然而,在单引号字符串中无法包含单引号。 - Austin Phillips
@fedorqui。这并不是丛林。转义非常容易。请看我的新帖子。 - Jo So
@fedorqui 你不能在单引号字符串中使用单引号,但是你可以通过类似于以下方式进行“转义”:'text'"'"'more text' - CR.

5

我注意到在使用自动完成时,Bash会自动转义一些字符。

例如,如果你有一个名为dir:A的目录,bash会自动完成为dir\:A

利用这一点,我运行了一些使用ASCII表字符的实验,并得出了以下列表:

Bash在自动完成时需要转义的字符:(包括空格)

 !"$&'()*,:;<=>?@[\]^`{|}

bash不转义的字符:

#%+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~

(我排除了/,因为它不能用于目录名)

3
如果你真的想要一个全面的列表,我建议查看 printf %q 命令在作为参数传递时修改和不修改哪些字符 - 最好是遍历整个字符集。 - Charles Duffy
有时即使使用撇号字符串,您可能希望转义字母和数字以生成特殊字符。例如:tr '\n' '\t'它将换行符转换为制表符。 - Dick Guertin
@CharlesDuffy 自动完成转义的字符与 printf %q 的有些不同,我在测试包含“home”波浪号的路径名时遇到了这个问题(%q会转义,对我造成了问题,而自动完成则不会)。 - Compholio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接