Michael Plotke
Michael Plotke

Reputation: 1001

Wait for process to finish, or user input

I have a backgrounded process that I would like to wait for (in case it fails or dies), unless I receive user input. Said another way, the user input should interrupt my waiting.

Here's a simplified snippet of my code

#!/bin/bash
...

mplayer -noconsolecontrols "$media_url" &
sleep 10 # enough time for it to fail

ps -p $!
if [ $? -ne 0 ]
then
    fallback
else
    read
    kill $!
fi

The line that I particularly dislike is sleep 10, which is bad because it could be too much time, or not enough time.

Is there a way to wait $! || read or the equivalent?

Upvotes: 3

Views: 2058

Answers (3)

Michael Plotke
Michael Plotke

Reputation: 1001

Original

ps -p to check the process. read -t 1 to wait for user input.

pid=$!
got_input=142
while ps -p $pid > /dev/null; do
    if read -t 1; then
        got_input=$?
        kill $pid
    fi
done

This allows for branching based whether the process died, or was killed due to user input.


All credit to gubblebozer. The only reason I'm posting this answer is the claim by moderators that my edits to his post constituted altering his intent.

Anti Race-Condition

First off, a race condition involving pids is (very likely) not a concern if you're fairly quick, because they're reused on a cycle.

Even so, I guess anything is possible... Here's some code that handles that possibility, without breaking your head on traps.

got_input=142

while true; do
    if read -t 1; then
        got_input=$?
        pkill --ns $$ name > /dev/null
        break
    elif ! pgrep --ns $$ name > /dev/null; then
        break
    fi
done

Now, we've accomplished our goal, while (probably) completely eliminating the race condition.

Upvotes: 2

mvds
mvds

Reputation: 47034

Any loop with a sleep or similar timeout in it, will introduce a race condition. It's better to actively wait for the process to die, or, in this case, to trap the signal that's sent when a child dies.

#!/bin/bash

set -o monitor
trap stop_process SIGCHLD
stop_process()
{
    echo sigchld received
    exit
}

# the background process: (this simulates a process that exits after 10 seconds)
sleep 10 &

procpid=$!
echo pid of process: $procpid

echo -n hit enter: 
read

# not reached when SIGCHLD is received
echo killing pid $procpid
kill $procpid

I'm not 100% sure this eliminates any race condition, but it's a lot closer than a sleep loop.

edit: the shorter, less verbose version

#!/bin/bash

set -o monitor
trap exit SIGCHLD

sleep 5 &

read -p "hit enter: "
kill $!

edit 2: setting the trap before starting the background process prevents another race condition in which the process would die before the trap was installed

Upvotes: 0

Mike Andrews
Mike Andrews

Reputation: 3206

Use kill -0 to validate that the process is still there and read with a timeout of 0 to test for user input. Something like this?

pid=$!
while kill -0 $pid; do
    read -t 0 && exit
    sleep 1
done

Upvotes: 3

Related Questions