Warum verschlingt Shell Command Substitution ein abschließendes Newline-Zeichen?

25

Wie im folgenden Beispiel und wie in meiner letzten Frage In Bash, wo ist das nachgestellte Newline-Zeichen geblieben? Ich möchte wissen, "warum" es passiert

  x="$(echo -ne "a\nb\n")" ; echo -n "$x" | xxd -p 
# Output is: 610a62 
# The trailing newline from the 'echo' command
#   has been "deleted" by Command Substitution

Ich gehe davon aus, dass es einen sehr wichtigen Grund für eine Shell-Aktion geben muss, nämlich die Befehlsersetzung, um tatsächlich einige Daten aus der Befehlsausgabe zu löschen, die sie ersetzt ...
aber ich kann mich nicht darum kümmern , wie es scheint das genaue Gegenteil von dem, was es soll .. also tun. Die Ausgabe eines Befehls zurück in den Skriptprozess zu leiten ... Ein Zeichen zurückzuhalten, erscheint mir seltsam, aber ich nehme an, es gibt einen vernünftigen Grund dafür ... Ich bin gespannt darauf, herauszufinden, was dieser Grund ist. .

Peter.O
quelle

Antworten:

22

Weil die Shell ursprünglich nicht als vollständige Programmiersprache gedacht war.

Es ist ziemlich schwierig, ein Trailing \naus einer Befehlsausgabe zu entfernen . Zu Anzeigezwecken beenden jedoch fast alle Befehle ihre Ausgabe mit \n, daher muss es eine einfache Möglichkeit geben, sie zu entfernen, wenn Sie sie in einem anderen Befehl verwenden möchten. Die automatische Entfernung mit der $()Konstruktion war die gewählte Lösung.

Vielleicht akzeptieren Sie diese Frage als Antwort:

Können Sie einen einfachen Weg finden, um das Trailing zu entfernen, \nwenn dies im folgenden Befehl nicht automatisch gemacht wurde?

> echo The current date is "$(date)", have a good day!

Beachten Sie, dass Anführungszeichen erforderlich sind, um das Zerschlagen von doppelten Leerzeichen zu verhindern, die in formatierten Daten auftreten können.

Stéphane Gimenez
quelle
1
Wenn das, was Sie sagen, wahr ist, wäre ich absolut fassungslos. Wenn es eine shoptMöglichkeit gibt , Ihr beschriebenes Standardverhalten zu ändern, würde ich mich wieder freuen ... Ich finde es schwierig zu glauben, dass Bash / Linux / Unix eine so wichtige Funktion wie "stdout erfassen" verschmutzen würde, nur weil datedies nicht der Fall ist a with / without '\ n' Option ... Die offensichtliche Lösung wäre, dass Bash (oder eine andere Shell) eine shoptfür diese hat ... Kennen Sie eine? .. und haben Sie Verweise, die über das Warum und Warum dieser Ausgabe sprechen? ... und warum gibt es keine date\ n Option .. Es sollte!
Peter.O
2
Befehlsersetzungen tauchten 1979 in der ersten Version der Bourne-Shell in der "7. Edition" von Unix auf. Es war bereits eine große Revolution, und ich glaube (ich wurde nicht geboren), dass es niemandem etwas ausmachte, das '\ n' zu behalten oder nicht. Ja, Sie können die Manpage in weniger als 10 Minuten lesen, und anscheinend hat sich bis dahin nichts an der Befehlsersetzung geändert. Mit Ausnahme der bevorzugten $()alternativen Syntax.
Stéphane Gimenez
Vielen Dank. Ich dachte, dies könnte ein altes Problem sein, und es hängt mit der Tatsache zusammen, dass ich besser anfangen sollte, meine Kommandosubstitutionen genauer zu beobachten. Bis heute muss ich auf einem Flügel überlebt und gebetet haben. Einige der "mysteriösen Ereignisse", denen ich begegnet bin, machen jetzt langsam Sinn Ich möchte meinen Trailing behalten \ n, ich muss so etwas codieren :( { echo -n "The current date is "; var="$(date; echo -e x)"; var="${var%x}"; echo -n "$var"; echo ", have a good day!"; }.. also sei es :)
Peter.O
PS zu meinem obigen Kommentar: Natürlich datewürde nur funktionieren, aber ich habe nur dateals Beispiel für einen Befehl verwendet ..
Peter.O
Ihre "Frage" nach dem Datum war der stärkste Anstoß für mein Verständnis, warum "Command Substitution" das tut, was es tut. Daher war dies die "beste" Antwort auf meine Frage ... und ich brauchte sicherlich auch die anderen guten Antworten ..
Peter.O
19

Es ist Teil des Standards :

Die Shell soll die Befehlsersetzung erweitern, indem sie den Befehl in einer Subshell-Umgebung ausführt (siehe Shell Execution Environment ) und die Befehlsersetzung (den Befehlstext plus das einschließende "$ ()" oder Anführungszeichen) durch die Standardausgabe des Befehls ersetzt und entfernt Folgen einer oder mehrerer <newline> am Ende der Ersetzung.

Natürlich ist der Standard wahrscheinlich so geschrieben, weil kshes so oder so war, aber es ist der Standard und dokumentiertes Verhalten. Wenn Sie es nicht mögen, verwenden Sie Perl oder etwas anderes, mit dem Sie die nachgestellten Zeilenumbrüche beibehalten können.

Steven Pritchard
quelle
Eine schöne Zusammenfassung .. Danke .. und die Links sicherlich geholfen
Peter.O
4
Der Standard ist so, denn so hat es die Bourne-Shell und seitdem jede andere Shell gemacht.
Gilles 'SO- hör auf böse zu sein'
6

Nun, es macht Sinn für mich. Die neue Zeile ist in der normalen Befehlsausgabe nur an erster Stelle vorhanden, sodass die Eingabeaufforderung angezeigt wird, nachdem der Befehl in einer neuen Zeile ausgeführt wurde. Die neue Zeile ist in den meisten Fällen nicht Teil der ursprünglichen Ausgabe, sondern dient zum Aufräumen des Bildschirms. Wenn Sie die Ausgabe eines Befehls analysieren, ist der Zeilenumbruch am Ende normalerweise problematisch. Warum gibt der wcBefehl 2 Textzeilen aus? Oh, tut es nicht, es gibt eine Zeile gefolgt von einer neuen Zeile aus. Wenn Sie analysieren wc, möchten Sie sich keine Sorgen darüber machen, dass es zwei Ausgabezeilen gibt - es gibt eigentlich keine, es gibt nur eine.

EightBitTony
quelle
@EightBitTony: Meine Frage hat in keiner Weise etwas mit der Anzeige im Terminal zu tun . Das ist ganz einfach. Wenn eine neue Zeile angezeigt werden soll, wird die neue Zeile angezeigt. Der fragliche Punkt ist, dass ein \nim ursprünglichen stdoutStream durch Befehlssubstitution entfernt wird. dh Bei meinen ursprünglichen Daten (sehr wichtigen Daten) wurden nachgestellte Zeilenumbrüche entfernt, auch wenn die Daten in "Anführungszeichen" gesetzt sind. Ich einigermaßen verstehen , wie und warum Wort Splitting funktioniert, aber ich habe absolut keine Ahnung , warum Befehl Substitution nur Streifen Zeilenumbrüche und nur Hinter denjenigen.
Peter.O
1
Nichts, was Sie gesagt haben, ändert meine Ansicht. In den meisten Fällen dient der letzte Zeilenumbruch in einer Ausgabe lediglich Anzeigezwecken und ist nicht Teil der Daten.
EightBitTony
Ich stimme Ihrer Ansicht zu und habe die ganze Zeit über ... Ja, der Zeilenumbruch am Ende der Ausgabe dient der Vereinfachung ... aber mein Problem hat nichts damit zu tun ... Ich frage: Warum wird er von der Befehlsersetzung zwangsweise entfernt? ? ... Dies sind zwei verschiedene Probleme. Vielleicht sollte meine Frage lauten: Gibt es eine integrierte Problemumgehung? . weil für dieses Problem Code durch Hinzufügen eines abschließenden Dummy-Zeichens erforderlich ist, ist zum Kotzen! ... Ich werde es tun, wenn ich muss, aber dies ist das erste Mal, dass ich von einer Bash gebannt wurde (und ich bin in einem Schockzustand :) .. Es macht so viel so gut ...
Peter.O
Wie in einer anderen Antwort erwähnt, ist dies Teil des Standards. Ich denke, das funktioniert so, denn wenn Sie Daten nachbearbeiten, möchten Sie nur die Daten sehen, nicht den bequemen Zeilenumbruch. Das liegt daran, dass die Shell erwartet , dass Sie die Daten in einer Pipe verarbeiten, und dass die Zeilenumbruchzeile keine Daten sind. Mehr und wir müssen dies zu einer Diskussion bringen.
EightBitTony
Danke. Ich habe die Idee jetzt ziemlich gut verstanden. Es scheint ein Fall von Befehlssubstitution zu sein, der einfach darauf abzielt, die nachgestellten Zeilen zu löschen, anstatt sie zu pflegen "noch, aber ich dachte, dass die überwältigende populäre Verwendung aller Groß- und Kleinschreibung in Dateinamen ein bisschen seltsam / unnötig war, aber ich dachte erst neulich, dass es tatsächlich ein ziemlich komfortables System ist. Ich werde es wahrscheinlich tun siehe (genauer) den Reim und den Grund für das Verhalten von Command Substitution eines Tages ...
Peter.O
1

Ich bin auf dasselbe Problem gestoßen und habe das folgende Beispiel gefunden. Anscheinend hat das Zitieren der Situation geholfen, zumindest für mich:

http://tldp.org/LDP/abs/html/commandsub.html .

dir_listing=`ls -l`
echo $dir_listing     # unquoted

# Expecting a nicely ordered directory listing.

# However, what you get is:
# total 3 -rw-rw-r-- 1 bozo bozo 30 May 13 17:15 1.txt -rw-rw-r-- 1 bozo
# bozo 51 May 15 20:57 t2.sh -rwxr-xr-x 1 bozo bozo 217 Mar 5 21:13 wi.sh

# The newlines disappeared.


echo "$dir_listing"   # quoted
# -rw-rw-r--    1 bozo       30 May 13 17:15 1.txt
# -rw-rw-r--    1 bozo       51 May 15 20:57 t2.sh
# -rwxr-xr-x    1 bozo      217 Mar  5 21:13 wi.sh
christian_hercules
quelle
2
Hier ist was hier passiert. Angenommen, Sie haben eine Variable $str, die die Zeichenfolge enthält "a b c". Wenn Sie passieren $strzu echoohne Anführungszeichen ( echo $str), echoerhalten a, bund cals drei getrennte Argumente. Das Leerzeichen zwischen den Argumenten ist Ihrer Shell egal. echoDiese Argumente werden dann mit Leerzeichen zwischen den einzelnen Argumenten gedruckt, sodass Sie Folgendes erhalten "a b c". Wenn Sie die Variable echomit Anführungszeichen ( echo "$str") übergeben, sieht die Shell sie als ein Argument und echoempfängt sie als solches, sodass das Leerzeichen nicht reduziert wird. Gleiches gilt für Zeilenumbrüche.
1
Das Problem, das das Original-Poster hat, ist, dass die abschließenden Zeilenumbrüche (nur die allerletzten) seiner Befehle verschwinden. Wenn das -nFlag nicht übergeben wird, wird der echoAusgabe eine abschließende neue Zeile hinzugefügt, sodass beide Echos in Ihrem Beispiel abschließende neue Zeilen haben. Wenn Sie jedoch echo -n $dir_listingoder versuchen echo -n "$dir_listing", werden Sie feststellen, dass keine nachgestellten Zeilen mit oder ohne Anführungszeichen gedruckt werden $dir_listing, was darauf hindeutet, dass das Problem bei der Befehlsersetzung liegt.
2
tldp ist ein schlecht veralteter Ort, um etwas über Shell- Skripte zu lernen. Versuchen Sie stattdessen das Wooledge Bash Wiki. mywiki.wooledge.org/BashGuide
Wildcard
-1

warum echo "hello "verschlingt (ohne die Anführungszeichen) den Raum? Der Wert von IFS-Zeichen wird von der Shell als Begrenzer angesehen, daher werden die nachfolgenden Zeichen (die nichts begrenzen) entfernt. Die commend-Ersetzung führt lediglich die commends in einer Sub-Shell aus, daher folgt sie derselben Logik.

Philomath
quelle
@Philomath: Beide x="hello  "und x="hello     "verlieren alle ihre Leerzeichen, wenn sie über echo $x... angezeigt werden . Es geht jedoch nur die allerletzte Zeile der Befehlsersetzung verloren.
Peter.O
seltsam, ich sehe keinen Unterschied in meiner Bash (Überprüfung mit xxd)
Philomath
Ich überprüfe das nur ... Es entfernt alle nachgestellten Zeilenumbrüche ... Ich habe mit IFS experimentiert, als ich diesen Eindruck hatte, also lassen Sie uns diesen abbrechen :) .. aber die Frage bleibt immer noch ... Es ist ein grundlegender Teil Wortteilung, um mehrere Leerzeichen zu einem einzigen Leerzeichen zusammenzufassen und führende und nachfolgende Leerzeichen vollständig zu entfernen. Ich kann das verstehen, aber ich verstehe mit Sicherheit nicht, warum bei der Befehlsersetzung nur \ n und nicht alle Leerzeichen entfernt werden (wie mit Worttrennung) und warum nur das Ende? .. und vor allem: "Command Substitution" ersetzt nur teilweise .. Warum?
Peter.O
4
IFShat nichts mit dem Entfernen von Zeilenumbrüchen am Ende von Befehlsersetzungen zu tun.
Gilles 'SO- hör auf böse zu sein'