回显在 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 命令总是失败(根据定义)。传递给false12345 只是为了表明失败命令的参数也被捕获(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 中运行的最后一个命令?的主要内容,如果未能解决你的问题,请参考以下文章

通过动态类加载解决通过Tomcat全局存储进行回显在Shiro中的Header过长问题

屏蔽回显

使用子进程运行多个 bash 命令

如何回显在我的数据库中存储为 blob 的图像?

实现将a页面的数据传入到b页面中并回显在b页面

是否可以获取在父终端中运行的最后一个命令?