评估 $BASH_COMMAND 是否安全?

Mar*_*ich 11 bash

我正在开发一个从变量构造复杂命令的 shell 脚本,例如这样(使用我从 Bash FAQ 中学到的技术):

#!/bin/bash

SOME_ARG="abc"
ANOTHER_ARG="def"

some_complex_command \
  ${SOME_ARG:+--do-something "$SOME_ARG"} \
  ${ANOTHER_ARG:+--with "$ANOTHER_ARG"}
Run Code Online (Sandbox Code Playgroud)

如果定义了这些变量,此脚本会动态添加参数--do-something "$SOME_ARG"和添加--with "$ANOTHER_ARG"some_complex_command。到目前为止,这工作正常。

但是现在我还希望能够在我运行时打印或记录命令,例如当我的脚本在调试模式下运行时。因此,当我的脚本运行时some_complex_command --do-something abc --with def,我还希望将此命令放在一个变量中,以便我可以将它记录到系统日志中。

Bash FAQ 演示了为此目的使用DEBUG陷阱和$BASH_COMMAND变量(例如用于调试目的)的技术。我已经尝试过使用以下代码:

#!/bin/bash

ARG="test string"

trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG"

echo "Command was: ${COMMAND}"
Run Code Online (Sandbox Code Playgroud)

这有效,但它不会扩展命令中的变量:

host ~ # ./test.sh
test string
Command was: echo "$ARG"
Run Code Online (Sandbox Code Playgroud)

我想我必须使用 eval 扩展echo "$ARG"echo test string(至少我还没有找到没有的方法eval)。以下确实有效:

eval echo "Command was: ${COMMAND}"
Run Code Online (Sandbox Code Playgroud)

它产生以下输出:

host ~ # ./test.sh
test string
Command was: echo "$ARG"
Command was: echo test string
Run Code Online (Sandbox Code Playgroud)

但是我不确定我是否可以eval像这样安全地使用。我试图利用一些东西没有成功:

#!/bin/bash

ARG="test string; touch /x"
DANGER='$(touch /y; cat /etc/shadow)'

trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG" $DANGER

echo "Command was: ${COMMAND}"
eval echo "Command was: ${COMMAND}"
Run Code Online (Sandbox Code Playgroud)

它似乎处理得很好,但我很好奇是否有人看到了我遗漏的问题。

gni*_*urf 7

一种可能性是创建一个包装函数,该函数将同时打印命令并执行它,如下所示:

debug() {
    # This function prints (to stdout) its arguments and executes them
    local args=() idx=0 IFS=' ' c
    for c; do printf -v args[idx++] '%q' "$c"; done
    printf "%s\n" "${args[*]}"
    # Execute!
    "$@"
}
Run Code Online (Sandbox Code Playgroud)

这样在您的脚本中您可以执行以下操作:

debug echo "$ARG"
Run Code Online (Sandbox Code Playgroud)

无需摆弄陷阱。缺点是它debug在你的代码中添加了一些关键字(但这应该没问题,有这样的东西很常见,比如断言等)。

您甚至可以添加一个全局变量DEBUGdebug像这样修改函数:

debug() {
    # This function prints (to stdout) its arguments if DEBUG is non-null
    # and executes them
    if [[ $DEBUG ]]; then
        local args=() idx=0 IFS=' ' c
        for c; do printf -v args[idx++] '%q' "$c"; done
        printf "%s\n" "${args[*]}"
    fi
    # Execute!
    "$@"
}
Run Code Online (Sandbox Code Playgroud)

然后你可以调用你的脚本:

$ DEBUG=yes ./myscript
Run Code Online (Sandbox Code Playgroud)

或者

$ DEBUG= ./myscript
Run Code Online (Sandbox Code Playgroud)

要不就

$ ./myscript
Run Code Online (Sandbox Code Playgroud)

取决于您是否想要调试信息。

我将DEBUG变量大写,因为它应该被视为环境变量。DEBUG是一个微不足道的通用名称,因此这可能会与其他命令发生冲突。也许叫它GNIOURF_DEBUG或者MARTIN_VON_WITTICH_DEBUG或者UNICORN_DEBUG如果你喜欢独角兽(然后你可能喜欢小马太)。

笔记。debug函数中,我仔细地格式化了每个参数,printf '%q'以便正确地转义和引用输出,以便通过直接复制和粘贴可以逐字重复使用。它还将准确地向您显示 shell 看到的内容,因为您将能够找出每个参数(在空格或其他有趣符号的情况下)。该函数也使用带有-v开关的直接赋值,printf以避免不必要的子shell。


Gil*_*il' 5

eval "$BASH_COMMAND" 运行命令。

printf '%s\n' "$BASH_COMMAND" 打印确切的指定命令,加上一个换行符。

如果命令包含变量(即,如果它类似于cat "$foo"),则打印命令会打印出变量文本。不执行命令就不可能打印变量的值——想想像variable=$(some_function) other_variable=$variable.

从执行 shell 脚本中获取跟踪的最简单方法是通过在xtraceshell 中运行脚本bash -x /path/to/scriptset -x在 shell 内调用来设置shell 选项。跟踪被打印为标准错误。

  • “不执行命令就不可能打印变量的值”——很好的例子,我没有考虑过这种情况。如果 bash 在包含扩展命令的`BASH_COMMAND`旁边有一个附加变量会很好,因为在某些时候它必须在执行命令时对命令进行变量扩展:) (2认同)