
シェルやbashの動作にバグがあると思われるケースで困っています。ヒアドキュメント(ファイルからかもしれないが、テストはしていない)から提供された非対話型コマンドとヒアドキュメント内の(または同様の)コマンドを実行すると、コマンドread
の後のコードがread
無視された、そして処刑は終了するエラーなし、コマンドを実行する後ヒアドキュメントを実行するコマンド。
具体的には、いくつかの Linux ユーティリティのツールボックスとして機能する Docker コンテナでコードを実行していますが、この問題により、期待どおりに実行されるかどうか信頼できないという意味で、コードが実際には安全ではなくなります。
問題の例を以下に示します。
#!/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
上記のコードは、すべてのエコーを実行して正常に終了するか、コマンドでエラーが発生しread
てエラーで終了すると予想されます。
残念ながら、上記のコードの出力は次のようになります。
before outside
before inside
after outside
read
基本的にコマンドの後に続くものは無視されますread
おそらく、疑似 tty を割り当てないコマンドの命令があるためです(docker run -it
疑似 tty を割り当てますが、コマンドにパイプされているヒアドキュメントのために上記のコードを実行できず、代わりにエラーが発生します: the input device is not a TTY
)。
2つを削除してset -eou pipefail
も同じ問題が発生するのでread 'var' ||:
、(落とし穴の)set -e
。
期待される結果の例は次のとおりです。
#!/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
これは正常に終了し、次のように出力されます:
before outside
before inside
/bin/bash: line 3: unknow_command: command not found
after inside
after outside
または:
#!/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
これはエラーで終了し、次のように出力されます:
before outside
before inside
/bin/bash: line 3: unknow_command: command not found
私の場合、read
コマンドがスキップされ(たとえば、入力として空の値を使用)、ヒアドキュメント内のコマンドも含め、その後のすべてのコマンドが実行されるか、エラーが発生して実行が直ちに停止される(docker コマンドのエラーを無視しない場合)のであれば問題ありません。
上記は単なる例です。実際のケースでは、read
(または同様の) コマンドが直接呼び出されず、コマンド内で、特定の条件下でのみ呼び出される可能性があるため、状況はさらに悪くなる可能性があります。例:
#!/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
上記のコードは問題ないように見えるかもしれませんが、パスワードが空の場合、stdin から読み取ろうとするため、最初の例で問題が発生し、 がスキップされますsome_important_command_2
が、some_important_command_after_2
の実行は の後にのみ実行される必要がありますsome_important_command_2
。
上記の mysql の例も単なる例ですが、この場合はパスワードが空かどうかを確認して処理することができます。本当の問題は、この問題がコード内で発生するかどうか判断できず、それを回避する安全な方法が見つからないことです。、docker toolbox コンテナの使用を停止し、すべてのユーティリティとその他のものをすべてのホスト内にインストールし、それらを最新の状態に保つこと(コンテナ イメージを最新の状態に保つだけでなく)を除きます。また、コンテナ内のサービスで具体的に実行されるコマンド(上記の mysql の例など)では機能しません。
上記の問題に対する解決策を持っている人はいますか?解決策は具体的ではない私が挙げた例とは異なり、一般的なアプローチで解決できるこのタイプのバグ(正常に完了するか、すべてのコマンドを実行するか、エラーを出して後続のすべてのコマンドを停止します)。
アップデート:
declare -p var
出力後の追加echo "after inside"
:
before outside
before inside
declare -- var="echo \"after inside\" >&2"
after outside
@muru がコメントで指摘したように、read コマンドは最終的に heredoc から読み取ることになると思います。echo "var=\$var"
after を追加してecho "after inside"
、次のように出力した場合にも、同じことがわかります。var=echo "after inside" >&2
したがって、heredoc 自体からの読み取りをスキップする方法を見つける必要があります。