我想向 bash 脚本的用户提供类似这样的反馈:“您已运行以下命令 <用户输入的确切命令>,最好像这样运行它 <更好的命令>”。出于可用性原因,我认为最好尽可能精确地打印用户输入的命令。
可以做的事情之一是这样的:
echo "You've run the following command:" ${0} "${@}"
但这不会保留引号或转义文本。可以检查“${@}”并查看哪些条目需要转义或引号,但脚本无法分辨用户使用了其中哪一个,并重现它。
history
无权访问运行该脚本的 shell 中的最后一个命令(而且,出于充分的理由,它在脚本中默认不启用)。
问题:尽可能精确地重现用户输入的运行脚本的命令的最佳方法是什么?
答案1
您无法获取未解析的命令行;shell 接收命令行并在启动代码之前对其进行解析。(当然,假设有一个 shell。但不一定需要有一个。)
示例。将其另存为args
,并使其可执行(chmod a+x args
):
#!/bin/sh
echo "0=$0, #=$#, *=($*)" # Program, number of args, list of args
printf '> %s\n' "$@" # Each arg in turn
你可以用任何一种方式调用它,并且结果输出将是相同的
./args 'one two' three
a='one two'; ./args "$a" three # Notice $a is quoted so remains one arg
./args 'one'" two" three # Quotes are stripped by the shell
a=one b=; ./args "$a two" $b three # I really dislike unquoted variables
a=one; ./args "$a two" three # Variations on a quoted theme
./args one\ two three # And more
您所能期望的最好结果是"$@"
根据需要使用和引用参数。如果您有来自 Frodo Looijaard 的非标准getopt
(不是getopts
),util‐linux
您可以使用几行这样的代码来获取引用的字符串:
line=$(getopt --shell sh '' -- "$@") # Guess the command line
printf 'Command: %s %s\n' "$0" "${line# -- }"
但它仍然没有完美地再现原始的命令行。
答案2
没有固定的方法。只有父 shell 知道使用的确切引用;但父 shell 不一定是 shell,它可以是一个进程,它为脚本提供参数数组,而无需解析需要引用或转义的表示。如果它是 shell,则没有标准接口允许您的脚本获取有关引用的信息。
“用户输入的命令”可能只是shell_function
或./the-script "${array[@]}"
,因此如果您想“尽可能精确地”复制它,那么不仅仅是引用。
如果父 shell 足够复杂,则可能可以对其进行配置预先,这样你的脚本就可以获取有关命令原始形式的信息。这会很麻烦,而且依赖于 shell(首先请参阅echo-literally
这里)。如果我是你的脚本的用户,我不会只想让你的脚本看起来很智能而配置我的交互式 shell。
Windows 中的程序可以用引号知道它的命令字符串(参见另一个答案其中说“情况有些复杂”),但我不认为 Bash 脚本即使在 Windows 中启动时也可以使用这个“功能”。
您能做的最好的就是您所描述的:
可以检查“${@}”并查看哪些条目需要转义或引用,但脚本无法判断用户使用了哪一个,并重现它。
实际上,你可以使用以下方法轻松添加引号"${0@Q}"
和"${@@Q}"
。 例子:
foo=bar' 'baz
bash -c '
echo "You have run the following command: ${0@Q} ${@@Q}"
' "arbitrary name" arg1 arg\ 2 "arg'3" 'arg 4' "a r"\ g'\5' "$foo"
输出:
You have run the following command: 'arbitrary name' 'arg1' 'arg 2' 'arg'\''3' 'arg 4' 'a r g\5' 'bar baz'
传递后的 shell 代码-c
不仅无法分辨出所使用的确切引用,也无法知道它foo
曾经存在过。