我有一个小型开源项目,出于各种原因,我尝试用相当可移植的 shell 脚本编写该项目。其自动集成测试检查路径表达式中的敌对字符是否得到正确处理等。
/bin/sh
使用提供者提供的用户bash
在测试中看到失败,我已将其简化为以下内容:
echo "A bug\\'s life"
echo "A bug\\\\'s life"
在 bash 上,它会产生以下预期结果:
A bug\'s life
A bug\\'s life
使用我开发的 dash,它可以执行以下操作:
A bug\'s life
A bug\'s life
我想我还没有发现破折号中的错误,我可能会丢失一些东西。对此有合理的解释吗?
答案1
在
echo "A bug\\'s life"
因为它们是双引号,并且\
在双引号内是特殊的,所以第一个\
被 shell 理解为逃跑/引用第二\
。因此,一个A bug\'s life
参数被传递给echo
.
echo "A bug\'s life"
会达到完全相同的效果。'
由于双引号内并不特殊,因此\
不会删除 ,因此它与传递给 的参数完全相同echo
。
正如所解释的为什么 printf 比 echo 更好?echo
,实现之间存在很多差异。
在 UNIX 兼容的实现中,如dash
内置echo
的 ,\
用于引入转义序列:\n
用于换行符、\b
退格符、\0123
八进制序列......以及\\
反斜杠本身。
一些(非 POSIX)需要一个-e
选项,或者仅在一致性模式下才执行此操作(例如bash
使用正确的选项构建时,例如OS/X 的 或在环境中sh
调用时)。SHELLOPTS=xpg_echo
所以在标准中(仅限 Unix 标准;POSIX 未指定行为)echo
,
echo '\\'
与...一样:
echo "\\\\"
输出一反斜杠,当bash
不处于一致性模式时:
echo '\\'
将输出二反斜杠。
最好避免echo
并使用printf
:
$ printf '%s\n' "A bug\'s life"
A bug\'s life
在这种情况下,在所有实现中它的工作原理都是相同的printf
。
1在这方面是兼容的,但不输出dash
任何内容,而 UNIX 规范 (POSIX + XSI) 要求它输出。echo
echo -n
-n<newline>
答案2
echo 和 printf 的问题仅与理解反引号字符何时是“特殊字符”有关。
最简单的是使用 中的字符串printf '%s' "$string"
。
在这种情况下,没有需要处理的特殊字符,并且命令 printf 在第二个参数中接收到的所有内容都按原样打印。
请注意,仅使用单引号:
$ printf '%s\n' '\\\\\\\\\T ' # nine \
\\\\\\\\\T # nine \
当字符串用作第一个参数时,某些字符是特殊的。
一\\
对代表一个单一的\
和一个\T
单一的T
:
$ printf '\\\\\\\\\T ' # nine \
\\\\T # four \
四对中的每对都\\
转变为单个\
,最后\T
一个转变为T
。
$ printf '\\\\\\\\\a ' # nine \
\\\\ # four \
四对中的每一对都\\
转换为单个字符\
,最后一个转换\a
为响铃 (BEL) 字符(不可打印)。
同样的情况发生在一些echo 的实现。
破折号实现总是转换特殊的反斜杠字符。
如果我们将此代码放入脚本中:
set -- '\g ' '\\g ' '\\\g ' '\\\\g ' '\\\\\g ' '\\\\\\g ' '\\\\\\\g ' '\\\\\\\\g ' '\\\\\\\\\g '
for i ; do
printf '<%-14s> \t<%-9s> \t<%-14s> \t<%-12s>\n' \
"$(printf '%s ' "|$i|")" \
"$(printf "|$i|")" \
"$(echo "|$i|")" \
"$(echo -e "|$i|")" ;
done
然后,dash 将打印 ( dash ./script
):
<|\g | > <|\g | > <|\g | > <-e |\g | >
<|\\g | > <|\g | > <|\g | > <-e |\g | >
<|\\\g | > <|\\g | > <|\\g | > <-e |\\g | >
<|\\\\g | > <|\\g | > <|\\g | > <-e |\\g | >
<|\\\\\g | > <|\\\g | > <|\\\g | > <-e |\\\g | >
<|\\\\\\g | > <|\\\g | > <|\\\g | > <-e |\\\g | >
<|\\\\\\\g | > <|\\\\g | > <|\\\\g | > <-e |\\\\g | >
<|\\\\\\\\g | > <|\\\\g | > <|\\\\g | > <-e |\\\\g | >
<|\\\\\\\\\g | > <|\\\\\g |> <|\\\\\g | > <-e |\\\\\g |>
对于所有 shell,前两列都是相同的 (printf)。
另外两个会随着所使用的 echo 的具体实现而改变。
例如:ash ./script
(busybox灰):
<|\g | > <|\g | > <|\g | > <|\g | >
<|\\g | > <|\g | > <|\\g | > <|\g | >
<|\\\g | > <|\\g | > <|\\\g | > <|\\g | >
<|\\\\g | > <|\\g | > <|\\\\g | > <|\\g | >
<|\\\\\g | > <|\\\g | > <|\\\\\g | > <|\\\g | >
<|\\\\\\g | > <|\\\g | > <|\\\\\\g | > <|\\\g | >
<|\\\\\\\g | > <|\\\\g | > <|\\\\\\\g | > <|\\\\g | >
<|\\\\\\\\g | > <|\\\\g | > <|\\\\\\\\g | > <|\\\\g | >
<|\\\\\\\\\g | > <|\\\\\g |> <|\\\\\\\\\g | > <|\\\\\g | >
如果使用的字符是a
, 表示破折号:
<|\a | > <| | > <| | > <-e | | >
<|\\a | > <|\a | > <|\a | > <-e |\a | >
<|\\\a | > <|\ | > <|\ | > <-e |\ | >
<|\\\\a | > <|\\a | > <|\\a | > <-e |\\a | >
<|\\\\\a | > <|\\ | > <|\\ | > <-e |\\ | >
<|\\\\\\a | > <|\\\a | > <|\\\a | > <-e |\\\a | >
<|\\\\\\\a | > <|\\\ | > <|\\\ | > <-e |\\\ | >
<|\\\\\\\\a | > <|\\\\a | > <|\\\\a | > <-e |\\\\a | >
<|\\\\\\\\\a | > <|\\\\ | > <|\\\\ | > <-e |\\\\ | >
对于bash:
<|\a | > <| | > <|\a | > <| | >
<|\\a | > <|\a | > <|\\a | > <|\a | >
<|\\\a | > <|\ | > <|\\\a | > <|\ | >
<|\\\\a | > <|\\a | > <|\\\\a | > <|\\a | >
<|\\\\\a | > <|\\ | > <|\\\\\a | > <|\\ | >
<|\\\\\\a | > <|\\\a | > <|\\\\\\a | > <|\\\a | >
<|\\\\\\\a | > <|\\\ | > <|\\\\\\\a | > <|\\\ | >
<|\\\\\\\\a | > <|\\\\a | > <|\\\\\\\\a | > <|\\\\a | >
<|\\\\\\\\\a | > <|\\\\ | > <|\\\\\\\\\a | > <|\\\\ | >
为此,我们必须添加解释,即正在执行命令的 shell 也可能适用于字符串。
$ printf '%s\n' '\\\\T '
\\\\T
$ printf '%s\n' "\\\\T "
\\T
请注意,shell 对双引号内的反斜杠执行一些操作。
有了这个代码:
tab=' '
say(){ echo "$(printf '%s' "$a") $tab $(echo "$a") $tab $(echo -e "$a")"; }
a="one \a " ; say
a="two \\a " ; say
a="t33 \\\a " ; say
a="f44 \\\\a " ; say
a="f55 \\\\\a " ; say
a="s66 \\\\\\a " ; say
a="s77 \\\\\\\a " ; say
a="e88 \\\\\\\\a " ; say
a="n99 \\\\\\\\\a " ; say
两种效果都被添加,我们得到:
$ bash ./script
one \a one \a one
two \a two \a two
t33 \\a t33 \\a t33 \a
f44 \\a f44 \\a f44 \a
f55 \\\a f55 \\\a f55 \
s66 \\\a s66 \\\a s66 \
s77 \\\\a s77 \\\\a s77 \\a
e88 \\\\a e88 \\\\a e88 \\a
n99 \\\\\a n99 \\\\\a n99 \\
对于 dash 来说,情况更加严重:
$ dash ./script
one one -e one
two two -e two
t33 \a t33 -e t33
f44 \a f44 -e f44
f55 \ f55 \ -e f55 \
s66 \ s66 \ -e s66 \
s77 \\a s77 \a -e s77 \a
e88 \\a e88 \a -e e88 \a
n99 \\ n99 \ -e n99 \