Do not set $? to non-zero on Control+C

command linesignalszsh

My $PS1 in Zsh includes this expression: %(?. %?.)
It means «if exit code of previous command ($?) is true, show $?, else show nothing».
This is generally useful, but one thing annoys me: shells set return code of last command to non-zero when I press Control+C without running a command! Zsh sets it to 1, Bash sets it to 130. Dash is the only shell on my system that does not reset it on Control+C.

How to reproduce:

  1. Launch zsh or bash.
  2. Press Control+C.
  3. echo $?.

Is there a way to turn off this behavior on Zsh?

Best Answer

I don't think there's any way to turn it off.

First thought is to have preexec set a variable that indicates a command was run. If you pressed Ctrl+C at the prompt, it wouldn't get set.

precmd() {
    exit_status=$?
    if ! $ran_something; then
        exit_status=0
    fi
    ran_something=false
}
preexec() {
    ran_something=true
}
show_non_zero_exit_status() {
    case $exit_status in
    0)
        :;;
    *)
        echo $exit_status;;
    esac
}
PS1='$(show_non_zero_exit_status)$ '

But there's another similar problem: if you suspend a command, you'll get exit status 20 (zsh < 5.0.7) or 148 (bash and zsh >= 5.0.7).

To get around that, you can add 20|148 to the case statement above, i.e.

show_non_zero_exit_status() {
    case $exit_status in
    0|20|148)
        :;;
    *)
        echo $exit_status;;
    esac
}

If you're using zsh, make sure you have setopt promptsubst in your .zshrc.

If you're using bash, add:

PROMPT_COMMAND=precmd
trap preexec DEBUG

but really you should use the more complicated DEBUG trap mentioned in https://superuser.com/a/175802/15334

Another possibility is that setopt printexitvalue is close enough to what you want.

Related Question