Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Echoing the last command run in Bash?

Tags:

bash

command

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.

like image 616
Max Avatar asked May 24 '11 10:05

Max


People also ask

How do I undo the last bash command?

In Bash and Zsh ctrl + w erases backwards from where the cursor is.

How do you repeat the last command in bash?

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.

How do I get rid of last command?

To reverse your last action, press CTRL+Z. You can reverse more than one action. To reverse your last Undo, press CTRL+Y.


2 Answers

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 
like image 101
groovyspaceman Avatar answered Sep 19 '22 13:09

groovyspaceman


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.

like image 24
Gilles 'SO- stop being evil' Avatar answered Sep 21 '22 13:09

Gilles 'SO- stop being evil'