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.
In Bash and Zsh ctrl + w erases backwards from where the cursor is.
To get the same effect as using the up arrow once, which is showing the last command run without executing it, you can use CTRL+P. Or change the number, and run any of your last commands. For example changing the 1 to a 2 will run the second from last command. This is all made possible by using bash history.
To reverse your last action, press CTRL+Z. You can reverse more than one action. To reverse your last Undo, press CTRL+Y.
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
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With