回显在 Bash 中运行的最后一个命令?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6109225/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 20:32:52  来源:igfitidea点击:

Echoing the last command run in Bash?

bashcommand

提问by Max

I am trying to echo the last command run inside a bash script. I found a way to do it with some history,tail,head,sedwhich works fine when commands represent a specific line in my script from a parser standpoint. However under some circumstances I don't get the expected output, for instance when the command is inserted inside a casestatement:

我正在尝试回显在 bash 脚本中运行的最后一个命令。我找到了一种方法,history,tail,head,sed当命令从解析器的角度代表我的脚本中的特定行时,它可以很好地工作。但是在某些情况下,我没有得到预期的输出,例如当命令插入到case语句中时:

The script:

剧本:

#!/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

The output:

输出:

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] Can someone help me find a way to echo the last run command regardless of how/where this command is called within the bash script?

[问] 有人能帮我找到一种方法来回显上次运行命令,而不管在 bash 脚本中如何/在何处调用此命令?

My answer

我的答案

Despite the much appreciated contributions from my fellow SO'ers, I opted for writing a runfunction - which runs all its parameters as a single command and display the command and its error code when it fails - with the following benefits:
-I only need to prepend the commands I want to check with runwhich keeps them on one line and doesn't affect the conciseness of my script
-Whenever the script fails on one of these commands, the last output line of my script is a message that clearly displays which command fails along with its exit code, which makes debugging easier

尽管我的 SO'ers 同事非常感谢他们的贡献,我还是选择编写一个run函数——它将所有参数作为单个命令运行,并在失败时显示命令及其错误代码——具有以下好处:
-我只需要将我想检查的命令run放在一行前面,并且不会影响我的脚本的简洁性 -
每当脚本在这些命令之一上失败时,我的脚本的最后输出行是一条消息,清楚地显示哪个命令失败及其退出代码,这使得调试更容易

Example script:

示例脚本:

#!/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

The output:

输出:

$ ./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

I tested various commands with multiple arguments, bash variables as arguments, quoted arguments... and the runfunction didn't break them. The only issue I found so far is to run an echo which breaks but I do not plan to check my echos anyway.

我测试了带有多个参数的各种命令,bash 变量作为参数,引用参数......并且该run函数没有破坏它们。到目前为止我发现的唯一问题是运行一个中断的回声,但我不打算检查我的回声。

采纳答案by Gilles 'SO- stop being evil'

The command history is an interactive feature. Only complete commands are entered in the history. For example, the caseconstruct is entered as a whole, when the shell has finished parsing it. Neither looking up the history with the historybuilt-in (nor printing it through shell expansion (!:p)) does what you seem to want, which is to print invocations of simple commands.

命令历史是一种交互式功能。历史中只输入完整的命令。例如,case当 shell 完成对构造的解析时,构造作为一个整体输入。使用history内置!:p函数查找历史记录(也不通过 shell 扩展 ( )打印它)都不会做您似乎想要的,即打印简单命令的调用。

The DEBUGtraplets you execute a command right before any simple command execution. A string version of the command to execute (with words separated by spaces) is available in the BASH_COMMANDvariable.

DEBUG陷阱让你任何简单的命令执行前执行命令权。要执行的命令的字符串版本(单词用空格分隔)在BASH_COMMAND变量中可用。

trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG
…
echo "last command is $previous_command"

Note that previous_commandwill change every time you run a command, so save it to a variable in order to use it. If you want to know the previous command's return status as well, save both in a single command.

请注意,previous_command每次运行命令时都会更改,因此将其保存到变量中以便使用。如果您还想知道上一个命令的返回状态,请将两者保存在一个命令中。

cmd=$previous_command ret=$?
if [ $ret -ne 0 ]; then echo "$cmd failed with error code $ret"; fi

Furthermore, if you only want to abort on a failed commands, use set -eto make your script exit on the first failed command. You can display the last command from the EXITtrap.

此外,如果您只想在失败的命令上中止,请使用set -e使您的脚本在第一个失败的命令上退出。您可以显示来自EXIT陷阱的最后一个命令。

set -e
trap 'echo "exit $? due to $previous_command"' EXIT

Note that if you're trying to trace your script to see what it's doing, forget all this and use set -x.

请注意,如果您试图跟踪脚本以查看它在做什么,请忘记所有这些并使用set -x.

回答by groovyspaceman

Bash has built in features to access the last command executed. But that's the last whole command (e.g. the whole casecommand), not individual simple commands like you originally requested.

Bash 内置了访问最后执行的命令的功能。但这是最后一个完整的命令(例如整个case命令),而不是您最初请求的单个简单命令。

!:0= the name of command executed.

!:0= 执行的命令的名称。

!:1= the first parameter of the previous command

!:1= 上一条命令的第一个参数

!:*= all of the parameters of the previous command

!:*= 上一条命令的所有参数

!:-1= the final parameter of the previous command

!:-1= 上一条命令的最后一个参数

!!= the previous command line

!!= 上一个命令行

etc.

等等。

So, the simplest answer to the question is, in fact:

所以,这个问题最简单的答案实际上是:

echo !!

...alternatively:

...或者:

echo "Last command run was ["!:0"] with arguments ["!:*"]"

Try it yourself!

自己试试吧!

echo this is a test
echo !!

In a script, history expansion is turned off by default, you need to enable it with

在脚本中,历史扩展默认是关闭的,你需要用

set -o history -o histexpand

回答by Hercynium

After reading the answerfrom Gilles, I decided to see if the $BASH_COMMANDvar was also available (and the desired value) in an EXITtrap - and it is!

在阅读了Gilles答案后,我决定看看var 在陷阱中是否也可用(以及所需的值)- 确实如此!$BASH_COMMANDEXIT

So, the following bash script works as expected:

因此,以下 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"

The output is

输出是

foo
Command [false 12345] exited with code [1]

baris never printed because set -ecauses bash to exit the script when a command fails and the false command always fails (by definition). The 12345passed to falseis just there to show that the arguments to the failed command are captured as well (the falsecommand ignores any arguments passed to it)

bar永远不会打印,因为set -e当命令失败时会导致 bash 退出脚本并且 false 命令总是失败(根据定义)。该12345传递给false在这里只用来表明该参数传递给失败的命令被捕获,以及(在false命令忽略传递给它的任何参数)

回答by Mark Drago

I was able to achieve this by using set -xin the main script (which makes the script print out every command that is executed) and writing a wrapper script which just shows the last line of output generated by set -x.

我能够通过set -x在主脚本中使用(它使脚本打印出执行的每个命令)并编写一个包装脚本来实现这一点,该脚本仅显示set -x.

This is the main script:

这是主要脚本:

#!/bin/bash
set -x
echo some command here
echo last command

And this is the wrapper script:

这是包装脚本:

#!/bin/sh
./test.sh 2>&1 | grep '^\+' | tail -n 1 | sed -e 's/^\+ //'

Running the wrapper script produces this as output:

运行包装器脚本会产生以下输出:

echo last command

回答by Guma

history | tail -2 | head -1 | cut -c8-999

history | tail -2 | head -1 | cut -c8-999

tail -2returns the last two command lines from history head -1returns just first line cut -c8-999returns just command line, removing PID and spaces.

tail -2返回历史记录中的最后两个命令行 head -1仅返回第一行 cut -c8-999仅返回命令行,删除 PID 和空格。

回答by WGRM

There is a racecondition between the last command ($_) and last error ( $?) variables. If you try to store one of them in an own variable, both encountered new values already because of the set command. Actually, last command hasn't got any value at all in this case.

最后一个命令 ($_) 和最后一个错误 ($?) 变量之间存在竞争条件。如果您尝试将其中之一存储在自己的变量中,由于 set 命令,两者都已经遇到了新值。实际上,在这种情况下,最后一个命令根本没有任何价值。

Here is what i did to store (nearly) both informations in own variables, so my bash script can determine if there was any error AND setting the title with the last run command:

这是我将(几乎)这两个信息存储在自己的变量中的操作,因此我的 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

This script will retain the information, if an error occured and will obtain the last run command. Because of the racecondition i can not store the actual value. Besides, most commands actually don't even care for error noumbers, they just return something different from '0'. You'll notice that, if you use the errono extention of bash.

如果发生错误,此脚本将保留该信息并获取上次运行命令。由于竞争条件,我无法存储实际值。此外,大多数命令实际上甚至不关心错误编号,它们只是返回与“0”不同的东西。你会注意到,如果你使用 bash 的错误扩展。

It should be possible with something like a "intern" script for bash, like in bash extention, but i'm not familiar with something like that and it wouldn't be compatible as well.

应该可以使用诸如 bash 的“实习生”脚本之类的东西,例如 bash 扩展,但我不熟悉类似的东西,而且也不兼容。

CORRECTION

更正

I didn't think, that it was possible to retrieve both variables at the same time. Although i like the style of the code, i assumed it would be interpreted as two commands. This was wrong, so my answer devides down to:

我不认为可以同时检索两个变量。虽然我喜欢代码的风格,但我认为它会被解释为两个命令。这是错误的,所以我的答案归结为:

   # 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

Although my post might not get any positive rating, i solved my problem myself, finally. And this seems appropriate regarding the intial post. :)

虽然我的帖子可能没有得到任何正面评价,但我终于自己解决了我的问题。这对于初始帖子似乎是合适的。:)