Wo ist das nachgestellte Newline-Zeichen von meiner Befehlsersetzung verschwunden?

15

Der folgende Code beschreibt die Situation am besten. Warum gibt die letzte Zeile kein Zeilenumbruchzeichen aus? Die Ausgabe jeder Zeile wird im Kommentar angezeigt. Ich benutze GNU Bash, Version 4.1.5

     echo -n $'a\nb\n'                  | xxd -p  # 610a620a  
           x=$'a\nb\n'   ; echo -n "$x" | xxd -p  # 610a620a
     echo -ne "a\nb\n"                  | xxd -p  # 610a620a
x="$(echo -ne "a\nb\n")" ; echo -n "$x" | xxd -p  # 610a62
Peter.O
quelle
4
Problemumgehung für die seltenen Fälle, in denen es erforderlich ist:tmp=$(somecommand; echo a); tmp=${tmp%a}
Gilles 'SO - hör auf, böse zu sein'
1
@Gilles: Bezüglich Ihres obigen Beispiels: tmp=$(somecommand; echo a)... Dies hat sicherlich den Punkt nach Hause getrieben ... Bis ich das Beispiel gesehen habe, wäre meine Tendenz immer noch gewesen, es zu verwenden echo -n a... aber natürlich !, es besteht keine Notwendigkeit dafür die -n, weil durch die Befehlsersetzung die eingeführte abschließende Zeile auf jeden Fall entfernt wird! ... danke ...
Peter.O

Antworten:

18

Die Befehlsersetzungsfunktion $()(und ihre Cousine das Backtick) entfernt speziell nachfolgende Zeilenumbrüche. Dies ist das dokumentierte Verhalten und sollte bei der Verwendung des Konstrukts immer beachtet werden.

Zeilenumbrüche im Textkörper werden vom Ersetzungsoperator nicht entfernt. Sie können jedoch auch entfernt werden, wenn Sie eine Wortteilung in der Shell durchführen. Wie sich das herausstellt, hängt davon ab, ob Sie Anführungszeichen verwendet haben oder nicht. Beachten Sie den Unterschied zwischen diesen beiden Verwendungen:

$ echo -n "$(echo -n 'a\nb')"
a
b

$ !! | xxd -p
610a62

$ echo -n  $(echo -n 'a\nb')
a b

$ !! | xxd -p   
612062

Im zweiten Beispiel wurde die Ausgabe nicht in Anführungszeichen gesetzt und die neue Zeile als Worttrennung interpretiert, sodass sie in der Ausgabe als Leerzeichen angezeigt wird!

Caleb
quelle
1
Dank Caleb .. Ich war von Wort-Splitting bewusst Leerzeichen zu einem einzigen Raum zu ändern , wenn nicht notiert ... Deshalb hat ich am meisten überrascht war meine Newline zu sehen verschwinden , obwohl ich es zitiert hatte ... Jetzt bin ich mir bewusst , dass es ist wegen der ‚normalen‘ Verhalten der Befehl Substitution ein nachlauf newline fallen ... Oh gut, c'est la vie .. und danke für den Link
Peter.O
6

Bei Verwendung der Befehlssubstitution führt die Shell die Befehle in einer Subshell aus und gibt ihre Standardausgabe zurück. In diesem Prozess verlieren die IFS-Zeichen ihre Bedeutung (wenn sie nicht in Anführungszeichen gesetzt sind), da das Commend einfach aufgeteilte Wörter zurückgibt und die nachfolgenden entfernt werden. Beispielsweise:

$ echo "$(echo -e '\n')" | wc
 1       0       1

$ echo -e '\n' | wc
2       0       2

und praktischer, pwdwird funktionieren, auch wenn Ihr Verzeichnisname eine neue Zeile dazwischen hat, aber $(pwd)nicht.

Die übliche Problemumgehung besteht darin, am Ende Ihres Befehls etwas hinzuzufügen und es anschließend zu entfernen.

Philomath
quelle
1
Danke Philomath ... Ihr erstes Beispiel ist übrigens syntaktisch falsch ('wc' akzeptiert keinen String als Argument). Damit Ihre Beispiele Sinn machen, könnte das erste Beispiel sein echo "$(echo -e '\n')" | wc, welches 1   0   1im Vergleich zum ausgibt2   0   2
Peter.O
@fred: Hoppla, nur ein Tippfehler.
Philomath
1
"In Leerzeichen konvertiert" ist keine angemessene Erklärung, es handelt sich lediglich um eine Aufteilung. Insbesondere können Sie Leerzeichen aus IFS entfernen, die nicht als Trennzeichen fungieren. Außerdem fällt Ihr Beispiel in eine Sonderfallkategorie, und es gibt einen Unterschied zwischen $(pwd)und "$(pwd)", siehe Calebs Antwort.
Stéphane Gimenez
PS .. Ihr Vorschlag der üblichen Problemumgehung ist genau das, was ich brauchte, um dieses
Problem zu beheben
Re "in Leerzeichen umgewandelt", siehe Word Splitting
Peter.O