我正在尝试回显在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]"]
[问]有人可以帮我找到一种方法来回显最后一次运行命令,无论在bash脚本中如何/在何处调用此命令?
我的回答
尽管我的同事们非常赞赏,但我选择编写一个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
函数没有破坏它们。到目前为止我发现的唯一问题是运行一个中断的回声,但我不打算检查我的回声。
答案 0 :(得分:145)
Bash内置了一些功能来访问最后执行的命令。但这是最后一个整个命令(例如整个case
命令),而不是你最初请求的单个简单命令。
!:0
=已执行命令的名称。
!:1
=上一个命令的第一个参数
!:*
=上一个命令的所有参数
!:-1
=上一个命令的最后一个参数
!!
=上一个命令行
等
所以,问题的最简单答案实际上是:
echo !!
...或者:
echo "Last command run was ["!:0"] with arguments ["!:*"]"
亲自试试!
echo this is a test
echo !!
在脚本中,默认情况下会关闭历史记录扩展,您需要使用
启用它set -o history -o histexpand
答案 1 :(得分:53)
命令历史记录是一种交互式功能。只在历史记录中输入完整的命令。例如,当shell完成解析时,case
构造作为一个整体输入。用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
。
答案 2 :(得分:14)
从answer阅读Gilles后,我决定查看$BASH_COMMAND
陷阱中EXIT
var是否也可用(以及所需的值) - 然后是!
因此,以下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命令总是失败(根据定义)。传递给12345
的{{1}}就是为了显示失败命令的参数也被捕获(false
命令忽略传递给它的任何参数)
答案 3 :(得分:7)
我能够通过在主脚本中使用set -x
(这使得脚本打印出每个执行的命令)并编写一个包装脚本来实现这一点,该脚本只显示由{{生成的最后一行输出1}}。
这是主要的脚本:
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/^\+ //'
答案 4 :(得分:0)
最后一个命令($ _)和最后一个错误($?)变量之间存在竞争条件。如果您尝试将其中一个存储在自己的变量中,则由于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
如果发生错误,此脚本将保留信息,并将获取上次运行命令。由于竞争条件,我无法存储实际值。此外,大多数命令实际上甚至不关心错误noumbers,它们只返回不同于'0'的东西。你会注意到,如果你使用bash的errono扩展。
应该可以使用类似bash的“实习生”脚本,比如在bash扩展中,但是我不熟悉这样的东西,它也不兼容。
CORRECTION
我没想到,可以同时检索这两个变量。虽然我喜欢代码的风格,但我认为它会被解释为两个命令。这是错误的,所以我的回答归结为:
# 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
虽然我的帖子可能没有得到任何正面评价,但我最终解决了我的问题。 这对于初始帖子来说似乎是合适的。 :)
答案 5 :(得分:0)
history | tail -2 | head -1 | cut -c8-999
tail -2
返回历史记录的最后两个命令行
head -1
仅返回第一行
cut -c8-999
仅返回命令行,删除PID和空格。