我正在开发一个 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"}
如果定义了这些变量,则此脚本会动态添加参数--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}"
这有效,但它不会扩展命令中的变量:
host ~ # ./test.sh
test string
Command was: echo "$ARG"
我想我必须使用 eval 来扩展echo "$ARG"
(echo test string
至少我还没有找到没有的方法eval
)。以下确实有效:
eval echo "Command was: ${COMMAND}"
它产生以下输出:
host ~ # ./test.sh
test string
Command was: echo "$ARG"
Command was: echo test string
但我不太确定我是否可以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}"
它似乎处理得很好,但我很好奇是否有人看到我错过的问题。
答案1
一种可能性是创建一个包装函数,该函数将同时打印命令并执行它,如下所示:
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!
"$@"
}
这样你就可以在你的脚本中执行以下操作:
debug echo "$ARG"
无需摆弄陷阱。缺点是它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!
"$@"
}
然后你可以将你的脚本调用为:
$ DEBUG=yes ./myscript
或者
$ DEBUG= ./myscript
要不就
$ ./myscript
取决于您是否想要调试信息。
我将DEBUG
变量大写,因为它应该被视为环境变量。DEBUG
是一个简单且常见的名称,因此这可能会与其他命令发生冲突。如果您喜欢独角兽,可以称之为“GNIOURF_DEBUG
或MARTIN_VON_WITTICH_DEBUG
” UNICORN_DEBUG
(然后您可能也喜欢小马)。
笔记。在debug
函数中,我仔细格式化了每个参数,printf '%q'
以便正确转义和引用输出,以便可以通过直接复制和粘贴逐字重用。它还将准确地向您显示 shell 看到的内容,因为您将能够找出每个参数(如果有空格或其他有趣的符号)。该函数还使用 的-v
switch直接赋值printf
,以避免不必要的子 shell。
答案2
eval "$BASH_COMMAND"
运行命令。
printf '%s\n' "$BASH_COMMAND"
打印确切指定的命令以及换行符。
如果命令包含变量(即,如果它类似于cat "$foo"
),则打印命令将打印出变量文本。在不执行命令的情况下不可能打印变量的值 - 想想像variable=$(some_function) other_variable=$variable
.
从执行 shell 脚本获取跟踪的最简单方法是xtrace
通过运行脚本或在 shell 内部bash -x /path/to/script
调用来设置 shell 选项。set -x
跟踪被打印到标准错误。