在 Bash 中,当为命令指定命令行参数时,需要转义哪些字符?
它们是否仅限于 Bash 的元字符:空格 、制表符|
、、、、、、、、和?&
;
(
)
<
>
答案1
在某些上下文中,以下字符对 shell 本身具有特殊含义,可能需要在参数中转义:
特点 | 统一码 | 姓名 | 用法 |
---|---|---|---|
` |
U+0060(重音) | 反引号 | 命令替换 |
~ |
U+007E | 波形符 | 波形符扩展 |
! |
U+0021 | 感叹号 | 历史扩展 |
# |
U+0023 数字符号 | 哈希值 | 评论 |
$ |
U+0024 | 美元符号 | 参数扩展 |
& |
U+0026 | 和号 | 后台命令 |
* |
U+002A | 星号 | 文件名扩展和通配符 |
( |
U+0028 | 左括号 | 子壳 |
) |
U+0029 | 右括号 | 子壳 |
|
U+0009 | 标签 (⇥ ) |
分词(空白) |
{ |
U+007B 左花括号 | 左大括号 | 支撑扩张 |
[ |
U+005B | 左方括号 | 文件名扩展和通配符 |
| |
U+007C 垂直线 | 竖条 | 管道 |
\ |
U+005C 逆固相线 | 反斜杠 | 转义字符 |
; |
U+003B | 分号 | 分隔命令 |
' |
U+0027 撇号 | 单引号 | 字符串引用 |
" |
U+0022 引号 | 双引号 | 带插值的字符串引用 |
↩ |
U+000A 换行 | 新队 | 越线 |
< |
U+003C | 少于 | 输入重定向 |
> |
U+003E | 比...更棒 | 输出重定向 |
? |
U+003F | 问号 | 文件名扩展和通配符 |
|
U+0020 | 空间 | 分词1(空白) |
其中一些字符比我链接的字符用于更多的事情和更多的地方。
有一些极端情况是明确可选的:
!
可以禁用set +H
,这是非交互式 shell 中的默认值。{
可以禁用set +B
。*
并且?
可以禁用set -f
或者set -o noglob
。=
等号 (U+003D) 也需要转义,如果set -k
或者set -o keyword
已启用。
转义换行符需要引用— 反斜杠不起作用。中列出的任何其他字符IFS将需要类似的处理。你不需要逃避]
或者}
,但是你做需要转义,)
因为它是一个运算符。
其中一些角色在真正需要逃跑时比其他角色有更严格的限制。例如,a#b
is ok,but a #b
is a comment,而>
在两种情况下都需要转义。无论如何,保守地避开它们并没有什么坏处,而且这比记住细微的区别更容易。
如果您的命令名称本身是 shell 关键字 ( if
, for
, do
),那么您也需要对其进行转义或引用。其中唯一有趣的是in
,因为它始终是一个关键字并不明显。你不仅当您(愚蠢地!)以其中一个关键字命名命令时,才需要对参数中使用的关键字执行此操作。 Shell 运算符((
、&
等)无论在哪里都始终需要引用。
1 Stéphane 指出任何其他单字节您所在区域的空白字符也需要逃避。在最常见、合理的语言环境中,至少是基于 C 或 UTF-8 的语言环境中,它只是上面的空白字符。在某些 ISO-8859-1 语言环境中,U+00A0 不间断空格被视为空白,包括 Solaris、BSD 和 OS X(我认为不正确)。如果您正在处理任意未知的区域设置,它可能包括任何内容,包括字母,所以祝您好运。
可以想象,可能会出现被视为空白的单个字节之内一个非空白的多字节字符,除了将整个字符放在引号中之外,您没有办法逃脱它。这不是理论上的问题:在上面的 ISO-8859-1 语言环境中,A0
可能会出现被视为空白的字节之内多字节字符,例如 UTF-8 编码的“à”( C3 A0
)。为了安全地处理这些字符,您需要引用它们"à"
。此行为取决于运行脚本的环境中的区域设置配置,而不是您编写脚本的环境中的区域设置配置。
我认为这种行为可以通过多种方式被打破,但我们必须按照我们所发的牌来打。如果您正在使用任何非自同步多字节字符集,最安全的做法是引用所有内容。如果您使用 UTF-8 或 C,那么您(目前)是安全的。
答案2
在 GNU Parallel 中,这已经过测试并被广泛使用:
$a =~ s/[\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\^\*\<\=\>\~\|\; \"\!\$\&\'\202-\377]/\\$&/go;
# quote newline as '\n'
$a =~ s/[\n]/'\n'/go;
它在bash
、dash
、ash
、ksh
、zsh
和中进行了测试fish
。某些字符在某些(版本)shell 中不需要引用,但上述内容在所有经过测试的 shell 中都有效。
如果您只想引用一个字符串,可以将其通过管道传输到parallel --shellquote
:
printf "&*\t*!" | parallel --shellquote
答案3
对于 Perl 中的轻量级转义解决方案,我遵循单引号的原则。单引号中的 Bash 字符串可以包含任何字符,但单引号本身除外。
我的代码:
my $bash_reserved_characters_re = qr([ !"#$&'()*;<>?\[\\`{|~\t\n]);
while(<>) {
if (/$bash_reserved_characters_re/) {
my $quoted = s/'/'"'"'/gr;
print "'$quoted'";
} else {
print $_;
}
}
示例运行 1:
$ echo -n "abc" | perl escape_bash_special_chars.pl
abc
示例运行 2:
echo "abc" | perl escape_bash_special_chars.pl
'abc
'
示例运行 3:
echo -n 'ab^c' | perl escape_bash_special_chars.pl
ab^c
示例运行 4:
echo -n 'ab~c' | perl escape_bash_special_chars.pl
'ab~c'
示例运行 5:
echo -n "ab'c" | perl escape_bash_special_chars.pl
'ab'"'"'c'
echo 'ab'"'"'c'
ab'c