Detect empty command

Here's a funny, very simple possibility: it uses the \# escape sequence of PS1 together with parameter expansions (and the way Bash expands its prompt).

The escape sequence \# expands to the command number of the command to be executed. This is incremented each time a command has actually been executed. Try it:

$ PS1='\# $ '
2 $ echo hello
hello
3 $ # this is a comment
3 $
3 $    echo hello
hello
4 $

Now, each time a prompt is to be displayed, Bash first expands the escape sequences found in PS1, then (provided the shell option promptvars is set, which is the default), this string is expanded via parameter expansion, command substitution, arithmetic expansion, and quote removal.

The trick is then to have an array that will have the k-th field set (to the empty string) whenever the (k-1)-th command is executed. Then, using appropriate parameter expansions, we'll be able to detect when these fields are set and to display the return code of the previous command if the field isn't set. If you want to call this array __cmdnbary, just do:

PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '

Look:

$ PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '

0 $ [ 2 = 3 ]

1 $ 

$ # it seems that it works

$     echo "it works"
it works

0 $

To qualify for the shortest answer challenge:

PS1='\n${a[\#]-$? }${a[\#]=}$ '

that's 31 characters.

Don't use this, of course, as a is a too trivial name; also, \$ might be better than $.


Seems you don't like that the initial prompt is 0 $; you can very easily modify this by initializing the array __cmdnbary appropriately: you'll put this somewhere in your configuration file:

__cmdnbary=( '' '' ) # Initialize the field 1!
PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '

Got some time to play around this weekend. Looking at my earlier answer (not-good) and other answers I think this may be probably the smallest answer.

Place these lines at the end of your ~/.bash_profile:

PS1='$_ret$ '
trapDbg() {
   local c="$BASH_COMMAND"
   [[ "$c" != "pc" ]] && export _cmd="$c"
}
pc() {
   local r=$?
   trap "" DEBUG
   [[ -n "$_cmd" ]] && _ret="$r " || _ret=""
   export _ret
   export _cmd=
   trap 'trapDbg' DEBUG
}
export PROMPT_COMMAND=pc
trap 'trapDbg' DEBUG

Then open a new terminal and note this desired behavior on BASH prompt:

$ uname
Darwin
0 $
$
$
$ date
Sun Dec 14 05:59:03 EST 2014
0 $
$
$ [ 1 = 2 ]
1 $
$
$ ls 123
ls: cannot access 123: No such file or directory
2 $
$

Explanation:

  • This is based on trap 'handler' DEBUG and PROMPT_COMMAND hooks.
  • PS1 is using a variable _ret i.e. PS1='$_ret$ '.
  • trap command runs only when a command is executed but PROMPT_COMMAND is run even when an empty enter is pressed.
  • trap command sets a variable _cmd to the actually executed command using BASH internal var BASH_COMMAND.
  • PROMPT_COMMAND hook sets _ret to "$? " if _cmd is non-empty otherwise sets _ret to "". Finally it resets _cmd var to empty state.