我正在开发一个从变量构造复杂命令的 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)
它似乎处理得很好,但我很好奇是否有人看到了我遗漏的问题。
一种可能性是创建一个包装函数,该函数将同时打印命令并执行它,如下所示:
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
在你的代码中添加了一些关键字(但这应该没问题,有这样的东西很常见,比如断言等)。
您甚至可以添加一个全局变量DEBUG
并debug
像这样修改函数:
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。
eval "$BASH_COMMAND"
运行命令。
printf '%s\n' "$BASH_COMMAND"
打印确切的指定命令,加上一个换行符。
如果命令包含变量(即,如果它类似于cat "$foo"
),则打印命令会打印出变量文本。不执行命令就不可能打印变量的值——想想像variable=$(some_function) other_variable=$variable
.
从执行 shell 脚本中获取跟踪的最简单方法是通过在xtrace
shell 中运行脚本bash -x /path/to/script
或set -x
在 shell 内调用来设置shell 选项。跟踪被打印为标准错误。