13

The $? variable holds the exit status of last run command. Is there a variable that holds last run command itself?

1

5 Answers 5

8

Use fc to get the previous command line. It is normally used to edit the previous command line in your favourite editor, but it has a "list" mode, too:

last_command="$(fc -nl -1)"

2
  • this sadly doesn't work well quite how I expected if there are large case statements in use or functions in use... :( I ended up using caller and the bash arrays BASH_LINENO, BASH_SOURCE, and FUNCNAME to do a kind of stack trace.
    – phyatt
    Commented Nov 7, 2016 at 20:32
  • 1
    Works in interactive shell but not well in script. The "DEBUG trap" approach below is better
    – Bing Ren
    Commented Oct 19, 2023 at 4:16
8

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

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

An alternate approach that might work for some uses is to use set -x to print a trace of the script's execution and examine the last few lines of the trace.

1
  • If you only want to trigger the trap when an error occured (the case for set -e), you can use the signal (last argument) ERR instead of EXIT and get rid if the if.
    – karfau
    Commented Jan 20, 2022 at 5:51
7

If the last command was executed without arguments, it'll be saved in the $_ variable. This normally contains the last argument of previous command - so if there were no arguments, the value of $_ is the last command itself.

Another option is to learn the details of last background command. As l0b0 wrote, $! holds its PID - so you can parse the output of ps $! (possibly with additional formating options to ps).

4

No, but you can get it during execution to store for other commands:

  • $0: Path of the current shell script.
  • $FUNCNAME: "Name of the current function."
  • "$@": All the parameters of the current command, quoted separately.
  • $!: "PID (process ID) of last job run in background."
  • $$: "Process ID (PID) of the script itself."

The full command of the current script should therefore be "$0" "$@". If it's a function it should be "$FUNCNAME" "$@". You might want to store that in an array for future processing. For example, store this in test.sh:

#!/usr/bin/env bash
foo()
{
    declare -a command=("$0")
    for param in "$@"
    do
        command+=("$(printf %q "$param")")
    done
    echo "${command[@]}"
}
foo "$@"

When running ./test.sh "first argument" "second argument", it should return:

./test.sh first\ argument second\ argument

Which are equivalent calls.

3
  • In bash there is a BASH_COMMAND variable, but do not seems to be useful in any way, apart from use in traps.
    – enzotib
    Commented Oct 3, 2011 at 8:26
  • Thanks for your input. What about if I run some-command in a shell script, and it fails. I'll have non-zero status in $?, will "no" still hold for the existence of variable holding some-command?
    – Eimantas
    Commented Oct 3, 2011 at 9:10
  • As far as I know, the sole fact that a command failed does not change the set of information your shell stores about it. So I'd say Yes, "no". Commented Oct 3, 2011 at 11:36
1

I find it essential to find the last failed command when having set -e and set -o pipefail options, as otherwise bash simply aborts with no feedback on why, so this is what I found working well:

#!/usr/bin/env bash
set -eu
set -o pipefail

cur_command=
first_err_command=
first_err_lineno=
# This trap is executed in exactly the same conditions in which the `set -e` results in an exit.
trap 'cur_command=$BASH_COMMAND;
      if [[ -z "$first_err_command" ]]; then
          first_err_command=$cur_command;
          first_err_lineno=$LINENO;
      fi' ERR
trap 'if [[ ! -z "$first_err_command" ]]; then
          echo "ERROR: Aborting at line: $first_err_lineno on command: $first_err_command";
      fi' EXIT

echo "The following command causes bash to abort, but it should also result in a nice message"
false
echo "This message is not expected"

If you run the above, you will end up seeing the below below sort of output:

The following command causes bash to abort, but it should also result in a nice message
ERROR: Aborting at line: 22 on command: false

The line number may not always be accurate, but it should give you something close enough to be useful.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .