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


206

在Bash中是否有需要转义的字符的完整列表?可以仅通过进行检查sed吗?

特别是,我正在检查是否%需要逃脱。我试过了

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

并且工作得很好,没有逃脱%。这是否意味着%不需要逃脱?这是检查必要性的好方法吗?

更笼统:它们是shell和一起逃脱的相同字符bash吗?


4
通常,如果您在乎,那就错了。处理数据绝不应涉及通过用于代码的解析和评估过程来运行数据,从而避免麻烦。这与SQL最佳实践非常相似-正确的事情是使用绑定变量,错误的事情是试图“清理”通过字符串替换注入的数据。
查尔斯·达菲


8
@CharlesDuffy是的,但是有时候准备好的语句引擎在后端所做的只是转义。SO之所以“做错了”,是因为它们在浏览器中显示用户注释之前会逃避用户提交的注释?不。他们正在阻止XSS。完全不关心就做错了。
Parthian Shot

@ParthianShot,如果准备好的语句引擎没有使数据完全脱离代码带外,则应该对编写数据的人员进行射击。是的,我知道MySQL的有线协议是通过这种方式实现的。我的陈述是正确的。
查尔斯·达菲

@CharlesDuffy我的观点是-有时您的选择是使用工具链使某事安全地工作,该工具链会产生纯粹的畏惧,或花费八倍的时间和精力使其变得漂亮-仍然有效。
Parthian Shot 2015年

Answers:


282

有两个工作,这不仅容易和安全规则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。2的可读性更高

有一组容易安全的字符,例如[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命令都假定不这样做的原因。您可以手动添加带引号的换行符。

请注意,仅在POSIX意义上为文本定义了外壳变量。未定义处理二进制数据。对于重要的实现,二进制可以使用NUL字节(因为变量是使用C字符串实现的,并且打算用作C字符串,即程序参数),但是您应该切换到“二进制”语言环境,例如latin1 。


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


注意:#1的一个很好的变化可以在这里看到:github.com/scop/bash-completion/blob/…。它不需要运行sed,但是需要bash
jwd

4
请注意为其他人(如我!)而努力使它们工作……。看起来您在OSX上获得的sed风格无法正常运行这些sed命令。它们在Linux上工作正常!
dalelane

@dalelane:不能在这里测试。当您同时使用两个版本时,请进行编辑。
乔苏(So So

似乎您错过了该字符串应该以“-”(减号)开头,还是仅适用于文件名?-在后一种情况下,需要在前面加一个“ ./”。
slashmais

我不确定你是什么意思。使用这些sed命令,输入字符串取自stdin。
乔·苏

59

可以作为shell输入重用的格式

针对这种请求有一个特殊的 printf格式指令(%q):

printf [-v var]格式[参数]

 %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 

这个问题是,通过bash / sh调用pritnf时,必须首先为bash / sh对该字符串进行外壳转义
ThorSummoner15年

1
@ThorSummoner,如果您将字符串作为文字参数从另一种语言(假设您已经知道如何引用)传递给shell,则不会。在Python中:subprocess.Popen(['bash', '-c', 'printf "%q\0" "$@"', '_', arbitrary_string], stdin=subprocess.PIPE, stdout=subprocess.PIPE).communicate()将为您提供正确的带外壳引号的版本arbitrary_string
Charles Duffy 2015年

1
FYI bash的%q损坏时间已经很长了-如果我的想法对我有益,那么在损坏了大约10年之后,2013年就修复了一个错误(但仍可能会损坏)。所以不要依赖它。
Jo So

@CharlesDuffy当然,一旦您进入Python领域,shlex.quote()(> = 3.3,pipes.quote()-未记录-对于较旧的版本)也可以完成此工作,并生成更易于理解的版本(如有必要,添加引号和转义),无需生成外壳。
Thomas Perl

1
感谢您添加有关的特殊说明,。我惊讶地发现内置的Bash printf -- %q ','给出\,,但是/usr/bin/printf -- %q ','给出,(未转义)。同为其他字符:{|}~
kevinarpe

34

为了节省别人不必RTFM ...在bash中

在双引号包围的字符保留了引号中的所有字符的字面意义,例外$`\,和,启用了历史扩展的时候,!

...因此,如果您逃避了这些问题(当然还有引号本身),则可能还可以。

如果您采取较为保守的“不确定时逃脱”方法,则应避免不转义标识符字符(例如ASCII字母,数字或'_'),从而避免获取具有特殊含义的字符。它们极不可能(即,在某些类似于POSIX的怪异外壳中)具有特殊含义,因此需要转义。



这是一个简短,甜蜜且几乎正确的答案(为此为+1),但也许最好使用单引号-参见我的较长答案。
乔·苏

26

使用该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,

2
请记住,您%q不知道打算在外壳中的哪个位置使用该字符,因此它将转义所有可能在任何外壳上下文中具有特殊含义的字符。,本身对shell没有特殊含义,但是正如@ F.Hauri在他的回复中指出的那样,它在{...}括号扩展中确实具有特殊含义:gnu.org/savannah-checkouts/gnu/bash/manual / ...就像!这也仅需要在特定情况下进行扩展,而不是一般情况下:echo Hello World!可以正常工作,但echo test!test会失败。
梅基

18

在Bourne或POSIX Shell中,需要转义的字符与Bash不同。通常,(非常)Bash是这些shell的超集,因此您逃脱的任何东西shell都应在Bash中逃逸。

一个好的一般规则是“如有疑问,请逃脱”。但是转义某些字符会赋予它们特殊的含义,例如\n。这些列在man bash下面的页面Quotingecho

除此之外,请转义任何非字母数字的字符,这样比较安全。我不知道一个明确的清单。

手册页将它们全部列出,而不是放在一个地方。学习语言,这是确定的方法。

吸引我的是!。这是Bash(和csh)中的特殊字符(历史扩展),但在Korn Shell中不是。甚至echo "Hello world!"给问题。照常使用单引号删除特殊含义。


1
我特别喜欢A好的一般规则是“如有疑问,请逃避”建议。仍然有疑问,检查是否sed足够好以查看是否必须进行转义。感谢您的回答!
fedorqui'SO停止伤害

2
@fedorqui:sed不需要检查,您几乎可以检查任何东西。 sed不是问题,bash而是。单引号内没有特殊字符(单引号除外),您甚至无法在其中转义字符。一个sed命令通常应该是单引号内,因为RE元字符与shell元字符太多的重叠是安全的。嵌入shell变量时是一个例外,必须谨慎进行。
cdarke

5
用检查echo。如果您放出所放内容,则无需逃脱。:)
马克·里德

6

我认为您正在谈论bash字符串。有不同类型的字符串,对转义有不同的要求。例如。单引号字符串与双引号字符串不同。

最佳参考是bash手册的“ 报价”部分。

它说明了哪些字符需要转义。请注意,某些字符可能需要转义,具体取决于启用了哪些选项(例如历史记录扩展)。


3
因此,它确认逃避是一个没有简单解决方案的丛林,将不得不检查每种情况。谢谢!
fedorqui'SO停止伤害

@fedorqui与任何语言一样,要遵循一组规则。对于bash字符串转义,规则集非常小,如手册中所述。由于不需要转义,因此最容易使用的字符串是单引号。但是,没有办法在单引号中包含单引号。
奥斯汀·菲利普斯

@fedorqui。这不是丛林。逃避是完全可行的。看到我的新帖子。
因此

@fedorqui不能在单引号引起来的字符串中使用单引号,但可以使用类似“ text”,“””“ more text”
CR的

4

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

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

使用此工具,我使用ASCII表的字符进行了一些实验,并得出了以下列表:

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

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

bash无法逃脱的字符

#%+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~

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


2
如果您真的想有一个完整的列表,我建议您查看一下哪些字符printf %q可以作为参数传递而不会修改-理想情况下,遍历整个字符集。
查尔斯·达菲,2016年

在某些情况下,即使使用撇号字符串,您也可能希望转义字母和数字以产生特殊字符。例如:tr'\ n''\ t'将换行符转换为制表符。
迪克·居尔丁
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.