Unterschied bei der Verwendung von () und $ () zum Ausführen einer Reihe von Befehlen

8

Ich versuche derzeit, ein Skript zu erstellen, das Bytes erstellt, die als Eingabe an netcat weitergeleitet werden.

Hier ist die Idee des Skripts:

(perl -e "print \"$BYTES\x00\";

cat file;

perl -e "print \"More bytes\"x16 . \"\r\n\"";) | netcat ip port

Ich habe versucht, sowohl eine Subshell als auch eine Befehlssubstitution (z. B. mit $ ()) zu verwenden, um die Befehle auszuführen. Ich verstehe jedoch nicht, warum die Ausgabe des Skripts bei Verwendung der Befehlssubstitution falsch ist. Ich vermute, dass die Befehlssubstitution ihre Ausgabe bei der Ausführung mehrerer Befehle falsch weiterleitet. Kann mir jemand erklären, warum das so ist?

BEARBEITEN

Hier ist die Variante, die die Befehlssubstitution verwendet hat:

$(perl -e "print \"$BYTES\x00\";

cat file;

perl -e "print \"More bytes\"x16 . \"\r\n\"";) | netcat ip port
MykelXIII
quelle
1
Die Befehlsersetzung wird durch ihre Ausgabe ersetzt. Das heißt, die Ausgabe dieses Befehls in Ersetzungsform wird nicht weitergeleitet, netcatda es sich nur um bloßen Text handelt. Wenn Sie beispielsweise echovor Beginn der Befehlsersetzung hinzugefügt haben, stellen Sie möglicherweise fest, dass dies funktioniert.
HalosGhost
@HalosGhost Sie wollen also sagen, dass die Hex-Bytes, die ich zu generieren versuchte, zu Text in ASCII-Form wurden? Ich habe versucht, einen Hexdump der Ausgaben zu erstellen, und was mich verwirrt, ist, dass die Ausgabe der Befehlssubstitution ein HTML-Dokument enthält und die von mir generierten Hex-Bytes darin enthalten zu sein scheinen.
MykelXIII
Nein, ich meine, Sie können sich jeden Befehl innerhalb einer Befehlssubstitution als gleichwertig mit der Eingabe der Ausgabe dieses Befehls vorstellen. In Ihrem zweiten Beispiel ist das Ergebnis nur Bare-Text. Es wird also nichts weitergeleitet, netcatweil nichts ausgeführt wird.
HalosGhost
@ HalosGhost Ich verstehe. Die Ausgabe der Befehlssubstitution entspricht also der Eingabe von stdin, wodurch nichts weitergeleitet wird. Bin ich richtig? Ich bin neu in der Skripterstellung und habe das nicht verstanden. Ich danke dir sehr. Wenn Sie Ihren Kommentar in den Antwortbereich übertragen, werde ich ihn akzeptieren.
MykelXIII
Ich habe versucht, meine Antwort so weit wie möglich zu erweitern, um das Bild so klar und einfach wie möglich zu verstehen.
HalosGhost

Antworten:

16

Okay, lass uns das zusammenfassen. Eine Subshell führt ihren Inhalt in einer Kette aus (dh sie gruppiert sie). Dies ist tatsächlich intuitiv sinnvoll, da eine Unterschale einfach durch Umgeben der Befehlskette mit erstellt wird (). Abgesehen davon, dass der Inhalt der Subshell bei der Ausführung zusammengefasst wird, können Sie eine Subshell weiterhin so verwenden, als wäre es ein einzelner Befehl. Das heißt, eine Unterschale hat immer noch eine stdin, stdoutund stderrso können Sie Dinge zu und von einer Unterschale leiten.

Andererseits ist das Ersetzen von Befehlen nicht dasselbe wie das einfache Verketten von Befehlen. Die Befehlssubstitution soll sich eher wie ein variabler Zugriff verhalten, jedoch mit einem Funktionsaufruf. Im Gegensatz zu Befehlen verfügen Variablen nicht über die Standard-Dateideskriptoren, sodass Sie (im Allgemeinen) nichts zu oder von einer Variablen weiterleiten können. Dies gilt auch für Befehlsersetzungen.

Um dies klarer zu machen, folgen eine Reihe von möglicherweise unklaren (aber genauen) Beispielen und eine Reihe von, wie ich denke, besser verständlichen Beispielen.

Angenommen, der date -uBefehl gibt Folgendes aus:

Thu Jul  2 13:42:27 UTC 2015

Wir möchten jedoch die Ausgabe dieses Befehls bearbeiten. Also, lasst es uns in etwas wie sed:

$ date -u | sed -e 's/ /    /g'
Thu    Jul        2    13:42:27    UTC    2015

Wow, das hat Spaß gemacht! Das Folgende entspricht vollständig dem oben Gesagten (abgesehen von einigen Umgebungsunterschieden, über die Sie in den Manpages zu Ihrer Shell lesen können):

$ (date -u) | sed -e 's/ /    /g'
Thu    Jul        2    13:42:27    UTC    2015

Das sollte keine Überraschung sein, da wir nur eine Gruppe gemacht haben date -u. Wenn wir jedoch Folgendes tun, werden wir etwas bekommen, das auf den ersten Blick etwas seltsam erscheint:

$ $(date -u) | sed -e 's/ /    /g'
command not found: Thu

Dies liegt daran $(date -u), dass Sie genau eingeben müssen, welche date -uAusgaben ausgegeben werden sollen. Das oben Gesagte entspricht also dem Folgenden:

$ Thu Jul  2 13:42:27 UTC 2015 | sed -e 's/ /    /g'

Was natürlich fehlerhaft sein wird, weil Thues kein Befehl ist (zumindest keiner, den ich kenne); und es leitet sicher nichts weiter stdout( sedwird also niemals eine Eingabe bekommen).

Da wir jedoch wissen, dass Befehlssubstitutionen wie Variablen wirken, können wir dieses Problem leicht beheben, da wir wissen, wie der Wert einer Variablen in einen anderen Befehl übertragen wird:

$ echo $(date -u) | sed -e 's/ /    /g'
Thu    Jul        2    13:42:27    UTC    2015

Aber wie bei jeder Variablen in bash sollten Sie wahrscheinlich Befehlsersetzungen mit zitieren "".

Nun zum vielleicht einfacheren Beispiel; Folgendes berücksichtigen:

$ pwd
/home/hypothetical
$ echo pwd
pwd
$ echo "$(pwd)"
/home/hypothetical
$ echo "$HOME"
/home/hypothetical
$ echo (pwd)
error: your shell will tell you something weird that roughly means “Whoa! you tried to have me echo something that isn't text!”
$ (pwd)
/home/hypothetical

Ich bin mir nicht sicher, wie ich es einfacher beschreiben soll. Die Befehlssubstitution funktioniert genau wie ein variabler Zugriff, bei dem die Subshell weiterhin wie ein Befehl funktioniert.

HalosGhost
quelle
3

Unterschale

  • (command) führt den Befehl in der Unterschale aus. Dies ist nützlich, wenn Sie mehr als einen Befehl haben.

    • (ls) | wcwird Rohr lszu wc, offensichtlich können Sie schreibenls | wc
    • (ls ; date) | wcwird das Ergebnis von beiden lsund datezu leiten wc. Die Verwendung ls ; date | wcführt dazu, dass nur datean weitergeleitet wird wc.

Auswechslung

  • $(command)führt den Befehl aus und ersetzt ihn durch die Ausgabe. z.B

    echo $(date)

ersetzt $(date)durch Thu Jul 2 15:20:43 CEST 2015, dann

    echo Thu Jul  2 15:20:43 CEST 2015

zusammenfügen

Sie können die beiden kombinieren.

file=/hello/word
( printf "%s has %d bytes\n" ${file} $(wc -c < $file) ; date ) | netcat ... 
  • Sie können $fileoder verwenden${file}
Archemar
quelle
Bedeutet das, dass ich die Befehlsersetzung nicht für mehrere Befehle verwenden kann, z. um sie zu trennen?
MykelXIII
Sicher kannst du. Es wird einfach nichts weiterleiten stdoutund sich stattdessen durch das Ergebnis des Ausdrucks ersetzen.
HalosGhost