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.
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 thehistorybuilt-in (nor printing it through shell expansion (!:p)) does what you seem to want, which is to print invocations of simple commands.The
DEBUGtrap 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 theBASH_COMMANDvariable.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.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 theEXITtrap.Note that if you’re trying to trace your script to see what it’s doing, forget all this and use
set -x.