Was ist falsch an "echo $ (stuff)" oder "echo` stuff` "?

31

Ich habe eine der folgenden verwendet

echo $(stuff)
echo `stuff`

(wo stuffist zB pwdoder dateoder etwas komplizierter).

Dann wurde mir gesagt, diese Syntax sei falsch, eine schlechte Praxis, nicht elegant, übertrieben, überflüssig, übermäßig kompliziert, eine Frachtkult-Programmierung, noobisch, naiv usw.

Aber der Befehl funktioniert , also was genau ist daran falsch?

Kamil Maciorowski
quelle
11
Es ist redundant und sollte daher nicht verwendet werden. Vergleichen Sie mit Useless Use Of Cat: porkmail.org/era/unix/award.html
dr01
7
echo $(echo $(echo $(echo$(echo $(stuff)))))auch tut Arbeit, und immer noch würden Sie wahrscheinlich nicht verwenden, nicht wahr? ;-)
Peter - Reinstate Monica
1
Was genau versuchst du mit dieser Erweiterung zu tun?
neugierig
@curiousguy Ich versuche, anderen Benutzern zu helfen, daher meine Antwort unten. In letzter Zeit habe ich mindestens drei Fragen gesehen, die diese Syntax verwenden. Ihre Autoren versuchten ... verschiedene stuff. : D
Kamil Maciorowski
2
Sind wir uns nicht alle einig, dass es unklug ist, sich auf eine Echokammer zu beschränken? ;-)
Peter - Reinstate Monica

Antworten:

63

tl; dr

Eine Sohle stuffwürde höchstwahrscheinlich für Sie arbeiten.



Volle Antwort

Was geschieht

Wenn Sie rennen foo $(stuff), passiert Folgendes:

  1. stuff läuft;
  2. seine Ausgabe (stdout) ersetzt, anstatt gedruckt zu werden, $(stuff)beim Aufruf von foo;
  3. Wenn es foodann ausgeführt wird, hängen seine Befehlszeilenargumente offensichtlich davon ab, was stuffzurückgegeben wurde.

Dieser $(…)Mechanismus wird "Befehlssubstitution" genannt. In Ihrem Fall ist der Hauptbefehl, echoder im Grunde seine Befehlszeilenargumente an stdout ausgibt. Was auch immer stuffversucht, auf stdout zu drucken, wird erfasst, an stdout übergeben echound von auf stdout gedruckt echo.

Wenn Sie möchten, dass die Ausgabe von stuffauf stdout gedruckt wird, führen Sie einfach die Sohle aus stuff.

Die `…`Syntax dient demselben Zweck wie $(…)(unter demselben Namen: "Befehlssubstitution"), es gibt jedoch nur wenige Unterschiede, sodass Sie sie nicht blind austauschen können. Siehe diese FAQ und diese Frage .


Sollte ich echo $(stuff)egal was vermeiden ?

Es gibt einen Grund, den Sie verwenden möchten, echo $(stuff)wenn Sie wissen, was Sie tun. Aus dem gleichen Grund sollten Sie vermeiden, echo $(stuff)wenn Sie nicht wirklich wissen, was Sie tun.

Der Punkt ist stuffund echo $(stuff)sind nicht genau gleichwertig. Letzteres bedeutet, dass der Operator split + glob für die Ausgabe von stuffmit dem Standardwert von aufgerufen wird $IFS. Doppelte Anführungszeichen für die Befehlsersetzung verhindern dies. Wenn die Befehlsersetzung in einfache Anführungszeichen gesetzt wird, handelt es sich nicht mehr um eine Befehlsersetzung.

Um dies beim Aufteilen zu beobachten, führen Sie die folgenden Befehle aus:

echo "a       b"
echo $(echo "a       b")
echo "$(echo "a       b")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "a       b")'

Und zum Verschlingen:

echo "/*"
echo $(echo "/*")
echo "$(echo "/*")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "/*")'

Wie Sie sehen, echo "$(stuff)"ist äquivalent (-ish *) zu stuff. Sie könnten es benutzen, aber was bringt es, die Dinge auf diese Weise zu komplizieren?

Auf der anderen Seite , wenn Sie wollen die Ausgabe von stuffSplitting + zu unterziehen Globbing dann Sie können finden echo $(stuff)nützlich. Es muss jedoch Ihre bewusste Entscheidung sein.

Es gibt Befehle, die eine Ausgabe generieren, die ausgewertet werden sollte (einschließlich Teilen, Verschieben und mehr) und von der Shell ausgeführt eval "$(stuff)"wird. Dies ist eine Möglichkeit (siehe diese Antwort ). Ich habe noch nie einen Befehl gesehen, dessen Ausgabe vor dem Drucken einer zusätzlichen Aufteilung + Globe unterzogen werden muss . Bewusstes Verwenden echo $(stuff)scheint sehr ungewöhnlich.


Was ist var=$(stuff); echo "$var"?

Guter Punkt. Dieser Ausschnitt:

var=$(stuff)
echo "$var"

sollte äquivalent sein zu echo "$(stuff)"äquivalent (-ish *) zu stuff. Wenn es sich um den gesamten Code handelt, führen Sie ihn stuffstattdessen aus.

Wenn Sie die Ausgabe jedoch stuffmehrmals verwenden müssen, verwenden Sie diesen Ansatz

var=$(stuff)
foo "$var"
bar "$var"

ist normalerweise besser als

foo "$(stuff)"
bar "$(stuff)"

Selbst wenn dies der Fall fooist echound Sie echo "$var"in Ihren Code aufgenommen werden, ist es möglicherweise besser, dies so zu belassen. Dinge, die man beachten muss:

  1. Mit var=$(stuff) stuffläuft einmal; Selbst wenn der Befehl schnell ist, ist es das Richtige, zu vermeiden, dass dieselbe Ausgabe zweimal berechnet wird. Oder hat möglicherweise stuffandere Auswirkungen als das Schreiben in stdout (z. B. Erstellen einer temporären Datei, Starten eines Dienstes, Starten einer virtuellen Maschine, Benachrichtigen eines Remoteservers), sodass Sie ihn nicht mehrmals ausführen möchten.
  2. Wenn stuffzeitabhängige oder etwas zufällige Ausgaben generiert werden, erhalten Sie möglicherweise inkonsistente Ergebnisse von foo "$(stuff)"und bar "$(stuff)". Nach var=$(stuff)dem Wert $varfestgelegt ist , und Sie können sicher sein , foo "$var"und bar "$var"identisches Befehlszeilenargument bekommen.

In einigen Fällen foo "$var"möchten (müssen) Sie möglicherweise anstelle von verwenden foo $var, insbesondere wenn mehrere Argumente für stuffgeneriert werden (eine Array-Variable ist möglicherweise besser, wenn Ihre Shell dies unterstützt). Weißt du nochmal, was du tust? Wenn es um den Unterschied zwischen und geht, ist er der gleiche wie zwischen und .fooechoecho $varecho "$var"echo $(stuff)echo "$(stuff)"


* Äquivalent ( -ish )?

Ich sagte echo "$(stuff)"ist gleichbedeutend mit stuff. Es gibt mindestens zwei Probleme, die es nicht genau gleichwertig machen:

  1. $(stuff)läuft stuffes in einer Subshell, so besser zu sagen , echo "$(stuff)"entspricht (ish) zu (stuff). Befehle, die sich auf die Shell auswirken, in der sie ausgeführt werden, wirken sich in einer Subshell nicht auf die Hauptshell aus.

    In diesem Beispiel stuffist a=1; echo "$a":

    a=0
    echo "$(a=1; echo "$a")"      # echo "$(stuff)"
    echo "$a"
    

    Vergleichen Sie es mit

    a=0
    a=1; echo "$a"                # stuff
    echo "$a"
    

    und mit

    a=0
    (a=1; echo "$a")              # (stuff)
    echo "$a"
    

    Beginnen Sie mit stuffeinem anderen Beispiel cd /; pwd:

    cd /bin
    echo "$(cd /; pwd)"           # echo "$(stuff)"
    pwd
    

    und Test stuffund (stuff)Versionen.

  2. echoist kein gutes Werkzeug, um unkontrollierte Daten anzuzeigen . Das, echo "$var"worüber wir gesprochen haben, hätte sein sollen printf '%s\n' "$var". Aber da die Frage erwähnt echound die wahrscheinlichste Lösung darin besteht, sie überhaupt nicht zu verwenden echo, habe ich mich entschlossen, printfsie bis jetzt nicht vorzustellen .

  3. stuffoder (stuff)verschachtelt echo $(stuff)die Ausgabe von stdout und stderr, während die gesamte Ausgabe von stderr stuff(die zuerst ausgeführt wird) und erst dann die Ausgabe von stdout gedruckt wird, die von echo(die zuletzt ausgeführt wird) verdaut wurde .

  4. $(…)Entfernt alle nachfolgenden Zeilenumbrüche und echofügt sie dann wieder hinzu. So echo "$(printf %s 'a')" | xxdergibt sich eine andere Ausgabe als printf %s 'a' | xxd.

  5. Einige Befehle ( lszum Beispiel) funktionieren unterschiedlich, je nachdem, ob die Standardausgabe eine Konsole ist oder nicht. so ls | cattut es nicht dasselbe ls. Ähnlich echo $(ls)wird anders funktionieren als ls.

    Putting lsin einem allgemeinen Fall beiseite, wenn Sie das andere Verhalten zu zwingen , haben dann stuff | catals besser echo $(ls)oder echo "$(ls)"weil es alle anderen Themen erwähnt hier nicht ausgelöst hat.

  6. Möglicherweise abweichender Exit-Status (zur Vervollständigung dieser Wiki-Antwort genannt; für Details siehe eine andere Antwort , die Anerkennung verdient).

Kamil Maciorowski
quelle
5
Ein weiterer Unterschied: Der stuffWeg wird verschachtelt stdoutund stderrausgegeben, während echo `stuff` die gesamte stderrAusgabe von stuffund erst dann die stdoutAusgabe gedruckt wird .
Ruslan
3
Und dies ist ein weiteres Beispiel dafür: Bash-Skripte können kaum zu viele Anführungszeichen enthalten. echo $(stuff)Sie können in weitaus mehr Schwierigkeiten geraten, als echo "$(stuff)"wenn Sie nicht explizit auf Globbing und Expansion verzichten möchten.
Rexkogitans
2
Noch ein $(…)kleiner Unterschied: Entferne alle abschließenden Zeilenumbrüche und füge sie dann wieder echohinzu. So echo "$(printf %s 'a')" | xxdergibt sich eine andere Ausgabe als printf %s 'a' | xxd.
Derobert
1
Sie sollten nicht vergessen, dass einige Befehle ( lszum Beispiel) unterschiedlich funktionieren, je nachdem, ob die Standardausgabe eine Konsole ist oder nicht. so ls | cattut es nicht dasselbe ls. Und natürlich echo $(ls)wird das anders funktionieren als ls.
Martin Rosenau
@ Ruslan Die Antwort ist jetzt Community-Wiki. Ich habe deinen nützlichen Kommentar zum Wiki hinzugefügt. Vielen Dank.
Kamil Maciorowski
5

Ein weiterer Unterschied: Der Exit-Code der Sub-Shell geht verloren, sodass echostattdessen der Exit-Code von abgerufen wird.

> stuff() { return 1
}
> stuff; echo $?
1
> echo $(stuff); echo $?
0
WaffelSouffle
quelle
3
Willkommen bei Super User! Können Sie den Unterschied erklären, den Sie demonstrieren? Danke
Bertieb
4
Ich glaube, dass dies zeigt, dass der Rückgabecode $?der Rückgabecode von echo(for echo $(stuff)) anstelle des von returned sein wird stuff. Die stuffFunktion return 1(Exit-Code), echosetzt sie aber unabhängig vom Return-Code von auf "0" stuff. Sollte trotzdem in der Antwort stehen.
Ismael Miguel
Der Rückgabewert der Subshell ist verloren gegangen
phuclv