BASH:回显最后一个命令运行

时间:2011-05-24 10:45:17

标签: bash command

我正在尝试回显在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函数没有破坏它们。到目前为止我发现的唯一问题是运行一个中断的回声,但我不打算检查我的回声。

6 个答案:

答案 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和空格。