根据Bash 手册,环境变量BASH_COMMAND
包含
当前正在执行或即将执行的命令,除非 shell 正在由于陷阱而执行命令,在这种情况下它是在陷阱时执行的命令。
撇开那个陷阱极端情况不谈,如果我理解正确的话,这意味着当我执行一个命令时,变量BASH_COMMAND
包含该命令。目前还不清楚该变量是否在命令执行后被取消设置(即,仅在尽管命令正在运行,但之后没有运行),尽管有人可能会争辩说,因为它是“命令现在正在执行或即将执行”,它不是命令那只是已执行。
但让我们检查一下:
$ set | grep BASH_COMMAND=
$
空的。我原本期望看到,BASH_COMMAND='set | grep BASH_COMMAND='
或者只是BASH_COMMAND='set'
,但是空的让我感到惊讶。
我们来尝试一下别的东西:
$ echo $BASH_COMMAND
echo $BASH_COMMAND
$
这很有道理。我执行命令echo $BASH_COMMAND
,因此变量BASH_COMMAND
包含字符串echo $BASH_COMMAND
。为什么这次成功了,而之前却不行?
让我们再做set
一次:
$ set | grep BASH_COMMAND=
BASH_COMMAND='echo $BASH_COMMAND'
$
所以,请稍等。曾是当我执行该echo
命令时设置不是之后取消设置。但当我set
再次执行时,BASH_COMMAND
不是设置为set
命令。无论我set
在这里执行多少次命令,结果都是一样的。那么,执行时变量是否设置echo
,而执行时不设置set
?让我们看看。
$ echo Hello AskUbuntu
Hello AskUbuntu
$ set | grep BASH_COMMAND=
BASH_COMMAND='echo $BASH_COMMAND'
$
什么?因此,当我执行时,变量被设置了echo $BASH_COMMAND
,但是不是当我执行时echo Hello AskUbuntu
?现在有什么区别?只有当前命令本身强制 shell 评估变量时,变量才会被设置吗?让我们尝试一些不同的东西。也许这次是一些外部命令,而不是 bash 内置命令,以求得变化。
$ /bin/echo $BASH_COMMAND
/bin/echo $BASH_COMMAND
$ set | grep BASH_COMMAND=
BASH_COMMAND='/bin/echo $BASH_COMMAND'
$
嗯,好的……再次,变量被设置了。那么我现在的猜测正确吗?变量是否仅在需要评估时才设置?为什么?为什么?出于性能原因?让我们再试一次。我们将尝试$BASH_COMMAND
在文件中 grep,由于$BASH_COMMAND
应该包含一个grep
命令,grep
所以应该 grep 该grep
命令(即,它本身)。所以让我们创建一个合适的文件:
$ echo -e "1 foo\n2 grep\n3 bar\n4 grep \$BASH_COMMAND tmp" > tmp
$ grep $BASH_COMMAND tmp
grep: $BASH_COMMAND: No such file or directory
tmp:2 grep <-- here, the word "grep" is RED
tmp:4 grep $BASH_COMMAND tmp <-- here, the word "grep" is RED
tmp:2 grep <-- here, the word "grep" is RED
tmp:4 grep $BASH_COMMAND tmp <-- here, the word "grep" is RED
$ set | grep BASH_COMMAND=
BASH_COMMAND='grep --color=auto $BASH_COMMAND tmp'
$
好的,很有趣。命令grep $BASH_COMMAND tmp
被扩展为grep grep $BASH_COMMAND tmp tmp
(当然,变量只被扩展一次),所以我在一个不存在的grep
文件中搜索了 ,一次在文件中搜索了 ,两次在文件中搜索了。$BASH_COMMAND
tmp
问题 1:我目前的假设是否正确:
BASH_COMMAND
仅当命令尝试实际评估它时才设置;并且- 这是不是执行命令后是否取消设置,即使描述可能让我们相信如此?
问题2:如果是,为什么?性能如何?如果不是,那么上述命令序列中的行为还能如何解释?
问题3:最后,在什么情况下这个变量实际上可以有意义地使用?我实际上试图使用它$PROMPT_COMMAND
来分析正在执行的命令(并根据该命令执行一些操作),但我不能,因为一旦在我的 中$PROMPT_COMMAND
执行命令来查看变量$BASH_COMMAND
,变量就会被设置为该命令。即使我MYVARIABLE=$BASH_COMMAND
在 的开头执行了$PROMPT_COMMAND
,然后MYVARIABLE
包含字符串MYVARIABLE=$BASH_COMMAND
,因为赋值也是一个命令。(这个问题不是关于如何在执行中获取当前命令$PROMPT_COMMAND
。我知道还有其他方法。)
这有点像海森堡的不确定性原理。只要观察变量,我就能改变它。
答案1
回答第三个问题:当然可以按照 Bash 手册中明确提示的方式有意义地使用它——在陷阱中,例如:
$ trap 'echo ‘$BASH_COMMAND’ failed with error code $?' ERR
$ fgfdjsa
fgfdjsa: command not found
‘fgfdjsa’ failed with error code 127
$ cat /etc/fgfdjsa
cat: /etc/fgfdjsa: No such file or directory
‘cat /etc/fgfdjsa’ failed with error code 1
答案2
现在已经回答了 Q3(我认为是正确的:BASH_COMMAND
在陷阱中很有用,但在其他任何地方都很难用),让我们尝试一下 Q1 和 Q2。
Q1 的答案是:您的假设是否正确是无法确定的。这两个要点的真实性都无法确定,因为它们询问的是未指定的行为。根据其规范,的值BASH_COMMAND
在执行该命令期间设置为命令的文本。规范没有说明在任何其他情况下(即没有执行任何命令时)其值必须是什么。它可以有任何值,也可以根本没有值。
问题 2“如果不是,那么上述命令序列中的行为还能如何解释?”的答案合乎逻辑(虽然有些迂腐):这是因为 的值BASH_COMMAND
未定义。由于其值未定义,因此它可以具有任何值,这正是该序列所显示的。
后记
有一点我认为你确实触及了规范中的薄弱环节。就是你说的:
即使我在 $PROMPT_COMMAND 的开头执行 MYVARIABLE=$BASH_COMMAND,MYVARIABLE 也会包含字符串 MYVARIABLE=$BASH_COMMAND,因为任务也是一个命令。
在我阅读 bash 手册页时,斜体部分是不正确的。本节SIMPLE COMMAND EXPANSION
解释了如何首先搁置命令行上的变量赋值,然后
如果没有命令名结果 [换句话说,只有变量分配],则变量分配会影响当前 shell 环境。
在我看来,变量赋值不是命令(因此不会出现在 中),就像在其他编程语言中一样。这也可以解释为什么的输出中BASH_COMMAND
没有一行,本质上是变量赋值的语法“标记”。BASH_COMMAND=set
set
set
另一方面,该部分的最后一段写道
如果扩展后还有命令名,则执行如下所述。否则,命令退出。
...这表明情况并非如此,变量赋值也是命令。
答案3
$BASH_COMMAND 的创新用法
最近发现这个令人印象深刻的 $BASH_COMMAND 使用实现类似宏的功能。
这是别名的核心技巧,它取代了 DEBUG 陷阱的使用。如果您阅读了上一篇文章中有关 DEBUG 陷阱的部分,您将认出 $BASH_COMMAND 变量。在那篇文章中,我说过它在每次调用 DEBUG 陷阱之前被设置为命令的文本。好吧,事实证明它是在执行每个命令之前设置的,无论是否有 DEBUG 陷阱(例如,运行“echo “this command = $BASH_COMMAND”' 以了解我在说什么)。通过为其分配一个变量(仅适用于该行),我们可以在命令的最外层范围内捕获 BASH_COMMAND,它将包含整个命令。
作者的上一篇文章在使用 DEBUG 实现技术时也提供了一些很好的背景知识trap
。trap
在改进版本中已消除。
答案4
trap...debug 的一个常见用途是改进使用“screen”时窗口列表(^A)中出现的标题。
我试图让“屏幕”、“窗口列表”更易于使用,因此我开始寻找引用“陷阱...调试”的文章。
我发现使用 PROMPT_COMMAND 发送“空标题序列”的方法效果不太好,所以我恢复了陷阱...调试方法。
操作方法如下:
1 通过将“shelltitle '$|bash:'”放入“$HOME/.screenrc”来告诉“screen”查找转义序列(启用它)。
2 关闭调试陷阱向子 shell 的传播。这很重要,因为如果启用它,一切都会变得混乱,使用:“set +o functrace”。
3 发送标题转义序列供“screen”解释:trap ‘printf "\ek$(date +%Y%m%d%H%M%S) $(whoami)@$(hostname):$(pwd) ${BASH_COMMAND}\e\"’ “DEBUG”
它并不完美,但它有帮助,你可以用这种方法把任何你喜欢的东西放进标题里
参见以下“窗口列表”(5 个屏幕):
编号 名称 标志
1 bash:20161115232035 mcb@ken007:/home/mcb/ken007 RSYNCCMD="sudo rsync" myrsync.sh -R -r "${1}" "${BACKUPDIR}${2:+"/${2}"}" $ 2 bash:20161115230434 mcb@ken007:/home/mcb/ken007 ls --color=auto -la $ 3 bash:20161115230504 mcb@ken007:/home/mcb/ken007 cat bin/psg.sh $ 4 bash:20161115222415 mcb@ken007:/home/mcb/ken007 ssh ken009 $ 5 bash:20161115222450 mcb@ken007:/home/mcb/ken007 mycommoncleanup $