回显在 Bash 中运行的最后一个命令?
Posted
技术标签:
【中文标题】回显在 Bash 中运行的最后一个命令?【英文标题】:Echoing the last command run in Bash? 【发布时间】:2011-08-31 20:10:55 【问题描述】:我正在尝试回显在 bash 脚本中运行的最后一个命令。我找到了一种使用 history,tail,head,sed
的方法,从解析器的角度来看,当命令代表我脚本中的特定行时,它可以正常工作。但是在某些情况下,我没有得到预期的输出,例如当命令插入到 case
语句中时:
脚本:
#!/bin/bash
set -o history
date
last=$(echo `history |tail -n2 |head -n1` | sed 's/[0-9]* //')
echo "last command is [$last]"
case "1" in
"1")
date
last=$(echo `history |tail -n2 |head -n1` | sed 's/[0-9]* //')
echo "last command is [$last]"
;;
esac
输出:
Tue May 24 12:36:04 CEST 2011
last command is [date]
Tue May 24 12:36:04 CEST 2011
last command is [echo "last command is [$last]"]
[Q] 有人可以帮我找到一种方法来回显上次运行的命令,而不管在 bash 脚本中如何/在何处调用此命令?
我的回答
尽管我的 SO'ers 同事做出了非常感谢的贡献,但我还是选择编写 run
函数 - 它将所有参数作为单个命令运行并在失败时显示命令及其错误代码 - 具有以下好处:
- 我只需要在我想检查的命令前面加上run
,这样可以将它们保持在一行并且不会影响我的脚本的简洁性
- 每当脚本在其中一个命令上失败时,我的脚本的最后输出行是一条消息,清楚地显示哪个命令失败及其退出代码,这使得调试更容易
示例脚本:
#!/bin/bash
die() echo >&2 -e "\nERROR: $@\n"; exit 1;
run() "$@"; code=$?; [ $code -ne 0 ] && die "command [$*] failed with error code $code";
case "1" in
"1")
run ls /opt
run ls /wrong-dir
;;
esac
输出:
$ ./test.sh
apacheds google iptables
ls: cannot access /wrong-dir: No such file or directory
ERROR: command [ls /wrong-dir] failed with error code 2
我测试了带有多个参数的各种命令,bash 变量作为参数,引用的参数......并且run
函数没有破坏它们。到目前为止我发现的唯一问题是运行一个中断的回声,但我不打算检查我的回声。
【问题讨论】:
+1,好主意!但是请注意,run()
在使用引号时无法正常工作,例如失败:run ssh-keygen -t rsa -C info@example.org -f ./id_rsa -N ""
。
@johndodo:可以修复:只需将参数中的"something"
更改为'"something"'
(或者,更确切地说,"'something'"
,以允许解释something
(例如:变量)/如果需要,在第一级进行评估)
我已将错误的run() $*; …
更改为更接近正确的run() "$@"; …
,因为错误的答案最终产生了问题cp
exits with a 64 error status,问题在于$*
破坏了命令名称中空格处的参数,但"$@"
不会这样做。
Unix StackExchange 上的相关问题:unix.stackexchange.com/questions/21930/…
last=$(history | tail -n1 | sed 's/^[[:space:]][0-9]*[[:space:]]*//g')
效果更好,至少对于 zsh 和 macOS 10.11
【参考方案1】:
Bash 具有访问最后执行的命令的内置功能。但这是最后一个完整的命令(例如整个 case
命令),而不是您最初请求的单个简单命令。
!:0
= 执行命令的名称。
!:1
= 上一条命令的第一个参数
!:*
= 上一条命令的所有参数
!$
= 上一条命令的最后一个参数
!:-1
= 0-1(含)范围内的所有参数
!!
= 上一个命令行
等等
所以,这个问题最简单的答案其实是:
echo !!
...或者:
echo "Last command run was ["!:0"] with arguments ["!:*"]"
自己试试吧!
echo this is a test
echo !!
在脚本中,历史扩展默认是关闭的,你需要使用
set -o history -o histexpand
【讨论】:
我见过的最有用的用例是re-running the last command with sudo access,即sudo !!
在 bash 脚本中使用 set -o history -o histexpand; echo "!!"
我仍然会收到错误消息:!!: event not found
(不带引号时相同。)
set -o history -o histexpand
在脚本中 -> 救星!谢谢!
有没有办法将此行为纳入时间函数使用的 TIMEFORMAT 字符串中?即 export TIMEFORMAT="*** !:0 花费了 %0lR"; /usr/bin/time find -name "*.log" ... 这不起作用,因为 !:0 在您运行导出时被评估:(
!:-1
不起作用。根据 bash 的文档,它不应该工作。可以改用!$
。见gnu.org/software/bash/manual/html_node/Word-Designators.html【参考方案2】:
命令历史是一个交互式功能。只有完整的命令会被输入到历史记录中。例如,case
结构在 shell 完成解析后作为一个整体输入。使用内置的 history
查找历史记录(也不是通过 shell 扩展 (!:p
) 打印)都不会执行您想要的操作,即打印简单命令的调用。
DEBUG
trap 可让您在执行任何简单命令之前立即执行命令。 BASH_COMMAND
变量中提供了要执行的命令的字符串版本(单词以空格分隔)。
trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG
…
echo "last command is $previous_command"
请注意,previous_command
会在您每次运行命令时更改,因此请将其保存到变量中以便使用。如果您还想知道上一个命令的返回状态,请将两者保存在一个命令中。
cmd=$previous_command ret=$?
if [ $ret -ne 0 ]; then echo "$cmd failed with error code $ret"; fi
此外,如果您只想在失败的命令上中止,请使用set -e
使您的脚本在第一个失败的命令上退出。您可以显示来自EXIT
trap 的最后一条命令。
set -e
trap 'echo "exit $? due to $previous_command"' EXIT
请注意,如果您尝试跟踪脚本以查看它在做什么,请忘记所有这些并使用set -x
。
【讨论】:
我已经尝试过你的 DEBUG 陷阱,但我不能让它工作,你能提供一个完整的例子吗?-x
输出每一个命令,但不幸的是我只对失败的命令感兴趣(如果我将它放在 [ ! "$? == "0" ]
语句中,我可以用我的命令来实现。
@user359650:已修复。在被当前命令覆盖之前,您需要保存上一个命令。要在命令失败时中止脚本,请使用 set -e
(通常但并非总是如此,该命令会产生足够好的错误消息,因此您无需提供进一步的上下文)。
感谢您的贡献。我最终编写了一个自定义函数(请参阅我的帖子),因为您的解决方案开销太大。
惊人的把戏。确定+1。我有 set -e 部分和 ERR 陷阱,你给了我 DEBUG 部分。非常感谢!
@JamesThomasMoon1979 通常eval echo "$BASH_COMMAND"
可以在命令替换中执行任意代码。这很危险。考虑像@987654342@ 这样的命令——现在想象一下名为rm
的命令替换。【参考方案3】:
从Gilles 读取answer 后,我决定查看$BASH_COMMAND
var 在EXIT
陷阱中是否也可用(以及所需的值) - 确实如此!
因此,以下 bash 脚本按预期工作:
#!/bin/bash
exit_trap ()
local lc="$BASH_COMMAND" rc=$?
echo "Command [$lc] exited with code [$rc]"
trap exit_trap EXIT
set -e
echo "foo"
false 12345
echo "bar"
输出是
foo
Command [false 12345] exited with code [1]
bar
永远不会被打印,因为set -e
在命令失败时会导致 bash 退出脚本,并且 false 命令总是失败(根据定义)。传递给false
的12345
只是为了表明失败命令的参数也被捕获(false
命令忽略传递给它的任何参数)
【讨论】:
这绝对是最好的解决方案。使用“set -euo pipefail”对我来说就像一个魅力【参考方案4】:我能够通过在主脚本中使用set -x
来实现这一点(这使得脚本打印出每个执行的命令)并编写一个仅显示set -x
生成的最后一行输出的包装脚本。
这是主脚本:
#!/bin/bash
set -x
echo some command here
echo last command
这是包装脚本:
#!/bin/sh
./test.sh 2>&1 | grep '^\+' | tail -n 1 | sed -e 's/^\+ //'
运行包装脚本会产生以下输出:
echo last command
【讨论】:
这看起来像是一种巧妙的方法。您的个人资料上有任何帖子以获取更多详细信息吗?发送。【参考方案5】:history | tail -2 | head -1 | cut -c8-999
tail -2
返回历史记录的最后两个命令行
head -1
只返回第一行
cut -c8-999
只返回命令行,删除 PID 和空格。
【讨论】:
你能解释一下命令参数是什么吗?这将有助于了解你做了什么 虽然这可能会回答问题,但最好添加一些关于此答案如何有助于解决问题的描述。请阅读How do I write a good answer 了解更多信息。 快速而简单。发送。 在很多情况下这个命令可能会中断。例如,当设置HISTTIMEFORMAT
,或用于多行命令,或用于超过 99'999 个条目的历史时。顺便说一句:history
只是枚举历史中的所有命令。这些数字与 PID 无关。【参考方案6】:
最后一个命令 ($_) 和最后一个错误 ($?) 变量之间存在竞争条件。如果您尝试将其中一个存储在自己的变量中,则由于 set 命令,两者都已经遇到了新值。实际上,在这种情况下,最后一条命令根本没有任何价值。
这是我将这两个信息(几乎)存储在自己的变量中所做的,因此我的 bash 脚本可以确定是否有任何错误并使用上次运行命令设置标题:
# This construct is needed, because of a racecondition when trying to obtain
# both of last command and error. With this the information of last error is
# implied by the corresponding case while command is retrieved.
if [[ "$?" == 0 && "$_" != "" ]] ; then
# Last command MUST be retrieved first.
LASTCOMMAND="$_" ;
RETURNSTATUS='✓' ;
elif [[ "$?" == 0 && "$_" == "" ]] ; then
LASTCOMMAND='unknown' ;
RETURNSTATUS='✓' ;
elif [[ "$?" != 0 && "$_" != "" ]] ; then
# Last command MUST be retrieved first.
LASTCOMMAND="$_" ;
RETURNSTATUS='✗' ;
# Fixme: "$?" not changing state until command executed.
elif [[ "$?" != 0 && "$_" == "" ]] ; then
LASTCOMMAND='unknown' ;
RETURNSTATUS='✗' ;
# Fixme: "$?" not changing state until command executed.
fi
如果发生错误,此脚本将保留信息并获取上次运行命令。由于竞争条件,我无法存储实际值。此外,大多数命令实际上甚至不关心错误号,它们只是返回与“0”不同的东西。如果您使用 bash 的 errono 扩展名,您会注意到这一点。
应该可以使用 bash 的“实习生”脚本之类的东西,比如在 bash 扩展中,但我不熟悉这样的东西,它也不兼容。
更正
我没想到,可以同时检索两个变量。虽然我喜欢代码的风格,但我认为它会被解释为两个命令。这是错误的,所以我的答案归结为:
# Because of a racecondition, both MUST be retrieved at the same time.
declare RETURNSTATUS="$?" LASTCOMMAND="$_" ;
if [[ "$RETURNSTATUS" == 0 ]] ; then
declare RETURNSYMBOL='✓' ;
else
declare RETURNSYMBOL='✗' ;
fi
虽然我的帖子可能没有得到任何正面评价,但我自己终于解决了我的问题。 对于最初的帖子,这似乎是合适的。 :)
【讨论】:
天哪,您只需立即收到它们,这似乎是可能的:声明 RETURNSTATUS="$?" LASTCOMMAND="$_" ; 效果很好,但有一个例外。如果我有更多参数的别名,它只会显示参数。有人得出结论吗? 看起来像一个灵活的基线。必须测试一下! @Cymatical 它今天仍然有效,尽管我不再使用这些符号了。 :)以上是关于回显在 Bash 中运行的最后一个命令?的主要内容,如果未能解决你的问题,请参考以下文章