读取命令跳过 HEREDOC 中的后续指令 - Bash + Docker

读取命令跳过 HEREDOC 中的后续指令 - Bash + Docker

我遇到了一个问题,我认为 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 varecho "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,我也可以看到它。所以现在我必须找到一种方法来跳过来自定界文档本身的那些读取。

相关内容