Por que o dash expande \\\\ de forma diferente para o bash?

7

Eu tenho um pequeno projeto de código aberto que, por várias razões, tentei escrever em shell script razoavelmente portátil. Seus testes de integração automatizados verificam se caracteres hostis em expressões de caminho são tratados adequadamente, entre outras coisas.

Os usuários com /bin/sh fornecidos por bash estão vendo uma falha em um teste que simplificamos até o seguinte:

echo "A bug\'s life"
echo "A bug\\'s life"

No bash, produz esse resultado esperado:

A bug\'s life
A bug\'s life

Com o traço, que desenvolvi contra, ele faz isso:

A bug\'s life
A bug\'s life

Eu gostaria de pensar que não encontrei um bug no traço, que talvez eu esteja sentindo falta de algo. Existe uma explicação racional para isso?

    
por csirac2 06.11.2016 / 13:27

2 respostas

11

Em

echo "A bug\'s life"

Como essas são aspas duplas, e \ é especial entre aspas duplas, o primeiro \ é entendido pelo shell como escapando / citando o segundo% código%. Portanto, um argumento \ está sendo passado para A bug\'s life .

echo "A bug\'s life"

Teria alcançado exatamente o mesmo. echo não é especial dentro de aspas duplas, o ' não é removido, então é exatamente o mesmo argumento que é passado para \ .

Como explicado em Por que printf é melhor que echo? , há muita variação entre echo implementations.

Em implementações compatíveis com Unix como echo , dash é usado para introduzir seqüências de escape: \ para nova linha, \n para retrocesso, \b para sequências octal ... e 23 para backslash em si.

Alguns (não POSIX) requerem uma opção \ para isso, ou somente quando estão no modo de conformidade (como -e quando construído com as opções certas, como para bash do OS / X ou quando chamado com sh no ambiente).

Então, no padrão (somente padrão Unix; POSIX deixa o comportamento não especificado) SHELLOPTS=xpg_echo s,

echo '\'

o mesmo que:

echo "\\"

gera uma barra invertida, enquanto em echo quando não está no modo de conformidade:

echo '\'

produzirá duas barras invertidas.

É melhor evitar bash e usar echo :

$ printf '%s\n' "A bug\'s life"
A bug\'s life

O que funciona da mesma forma nesta instância em todas as implementações printf .

    
por 06.11.2016 / 22:42
2

O problema do echo e do printf está relacionado apenas ao entendimento de que um caractere entre aspas é um "caractere especial".

O mais simples é com uma string em printf '%s' "$string" .
Neste caso, não há caracteres especiais para processar e tudo o que o comando printf recebe no segundo argumento é impresso como está.

Observe que apenas aspas simples são usadas:

$ printf '%s\n' '\\\\\T '       # nine \
\\\\\T                          # nine \

Quando a string é usada como o primeiro argumento, alguns caracteres são especiais.
Um par \ representa um único \ e um \T a único T :

$ printf '\\\\\T '              # nine \
\\T                               # four \

Cada um dos quatro pares de \ transformados em um único \ e o último \T em um T .

$ printf '\\\\\a '              # nine \
\\                                # four \

Cada um dos quatro pares de \ transformados em um único caractere \ e o último \a em um sino (BEL) (não imprimível).

O mesmo acontece com algumas implementações de eco.

A implementação do traço sempre transforma caracteres especiais de contrabarra.

Se colocarmos esse código em um script:

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

Em seguida, o traço será impresso ( 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 |>

As duas primeiras colunas serão as mesmas (printf) para todos os shells.
Os outros dois serão alterados com a implementação específica do eco usado.

Por exemplo: ash ./script (busybox ash):

<|\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 |   >

Se o caractere usado for um a , para traço:

<|\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 |\\ | >

E para o 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 | >        <|\\ |    >

Para isso, nós temos que adicionar a interpretação de que o shell era que os comandos estão sendo executados também podem se aplicar à string de caracteres.

$ printf '%s\n' '\\T '
\\T
$ printf '%s\n' "\\T "
\T

Observe que o shell executa alguma ação na barra invertida entre aspas duplas.

Com este código:

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

Ambos os efeitos são adicionados e obtemos isso:

$ 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 \ 

Para traços, é ainda mais grave:

$ 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 \ 
    
por 07.11.2016 / 21:54