bashps1parameter-expansion

Display the exit code of previous command only when it's non-zero, without using commands?


Inspired by this answer implementing command execution time display with only substitutions, I'm trying to rewrite my exit code display with the same feature - it should produce no extra output even under set -x.

This is my current .bashrc:

PROMPT_COMMAND() {
  local e=$?
  PROMPT_ECODE=""
  (( e )) && PROMPT_ECODE="$e|"
  return $e
}
PROMPT_COMMAND=PROMPT_COMMAND
PS1='${PROMPT_ECODE}'"$PS1"

The current display result is exactly what I need: If $? is non-zero, show it before the usual PS1, with a vertical bar in between. If $? is zero, do nothing and display PS1.

This implementation produces some 4 extra lines for every PS1 printed if set -x. This is annoying and I'd like to avoid it.


Y problem: I know I can append a vertical bar to a non-zero $? with ${?:+$?|}, but I'm stuck at expanding a zero $? to nothing (empty string). I can't find a way to assign an empty string to an existing non-empty variable in the process of Parameter Expansion or Arithmetic Expansion.


Solution

  • I know I can append a vertical bar to a non-zero $? with ${?:+$?|}, but I'm stuck at expanding a zero $? to nothing (empty string). I can't find a way to assign an empty string to an existing non-empty variable in the process of Parameter Expansion or Arithmetic Expansion.

    You can do this with the help of a small utility array:

    _only_1=([1]=1)
    

    Then _only_1[1] has a defined value, but _only_1[0] does not, thus ${_only_1[1]:+foo} evaluates to foo, but ${_only_1[0]:+foo} evaluates to nothing. The idea would be that this array is defined once, perhaps in your .bash_profile, not every time your prompt is computed.

    We can combine that with arithmetic evaluation to squash all non-zero exit statuses to 1: ((\!\!$?)). The \! is the logical negation operator, escaped to avoid triggering history expansion.

    Put together, we have something like this:

    ${_only_1[((\!\!$?))]:+$?|}
    

    ... which expands to nothing when $? is 0, and to the same thing as "$?|" when $? is nonzero. Demo:

    _only_1=([1]=1)
    set -x
    echo "${_only_1[((\!\!$?))]:+$?|}"
    (exit 3)
    echo "${_only_1[((\!\!$?))]:+$?|}"
    

    output:

    + echo ''
    
    + exit 3
    + echo '3|'
    3|
    

    That's a bit cluttered up with the exit and echo commands, but those should be attributed to the demo framework. The key thing to observe is thet there is no trace output for the expansions that produce alternatively nothing or 3|.

    Note that if ever that _only_1 support array happens to be unset, which could happen for a variety of reasons, then you will lose the exit-code augmentation of your prompt, but you should not otherwise be adversely affected. Similarly, if _only_1[0] gets defined to a non-null value, then you will start getting prompt augmentation for exit status 0, too, but there should be no other adverse effect.

    Update:

    I realized after the fact that I made this slightly more complicated than needed. I could have instead relied on an array with only index 0 defined, and used single-negation instead of double:

    _only_0=(0)
    
    # ...
    
    (exit 3)
    echo "${_only_0[((\!$?))]:+$?|}"