
Ich habe Probleme mit einem Fall, den ich als Fehler in der Funktionsweise der Shell oder Bash ansehe. Das Ausführen eines nicht interaktiven Befehls aus einem Heredoc (vielleicht auch aus einer Datei, das habe ich nicht getestet) und eines read
(oder ähnlichen) Befehls im Heredoc führt dazu, dass der Code nach einem read
Befehlignoriertund die Ausführung endetohne Fehler, Ausführen der Befehlenachder Befehl, der das Heredoc ausführt.
Genauer gesagt führe ich Code in einem Docker-Container aus, der als Toolbox für verschiedene Linux-Dienstprogramme fungiert, und dieses Problem führt dazu, dass der Code insofern nicht wirklich sicher ist, als dass ich nicht darauf vertrauen kann, dass alles wie erwartet funktioniert.
Unten sehen Sie ein Beispiel des Problems:
#!/bin/bash
set -eou pipefail
echo "before outside" >&2
docker run --rm -i ubuntu:20.04 /bin/bash <<-SHELL
set -eou pipefail
echo "before inside" >&2
read 'var'
echo "after inside" >&2
SHELL
echo "after outside" >&2
Ich würde erwarten, dass der obige Code entweder alle Echos ausführt und erfolgreich endet oder einen Fehler im read
Befehl ausgibt und mit einem Fehler endet.
Leider lautet die Ausgabe des obigen Codes:
before outside
before inside
after outside
Es ignorierte grundsätzlich, was nach dem read
Befehl kam, möglicherweise weil ein Befehl eine Anweisung enthält read
, die kein Pseudo-TTY zuweist ( docker run -it
weist ein Pseudo-TTY zu, kann den obigen Code jedoch aufgrund des Heredoc, das an den Befehl weitergeleitet wird, nicht ausführen, sondern gibt stattdessen den Fehler aus: the input device is not a TTY
).
Wenn ich die 2 entferne, set -eou pipefail
habe ich das gleiche Problem, sogar mit read 'var' ||:
, also glaube ich nicht, dass es damit zusammenhängt (eines derFallstrickevon) set -e
.
Ein Beispiel für ein erwartetes Ergebnis ist wie folgt:
#!/bin/bash
set -eou pipefail
echo "before outside" >&2
docker run --rm -i ubuntu:20.04 /bin/bash <<-SHELL
set -eou pipefail
echo "before inside" >&2
unknow_command error ||:
echo "after inside" >&2
SHELL
echo "after outside" >&2
Das endet erfolgreich und druckt:
before outside
before inside
/bin/bash: line 3: unknow_command: command not found
after inside
after outside
Oder:
#!/bin/bash
set -eou pipefail
echo "before outside" >&2
docker run --rm -i ubuntu:20.04 /bin/bash <<-SHELL
set -eou pipefail
echo "before inside" >&2
unknow_command error
echo "after inside" >&2
SHELL
echo "after outside" >&2
Das endet mit einem Fehler und druckt:
before outside
before inside
/bin/bash: line 3: unknow_command: command not found
Für mich wäre es ok, wenn der read
Befehl einfach übersprungen würde (zum Beispiel mit einem leeren Wert als Eingabe) und alle nachfolgenden Befehle ausgeführt würden, auch die im Heredoc, oder dass ein Fehler ausgegeben und die Ausführung sofort gestoppt würde (wenn ich den Fehler im Docker-Befehl nicht ignoriere).
Das obige war nur ein Beispiel. In der Praxis kann es noch schlimmer sein, da der read
(oder ein ähnlicher) Befehl nicht direkt aufgerufen werden kann, sondern innerhalb eines Befehls und nur unter bestimmten Bedingungen. Beispiel:
#!/bin/bash
set -eou pipefail
docker run --rm -i my_mysql /bin/bash <<-SHELL
set -eou pipefail
some_important_command_1
mysql -u "$user" -p "$pass" -e "some sql command"
some_important_command_2
SHELL
some_important_command_after_2
Der obige Code scheint in Ordnung zu sein, aber wenn das Kennwort leer ist, wird versucht, von stdin zu lesen, wodurch das Problem im ersten Beispiel auftritt und übersprungen wird. Die some_important_command_2
Ausführung some_important_command_after_2
sollte jedoch erst nach erfolgen some_important_command_2
.
Das obige MySQL-Beispiel war auch nur ein Beispiel. Ich kann in diesem Fall überprüfen, ob das Passwort leer ist, und es verarbeiten.Das eigentliche Problem ist, dass ich nicht feststellen kann, ob dieses Problem innerhalb eines Codes auftreten kann, und ich sehe keinen sicheren Weg, es zu vermeiden, abgesehen davon, dass Sie die Verwendung des Docker-Toolbox-Containers beenden und alle Dienstprogramme und andere Dinge in allen Hosts installieren und auf dem neuesten Stand halten (anstatt nur das Container-Image auf dem neuesten Stand zu halten). Es funktioniert auch nicht für Befehle, die speziell in Diensten innerhalb von Containern ausgeführt werden (wie das MySQL-Beispiel oben).
Hat jemand eine Lösung für das obige Problem?Eine Lösung, dieist nicht spezifisch to the examples I gave, but a general approach that can solve this type of bug (either by completing successfully, executing all commands, or giving an error and stopping all subsequent commands).
Update:
Adding declare -p var
after echo "after inside"
outputs:
before outside
before inside
declare -- var="echo \"after inside\" >&2"
after outside
I guess that the read command ends up reading from the heredoc as @muru pointed in the comments. I can also see it if I add echo "var=\$var"
after echo "after inside"
, which prints: var=echo "after inside" >&2
. So now I have to see a way to skip those reads from the heredoc itself.