为什么 dash 的 echo 扩展 \\\\ 与 bash 的 echo 不同?

为什么 dash 的 echo 扩展 \\\\ 与 bash 的 echo 不同?

我有一个小型开源项目,出于各种原因,我尝试用相当可移植的 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) 要求它输出。echoecho -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 \ 

相关内容