
我遇到了一個問題,我認為 shell 或 bash 的工作方式存在錯誤,其中運行由heredoc(也可能來自文件,我還沒有測試)提供的非交互式命令和read
(或類似)heredoc中的命令,導致read
命令後面的程式碼是被忽略,執行結束沒有錯誤,執行命令後運行heredoc的命令。
更具體地說,我在一個 docker 容器中運行程式碼,該容器充當多個 Linux 實用程式的工具箱,這個問題導致程式碼實際上並不安全,因為我不能相信它會按照我的預期發生。
該問題的範例如下所示:
#!/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,但無法運行上面的程式碼,因為透過管道傳輸到命令的heredoc,而是給出了錯誤:the input device is not a TTY
)。
如果我刪除 2set -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
我猜想 read 命令最終會從定界文檔中讀取,正如@muru 在評論中指出的那樣。如果我添加echo "var=\$var"
after echo "after inside"
,它會打印:var=echo "after inside" >&2
,我也可以看到它。所以現在我必須找到一種方法來跳過來自定界文檔本身的那些讀取。