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

发布于 2024-11-09 16:30:21 字数 1500 浏览 2 评论 0原文

我试图回显 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 脚本中如何/在何处

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

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,sed which 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 case statement:

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?

My answer

Despite the much appreciated contributions from my fellow SO'ers, I opted for writing a run function - 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 run which 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

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 run function 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

↙温凉少女 2024-11-16 16:30:21

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

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

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

!:4 = 上一个命令的第四个参数

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

!^ = 上一个命令的第一个参数(与 !:1 相同)

!$ = 的最后一个参数上一个命令

!:-3 = 0-3 范围内的所有参数(含)

!:2-5 = 2-5 范围内的所有参数(含)

!! = 上一个命令行

等。

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

echo !!

...或者:

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

自己尝试一下!

echo this is a test
echo !!

在脚本中,历史记录扩展默认是关闭的,您需要启用它

set -o history -o histexpand

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

!:0 = the name of command executed.

!:1 = the first parameter of the previous command

!:4 = the fourth parameter of the previous command

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

!^ = the first parameter of the previous command (same as !:1)

!$ = the final parameter of the previous command

!:-3 = all parameters in range 0-3 (inclusive)

!:2-5 = all parameters in range 2-5 (inclusive)

!! = 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
鱼忆七猫命九 2024-11-16 16:30:21

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

DEBUG 陷阱 让您可以在执行任何简单命令之前执行命令。要执行的命令的字符串版本(单词之间用空格分隔)可在 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

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

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

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

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

The DEBUG trap lets 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_COMMAND variable.

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

Note that previous_command will 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.

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 -e to make your script exit on the first failed command. You can display the last command from the EXIT trap.

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.

吐个泡泡 2024-11-16 16:30:21

阅读了 Gilles 的答案后,我决定看看 $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 在命令失败且错误命令始终时退出脚本失败(根据定义)。传递给 false12345 只是为了表明失败命令的参数也被捕获(false 命令忽略传递的任何参数)到它)

After reading the answer from Gilles, I decided to see if the $BASH_COMMAND var was also available (and the desired value) in an EXIT trap - and it is!

So, the following bash script works as expected:

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

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

温柔嚣张 2024-11-16 16:30:21

我能够通过在主脚本中使用 set -x (这使得脚本打印出执行的每个命令)并编写一个包装器脚本来实现这一点,该脚本仅显示由设置-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

I was able to achieve this by using set -x in 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.

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
月亮邮递员 2024-11-16 16:30:21

历史 |尾部-2 |头-1 | cut -c8-

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

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

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

圈圈圆圆圈圈 2024-11-16 16:30:21

最后一个命令 ($_) 和最后一个错误 ($?) 变量之间存在竞争条件。如果您尝试将其中之一存储在自己的变量中,则由于 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

虽然我的帖子可能不会得到任何积极的评价,但我最终自己解决了我的问题。
对于最初的帖子来说,这似乎是合适的。 :)

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.

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:

   # 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.

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.

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. :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文