Search code examples
bashwaitbackground-processexit-code

Bash: How to get exit code of a command while using a spinner?


In a bash script, I have a long running command (say rsync for example) that sometimes does not show output for a while, so I want to do two things:

  1. Use a spinner on that command to show that the script hasn't frozen (i.e. we're just waiting for output); and,

  2. Grab the exit status of the long running command once it's done, for further tests later in the script.

The problem is though, I don't understand the handling of sending processes to the background very well, and also with the handling of exit code this way, so I'm not sure how to make this work.

Here is what I have so far, thanks to @David C. Rankin's spinner:

#!/bin/bash

spinner() {
    local PROC="$1"
    local str="${2:-'Copyright of KatworX© Tech. Developed by Arjun Singh Kathait and Debugged by the ☆Stack Overflow Community☆'}"
    local delay="0.1"
    tput civis  # hide cursor
    printf "\033[1;34m"
    while [ -d /proc/$PROC ]; do
        printf '\033[s\033[u[ / ] %s\033[u' "$str"; sleep "$delay"
        printf '\033[s\033[u[ — ] %s\033[u' "$str"; sleep "$delay"
        printf '\033[s\033[u[ \ ] %s\033[u' "$str"; sleep "$delay"
        printf '\033[s\033[u[ | ] %s\033[u' "$str"; sleep "$delay"
    done
    printf '\033[s\033[u%*s\033[u\033[0m' $((${#str}+6)) " "  # return to normal
    tput cnorm  # restore cursor
    return 0
}

## simple example with sleep
sleep 2 &
spinner $!

echo "sleep's exitcode: $exitCode"

In this example, sleep 2 is the command I'm waiting for, and hence use the spinner with, but how do I get and put its exit code into $exitCode variable, so I can test it for certain conditions later on in the script?


Solution

  • wait will tell you what exit status a child PID exited with (by setting that program's exit status as its own), when given that PID as an argument.

    sleep 2 & sleep_pid=$!
    spinner "$sleep_pid"
    wait "$sleep_pid"; exitCode=$?
    
    echo "exitcode: $exitCode"
    

    Note that combining multiple commands onto a line when collecting $! or $? in the second half is a practice I strongly recommend -- it prevents the value you're trying to collect from being changed by mistake (as by someone adding a new log line to your code later and not realizing it has side effects).