Warum erweitert sich dash \\\\ anders als bash?

7

Ich habe ein kleines Open-Source-Projekt, das ich aus verschiedenen Gründen versucht habe, in einem einigermaßen portablen Shell-Skript zu schreiben. Die automatisierten Integrationstests prüfen unter anderem, ob feindliche Zeichen in Pfadausdrücken richtig behandelt werden.

Benutzer mit /bin/shbereitgestellt von bashsehen einen Fehler in einem Test, den ich auf Folgendes vereinfacht habe:

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

Bei Bash wird das erwartete Ergebnis erzielt:

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

Mit dash, gegen das ich mich entwickelt habe, macht es das:

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

Ich würde gerne denken, dass ich keinen Fehler im Bindestrich gefunden habe, dass mir stattdessen möglicherweise etwas fehlt. Gibt es dafür eine rationale Erklärung?

csirac2
quelle
2
Das ist sehr hilfreich. Obwohl es die \\\\ Sequenz nicht direkt abdeckt, erklärt es, warum die Dinge möglicherweise so sind, wie sie im Bindestrich sind, und verweist auf die Lösung: Verwenden Sie printf, das bei der Behandlung von Escape-Sequenzen portabler ist. Danke
csirac2
1
Es sollte eher sein, warum sich die Bash-Ausgabe von der Bash-Ausgabe unterscheidet. . Es ist die Dash-Version, die hier Standard ist.
Stéphane Chazelas

Antworten:

11

Im

echo "A bug\\'s life"

Da es sich um doppelte Anführungszeichen handelt und diese in doppelten Anführungszeichen \besonders sind, wird die erste \von der Shell als Escape / Quoting der zweiten verstanden \. Es wird also ein A bug\'s lifeArgument an weitergegeben echo.

echo "A bug\'s life"

Hätte genau das gleiche erreicht. Da dies 'in doppelten Anführungszeichen nicht besonders ist, wird das \nicht entfernt, sodass es genau das gleiche Argument ist, an das übergeben wird echo.

Wie unter Warum ist printf besser als Echo erklärt? Es gibt viele Unterschiede zwischen den echoImplementierungen.

In Unix-konformen Implementierungen wie dash's \wird verwendet, um Escape-Sequenzen einzuführen: \nfür Newline, \bfür Backspace, \0123für Oktalsequenzen ... und \\für Backslash selbst.

Einige (Nicht-POSIX-) erfordern eine -eOption dafür oder tun dies nur, wenn sie sich im Konformitätsmodus befinden (wie bashwenn sie mit den richtigen Optionen wie für shOS / X erstellt wurden oder wenn sie SHELLOPTS=xpg_echoin der Umgebung aufgerufen werden).

Im Standard (nur Unix-Standard; POSIX lässt das Verhalten nicht spezifiziert) echos,

echo '\\'

gleich wie:

echo "\\\\"

gibt einen Backslash aus, bashwenn Sie sich nicht im Konformitätsmodus befinden:

echo '\\'

gibt zwei Backslashes aus.

Am besten vermeiden echound printfstattdessen verwenden:

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

Was in diesem Fall in allen printfImplementierungen gleich funktioniert .

Stéphane Chazelas
quelle
2

Das Problem für echo und printf hängt nur mit dem Verständnis zusammen, wenn ein rückseitig zitiertes Zeichen ein "Sonderzeichen" ist.

Am einfachsten ist es mit einem String in printf '%s' "$string".
In diesem Fall müssen keine Sonderzeichen verarbeitet werden, und alles, was der Befehl printf im zweiten Argument empfängt, wird unverändert gedruckt.

Beachten Sie, dass nur einfache Anführungszeichen verwendet werden:

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

Wenn die Zeichenfolge als erstes Argument verwendet wird, sind einige Zeichen speziell.
Ein \\Paar repräsentiert eine Single \und \Teine Single T:

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

Jedes der vier Paare \\verwandelte sich in ein einzelnes \und das letzte \Tin ein T.

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

Jedes der vier Paare \\verwandelt sich in ein einzelnes \und das letzte \ain eine Glocke (BEL) (nicht druckbar).

Das gleiche passiert mit einigen Implementierungen von Echo.

Die Dash-Implementierung transformiert immer spezielle Backslash-Zeichen.

Wenn wir diesen Code in ein Skript einfügen:

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

Dann wird der Bindestrich gedruckt ( 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 |>

Die ersten beiden Spalten sind für alle Shells gleich (printf).
Die beiden anderen ändern sich mit der spezifischen Implementierung des verwendeten Echos.

Zum Beispiel: ash ./script(Busybox Asche):

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

Wenn das verwendete Zeichen ein ist a, für Bindestrich:

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

Und für 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 | >        <|\\\\ |    >

Dazu müssen wir die Interpretation hinzufügen, dass die Shell, in der die Befehle ausgeführt werden, möglicherweise auch für die Zeichenfolge gilt.

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

Beachten Sie, dass die Shell eine Aktion für den Backslash in doppelten Anführungszeichen ausführt.

Mit diesem Code:

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

Beide Effekte werden hinzugefügt und wir erhalten Folgendes:

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

Für Dash ist es noch schwerer:

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