Was ist los mit "echo $ (stuff)" oder "echo` stuff "?

4282
Kamil Maciorowski

Ich habe eines der folgenden verwendet

echo $(stuff) echo `stuff` 

(wo stuffist zB pwdoder dateoder etwas komplizierter).

Dann wurde mir gesagt, dass diese Syntax falsch ist, eine schlechte Praxis, nicht elegant, übertrieben, überflüssig, übermäßig kompliziert, ein Cargo-Kult-Programm, Noobish, Naive usw.

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

30
Es ist redundant und sollte daher nicht verwendet werden. Vergleichen Sie mit Useless Use Of Cat: http://porkmail.org/era/unix/award.html dr01 vor 5 Jahren 11
`echo $ (echo $ (echo $ (echo $ (stuff))))` auch * funktioniert *, und trotzdem würden Sie es wahrscheinlich nicht verwenden, oder? ;-) Peter A. Schneider vor 5 Jahren 7
Was versuchen Sie genau mit dieser Erweiterung? curiousguy vor 5 Jahren 1
@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 Sachen zu machen. : D Kamil Maciorowski vor 5 Jahren 0
Sind wir uns nicht alle einig, dass es unklug ist, sich auf eine Echokammer zu beschränken? ;-) Peter A. Schneider vor 5 Jahren 2
Bedeutet das Tag [tag: sh] eine strikte `/ bin / sh`-Frage? curiousguy vor 5 Jahren 0
@curiousguy "Der Standard-Interpreter für die Befehlssprache". Ich habe dies hinzugefügt, um anzuzeigen, dass Shells kompatibel sind und / oder von `sh` abgeleitet sind (zB nicht [Tag: Powershell]). Es geht nicht um eine einzelne Schale; Es geht um das Konzept der Verwendung von "Echo" mit Befehlsersetzung auf diese Weise. Kamil Maciorowski vor 5 Jahren 0
@KamilMaciorowski Es gibt meist kompatible Shells mit Unterstützung für alle grundlegenden Konstrukte, jedoch mit manchmal unterschiedlicher Semantik. `zsh` ist interessant. curiousguy vor 5 Jahren 0
@curiousguy Was ist dein Punkt? Wie kann ich die Frage dann verbessern? Kamil Maciorowski vor 5 Jahren 0

2 Antworten auf die Frage

62
Kamil Maciorowski

tl; dr

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



Volle Antwort

Was geschieht

Wenn du rennst foo $(stuff), passiert Folgendes:

  1. stuff läuft;
  2. seine Ausgabe (stdout) ersetzt nicht $(stuff)den Ausdruck, sondern ersetzt den Aufruf von foo;
  3. dann ausgeführt foo, hängen die Befehlszeilenargumente offensichtlich von den zurückgegebenen Werten ab stuff.

Dieser $(…)Mechanismus wird als "Befehlssubstitution" bezeichnet. In Ihrem Fall gibt der Hauptbefehl die Befehlszeilenargumente echogrundsätzlich an stdout aus. Was immer stuffversucht, auf stdout zu drucken, wird erfasst, an echostdout von und von dort gedruckt echo.

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

Die `…`Syntax hat den gleichen Zweck wie $(…)(unter dem gleichen Namen: "Befehlsersetzung"), es gibt jedoch einige 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 demselben Grund sollten Sie es vermeiden, echo $(stuff)wenn Sie nicht wirklich wissen, was Sie tun.

Der Punkt ist stuffund echo $(stuff)ist nicht genau gleichwertig. Letzteres bedeutet, dass Sie den split + glob-Operator für die Ausgabe von stuffmit dem Standardwert von aufrufen $IFS. Ein doppeltes Anführungszeichen der Befehlsersetzung verhindert dies. Einfaches Zitieren der Befehlsersetzung macht es nicht mehr zu einer Befehlsersetzung.

Um dies beim Splitten zu beobachten, führen Sie folgende 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 Globen:

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

Wie Sie sehen, echo "$(stuff)"entspricht (-ish *) dem stuff. Sie könnten es verwenden, aber was ist der Sinn, Dinge auf diese Weise zu komplizieren?

Auf der anderen Seite, wenn Sie möchten, dass die Ausgabe stuffsplitting + globbing durchläuft, können Sie dies für echo $(stuff)nützlich halten. Es muss jedoch Ihre bewusste Entscheidung sein.

Es gibt Befehle, die eine Ausgabe generieren, die ausgewertet werden sollte (einschließlich Splitting, Globbing und mehr) und von der Shell ausgeführt wird. Es besteht also die eval "$(stuff)"Möglichkeit (siehe diese Antwort ). Ich habe noch nie einen Befehl, der muss seinen Ausgang zu unterziehen zusätzliche Aufspaltung + Globbing, bevor sie gesehen gedruckt . Eine absichtliche Anwendung echo $(stuff)scheint sehr ungewöhnlich zu sein.


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

Guter Punkt. Dieser Ausschnitt:

var=$(stuff) echo "$var" 

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

Wenn Sie jedoch die Ausgabe von stuffmehr als einmal verwenden müssen, gehen Sie auf diese Weise vor

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

ist normalerweise besser als

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

Auch wenn fooist echound Sie erhalten echo "$var"in Ihrem Code, kann es besser sein, es auf diese Weise zu halten. Dinge, die man beachten muss:

  1. Mit var=$(stuff) stuffläuft einmal; Selbst wenn der Befehl schnell ist, sollten Sie vermeiden, dieselbe Ausgabe zweimal zu berechnen. Oder hat vielleicht stuffandere Auswirkungen als das Schreiben in stdout (z. B. Erstellen einer temporären Datei, Starten eines Dienstes, Starten einer virtuellen Maschine, Benachrichtigen eines Remote-Servers), sodass Sie diese nicht mehrmals ausführen möchten.
  2. Wenn stuffeine zeitabhängige oder etwas zufällige Ausgabe generiert wird, erhalten Sie möglicherweise inkonsistente Ergebnisse von foo "$(stuff)"und bar "$(stuff)". Nachdem var=$(stuff)der Wert von $varfestgelegt ist, können Sie sicher sein foo "$var"und bar "$var"ein identisches Befehlszeilenargument erhalten.

In einigen Fällen foo "$var"möchten Sie (müssen) möglicherweise verwendet werden foo $var, insbesondere wenn mehrere Argumente stuffgeneriert werden (eine Array-Variable ist möglicherweise besser, wenn Ihre Shell dies unterstützt). Wieder wissen Sie, was Sie tun. Wenn es um den Unterschied zwischen und geht, ist das Gleiche wie zwischen und .fooechoecho $varecho "$var"echo $(stuff)echo "$(stuff)"


* Äquivalent ( -ish )?

Ich sagte, echo "$(stuff)"ist äquivalent (-ish) zu stuff. Es gibt mindestens zwei Punkte, die es nicht genau gleichwertig machen:

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

    In diesem Beispiel stufflautet 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" 

    Ein weiteres Beispiel, beginnen Sie mit stuffWesen 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". Da die Frage jedoch erwähnt wird echound die wahrscheinlichste Lösung nicht echoin der ersten Anwendung ist, habe ich mich entschlossen, bisher keine Einführung zu finden printf.

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

  4. $(…)entfernt alle nachgestellten Zeilenumbrüche und echofügt sie dann zurück. So echo "$(printf %s 'a')" | xxdergibt sich eine andere Ausgabe als printf %s 'a' | xxd.

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

    Putting lsbeiseite, in einem allgemeinen Fall, wenn Sie das andere Verhalten zu zwingen, haben dann stuff | catist besser als echo $(ls)oder echo "$(ls)"weil es löst nicht alle anderen Themen hier erwähnt.

  6. Möglicherweise anderer Exit-Status (erwähnt wegen der Vollständigkeit dieser Wiki-Antwort; für Details siehe eine andere Antwort, die Anerkennung verdient).

Ein weiterer Unterschied: Der `stuff`-Weg verschachtelt die Ausgabe von 'stdout` und` stderr`, während `` echo `stuff`` `alle` stderr`-Ausgaben von `stuff` und nur die Ausgabe von` stdout` druckt . Ruslan vor 5 Jahren 5
Und dies ist ein weiteres Beispiel für: In Bash-Skripten kann es kaum zu viele Anführungszeichen geben. `echo $ (stuff)` kann Sie in weitaus mehr Schwierigkeiten bringen als `echo" $ (stuff) "`, wenn Sie nicht explizit Globbing und Expansion wollen. rexkogitans vor 5 Jahren 3
Noch ein kleiner Unterschied: `$ (…)` entfernt alle Zeilenumbrüche und `echo` fügt sie zurück. Also "echo" $ (printf% s 'a ")" | xxd gibt eine andere Ausgabe aus als `printf% s 'a' | xxd`. derobert vor 5 Jahren 2
Sie sollten nicht vergessen, dass einige Befehle (zum Beispiel `ls ') unterschiedlich funktionieren, je nachdem, ob die Standardausgabe eine Konsole ist oder nicht. so `ls | cat` macht nicht dasselbe wie 'ls'. Und natürlich funktioniert "echo $ (ls)" anders als "ls". Martin Rosenau vor 5 Jahren 1
@Ruslan Die Antwort lautet jetzt Community-Wiki. Ich habe Ihren nützlichen Kommentar zum Wiki hinzugefügt. Vielen Dank. Kamil Maciorowski vor 5 Jahren 0
@derobert Die Antwort lautet jetzt Community-Wiki. Ich habe Ihren nützlichen Kommentar zum Wiki hinzugefügt. Vielen Dank. Kamil Maciorowski vor 5 Jahren 0
@MartinRosenau Die Antwort lautet jetzt Community-Wiki. Ich habe Ihren nützlichen Kommentar zum Wiki hinzugefügt. Vielen Dank. Kamil Maciorowski vor 5 Jahren 0
5
WaffleSouffle

Ein weiterer Unterschied: Der Sub-Shell-Exit-Code geht verloren, daher echowird stattdessen der Exit-Code von abgerufen.

> stuff() { return 1 } > stuff; echo $? 1 > echo $(stuff); echo $? 0 
Willkommen bei Super User! Können Sie den Unterschied, den Sie demonstrieren, * erklären *? Vielen Dank bertieb vor 5 Jahren 3
Ich glaube, dass dies zeigt, dass der Rückkehrcode `$?` Der Rückkehrcode von `echo` ist (für` echo $ (stuff) `) anstelle des 'return' von` stuff '. Die `stuff`-Funktion` return 1` (Exit-Code), aber `echo` setzt sie ungeachtet des Return-Codes von` stuff` auf '0'. Sollte immer noch in der Antwort sein. Ismael Miguel vor 5 Jahren 4
Der Rückgabewert der Subshell ist verloren gegangen phuclv vor 5 Jahren 0