Wie unterscheidet sich die Bash-Dateiumleitung zum Standard von der Shell („sh“) unter Linux?

Wie unterscheidet sich die Bash-Dateiumleitung zum Standard von der Shell („sh“) unter Linux?

Ich habe ein Skript geschrieben, das während der Ausführung den Benutzer wechselt, und es mithilfe der Dateiumleitung zum Standard ausgeführt. So user-switch.shist es ...

#!/bin/bash

whoami
sudo su -l root
whoami

Und wenn ich es ausführe, basherhalte ich das erwartete Verhalten.

$ bash < user-switch.sh
vagrant
root

Wenn ich das Skript jedoch mit ausführe sh, erhalte ich eine andere Ausgabe

$ sh < user-switch.sh 
vagrant
vagrant

Warum wird bash < user-switch.sheine andere Ausgabe ausgegeben als sh < user-switch.sh?

Anmerkungen:

  • passiert auf zwei verschiedenen Boxen mit Debian Jessie

Antwort1

Ein ähnliches Skript, ohne sudo, aber mit ähnlichen Ergebnissen:

$ cat script.sh
#!/bin/bash
sed -e 's/^/--/'
whoami

$ bash < script.sh
--whoami

$ dash < script.sh
itvirta

Bei bashgeht der Rest des Skripts als Eingabe an sed, bei dashinterpretiert es die Shell.

Wird straceauf diesen ausgeführt: dashLiest einen Block des Skripts (hier acht KB, mehr als genug, um das gesamte Skript aufzunehmen) und erzeugt dann sed:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 8192) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Das bedeutet, dass sich der Dateihandle am Ende der Datei befindet und sedkeine Eingabe sieht. Der verbleibende Teil wird innerhalb gepuffert dash. (Wenn das Skript länger als die Blockgröße von 8 kB wäre, würde der verbleibende Teil von gelesen sed.)

Bash sucht dagegen zurück zum Ende des letzten Befehls:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 36) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
...
lseek(0, -7, SEEK_CUR)                  = 29
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Wenn die Eingabe aus einer Pipe kommt, wie hier:

$ cat script.sh | bash

Zurückspulen ist nicht möglich, da Pipes und Sockets nicht suchbar sind. In diesem Fall greift Bash auf das Lesen der Eingabe zurückjeweils ein Zeichenum zu viel Lesen zu vermeiden. (fd_to_buffered_stream()Ininput.c) Ein vollständiger Systemaufruf für jedes Byte ist grundsätzlich nicht sehr effektiv. In der Praxis glaube ich nicht, dass die Lesevorgänge einen großen Mehraufwand darstellen, verglichen beispielsweise mit der Tatsache, dass die meisten Dinge, die die Shell tut, das Starten ganz neuer Prozesse beinhalten.

Eine ähnliche Situation ist folgende:

echo -e 'foo\nbar\ndoo' | bash -c 'read a; head -1'

Die Subshell muss sicherstellen, dass readnur die erste neue Zeile gelesen wird, damit headdie nächste Zeile angezeigt wird. (Das funktioniert dashauch mit .)


Mit anderen Worten: Bash unternimmt zusätzliche Anstrengungen, um das Lesen derselben Quelle für das Skript selbst und für daraus ausgeführte Befehle zu unterstützen. dashtut dies nicht. Die in Debian verpackten zsh, und ksh93folgen Bash in dieser Hinsicht.

Antwort2

Die Shell liest das Skript von der Standardeingabe. Innerhalb des Skripts führen Sie einen Befehl aus, der auch die Standardeingabe lesen möchte. Welche Eingabe geht wohin?Man kann nicht zuverlässig sagen.

Shells arbeiten so, dass sie einen Abschnitt des Quellcodes lesen, ihn analysieren und, wenn sie einen vollständigen Befehl finden, diesen ausführen und dann mit dem Rest des Abschnitts und dem Rest der Datei fortfahren. Wenn der Abschnitt keinen vollständigen Befehl enthält (mit einem Abschlusszeichen am Ende – ich glaube, alle Shells lesen bis zum Ende einer Zeile), liest die Shell einen weiteren Abschnitt und so weiter.

Wenn ein Befehl im Skript versucht, aus demselben Dateideskriptor zu lesen, aus dem die Shell das Skript liest, findet der Befehl alles, was nach dem letzten gelesenen Block kommt. Dieser Speicherort ist unvorhersehbar: Er hängt von der Blockgröße ab, die die Shell ausgewählt hat, und das kann nicht nur von der Shell und ihrer Version abhängen, sondern auch von der Maschinenkonfiguration, dem verfügbaren Speicher usw.

Bash sucht im Skript bis zum Ende des Quellcodes eines Befehls, bevor der Befehl ausgeführt wird. Darauf können Sie sich nicht verlassen, nicht nur, weil andere Shells dies nicht tun, sondern auch, weil dies nur funktioniert, wenn die Shell aus einer regulären Datei liest. Wenn die Shell aus einer Pipe liest (z. B. ssh remote-host.example.com <local-script-file.sh), werden gelesene Daten gelesen und können nicht rückgängig gemacht werden.

Wenn Sie einem Befehl im Skript Eingaben übergeben möchten, müssen Sie dies explizit tun, normalerweise mit einemhier Dokument. (Für mehrzeilige Eingaben ist ein Here-Dokument normalerweise am praktischsten, aber jede andere Methode ist geeignet.) Der von Ihnen geschriebene Code funktioniert nur in einigen Shells und nur, wenn das Skript aus einer regulären Datei als Eingabe an die Shell übergeben wird. Wenn Sie erwartet haben, dass das zweite als whoamiEingabe an übergeben würde sudo …, denken Sie noch einmal darüber nach und bedenken Sie, dass das Skript meistens nicht an die Standardeingabe der Shell übergeben wird.

#!/bin/bash
whoami
sudo su -l root <<'EOF'
whoami
EOF

Beachten Sie, dass Sie in diesem Jahrzehnt verwenden können sudo -i root. Laufen sudo suist ein Hack aus der Vergangenheit.

verwandte Informationen