Warum schlägt die Zuweisung einer Variablen mit Befehlsersetzung und die anschließende Ausgabe dieser Variablen immer fehl?

Warum schlägt die Zuweisung einer Variablen mit Befehlsersetzung und die anschließende Ausgabe dieser Variablen immer fehl?

Warum funktioniert Folgendes in Bash nicht?

# Ensure TEST is unset
export TEST=''
echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"

Aus irgendeinem Grund wird immer eine leere Zeile zurückgegeben.

Wenn Sie dies in separaten Zeilen tun, erhalten Sie das erwartete Ergebnis:

# Ensure TEST is unset
export TEST=''
echo "Hello world" > test.txt
TEST=$(cat test.txt)
echo "$TEST"
Hello world!

Wie kann ich das in einem Einzeiler zum Laufen bringen?

Ich brauche das zum Entschlüsseln einesGPGDatei, die eine Zeichenfolge mit Sonderzeichen enthält, die ich dann weitergebe anAnsible.

ANSIBLE_VAULT_PASSWORD="$( gpg -d ~/gpg_encrypted_vault_password_file 2>/dev/null )" ansible-playbook etc etc etc

Aus irgendeinem Grund denken die Ansible-Entwickler, dass das Speichern eines Tresorkennworts in einer einfachen Textdatei sicher sei, und ich gebe nicht gerne bei jedem Test eines Spiels lange, sichere Kennwörter ein (oder kopiere und füge sie ein). Daher ist die Verwendung des GPG-Agenten mit einer verschlüsselten Datei die einzige sichere Problemumgehung, die mir einfällt.

Antwort1

Ich möchte nicht im Widerspruch zu den anderen bereits vorhandenen Antworten stehen, aber ich dachte, eine schrittweise Analyse könnte hilfreich sein. (Antwort von u1686_grawitysieht genau aus, aber ich dachte, das könnte klarer sein ...)

# ensure TEST is unset export TEST=''

Gute Neuigkeiten: Dies funktioniert wahrscheinlich wie erwartet. Das # wandelt die erste Zeile in einen Kommentar um, und die nächste Zeile sorgt dafür, dass die Variable TEST einen leeren String (null Bytes) enthält.

echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"

Die Shell bemerkt das &&. Sie führt den ersten Teil des Codes aus ...

echo "Hello world!" > test.txt

Anschließend verarbeitet es das &&. Es sieht sich den Rückgabewert des Echo-Befehls an und sieht, dass dieser Null ist. Daher kann es mit der Ausführung des zweiten Befehls fortfahren. Der zweite Befehl lautet:

TEST="$(cat test.txt)" echo "$TEST"

Das erste, was die Shell hier tun wird, ist, die Variablen zu ersetzen. Die „Befehlssubstitution“ verhält sich hier wie eine Variable, sodass der Befehl „cat text.txt“ ausgeführt wird. Aus der Sicht der Shell sieht der Befehl jetzt eher so aus:

TEST="Hello world!" echo "$TEST"

Nun, das sieht bisher wahrscheinlich alles gut aus, aber hier ist das große Problem. Die Shell startet den TEST="Hello world!"Befehl ' ' noch nicht. Stattdessen bemerkt die Shell, dass noch ein Verweis auf vorhanden ist $TEST, und ersetzt diesen.

Der Befehl, den die Shell ausführen möchte, sieht nun ungefähr so ​​aus:

TEST="Hello world!" echo ""

Wenn ich schon ins Detail gehe, möchte ich wohl darauf hinweisen, dass die Shell diesen Befehl in Teile zerlegt. Dabei werden die Anführungszeichen entfernt. Die einzelnen Teile sehen also folgendermaßen aus:

  1. PRÜFEN
  2. =
  3. Hallo Welt!
  4. Echo
  5. (leerer Wert)

Okay, jetzt ist die Shell endlich mit dem Ersetzen der Elemente fertig und kann mit der nächsten Aufgabe fortfahren, nämlich herauszufinden, welcher ausführbare Code ausgeführt werden soll.

Es fällt auf, dass die Befehlszeile mit einer Form von beginnt A=B C(wobei Aein Variablenname dargestellt wird, das Gleichheitszeichen angibt, dass einer Variablen ein Wert zugewiesen wird, und Bden zuzuweisenden Wert darstellt und dann Cein optionaler Teil folgt, der einen auszuführenden Befehl darstellt.

Wenn die Shell feststellt, dass die Befehlszeile diesem allgemeinen Muster entspricht, ist der ausführbare Code, den sie ausführen wird, der Code, der das Gleichheitszeichen verarbeitet. Im Grunde ähnelt das Gleichheitszeichen dem Namen einer ausführbaren Datei, da das Gleichheitszeichen letztendlich steuert, welchen Code die Shell ausführen wird. Die Shell führt den Code aus, um einer Variablen mit dem Namen TESTden Wert zuzuweisen Hello world!.

Nachdem dies erledigt ist, startet die Shell den angeforderten Befehl, der den vierten und fünften Teil verwendet, die oben angegeben sind (wobei der vierte Teil der echoBefehl und der fünfte Teil die leere Zeichenfolge ist).

Der Grund für meine Antwort (äh... ich meine,Antwort von sqrt-1) funktioniert, indem das zweite && die Shell veranlasst, den TEST="$(cat test.txt)"Rückgabewert des Codes auszuführen (beachten Sie, dass er Null ist), und erst dann mit der Verarbeitung dessen fortzufahren, was nach dem zweiten kommt &&(so dass dann in diesem echo "$TEST"Teil die Variable verarbeitet wird $TESTund jetzt alles funktioniert).

Beachten Sie, dass die von sqrt-1 bereitgestellte Antwort Code enthält, der etwas verschwenderisch ist, obwohl dies verständlich ist, da er die gestellte Frage direkt beantwortet. Im Wesentlichen tut er Folgendes:

Zunächst wird der Befehl in drei Teile aufgeteilt:

  • echo "Hallo Welt!" > test.txt
  • TEST="$(Katze test.txt)"
  • echo "$TEST"
  • Der dritte Teil wird nur ausgeführt, wenn der zweite Teil (läuft und) erfolgreich ist, und der zweite Teil wird aufgrund der beiden &&Operatoren nur ausgeführt, wenn der erste Befehl erfolgreich ist.

    Eine weitere Variante könnte sein:

    echo "Hello world!" > test.txt && ( TEST="$(cat test.txt)" ; echo "$TEST" )

    Mit diesem Semikolon TEST="$(cat test.txt)"werden die Ergebnisse von ' ' nicht ausgewertet, um zu bestimmen, ob ' echo "$TEST"' will be run. When I see a &&. Ich versuche eher herauszufinden, was ausgewertet wird und warum das wichtig ist. Trotz der zusätzlichen Komplexität durch die Verwendung von Klammern scheint mir diese Variante mit einem Semikolon tatsächlich etwas einfacher zu verarbeiten zu sein.

    Antwort2

    Dies funktioniert nicht, da $varErweiterungen von der Shell ausgeführt werden, bevor die Befehlszeile ausgeführt wird, d. h.nichtdurch den Befehl „Echo“.

    Inzwischen VAR=value somecommandhandelt es sich bei nicht um eine normale Zuweisung. Der Zweck besteht lediglich darin, eine Umgebungsvariable in den neuen Prozess einzufügen, es hat jedoch keine Auswirkung auf Shell-Variablen vor der Erstellung dieses Prozesses, d. h. es unterscheidet sich völlig von VAR=value; somecommand.

    Mit anderen Worten, es geschieht in umgekehrter Reihenfolge als im Titel Ihres Beitrags beschrieben. Zuerst "$TEST"wird das (immer noch leere) in erweitert "",Dann echo ""wird mit einer zusätzlichen Umgebungsvariablen ausgeführt TEST=..., die ihm egal ist.

    Da Ihre Testbefehle das Verhalten von Ansible nachahmen sollen, sollten sie tatsächlich Folgendes betrachten:ihreUmgebungsvariablen und kein Verlassen auf die Vorerweiterung durch die Shell; etwas wie VAR=value envoder VAR=value printenv VARwürde viel besser passen.

    Antwort3

    Warum funktioniert Folgendes in Bash nicht?

    echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"
    

    Wenn Sie es jedoch ausführenShellCheck – Shell-Skript-Analysetooles wird Ihnen sagen, warum:

    $ shellcheck myscript
     
    Line 2:
    echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"
                                      ^-- SC2097 (warning): This assignment is only seen by the forked process.
    >>                                                             ^-- SC2098 (warning): This expansion will not see the mentioned assignment.
    

    SehenShellCheck: SC2097 – Diese Zuweisung wird nur vom gegabelten Prozess gesehen.

    Antwort4

    Basierend auf dem Link von @DavidPostill würde ich verwenden

    echo "Hello world!" > test.txt && TEST="$(cat test.txt)" && echo "$TEST"
    

    FÄLLIGE BEARBEITUNG
    Wie viele Benutzer zu Recht angemerkt haben, ist meine Antwort alles andere als hilfreich: Sie ermöglicht es niemandem, etwas über das Thema zu lernen.
    Bitte beziehen Sie sich stattdessen auf @TOOGAM'sPostunten, was die beste Antwort ist (meine persönliche Meinung) und vollständig darlegt, warum der Code von @Peter Mortensen nicht funktioniert, wie die Variablenerweiterung in Bash funktioniert und sogar, warum meine Verwendung einer zweiten &&nicht die optimale Wahl ist.

    verwandte Informationen