I can't figure out my bug on OSX. When I try to see when Curl is finished, the process remains loaded. I never see the CURL FINISHED message.
#!/bin/bash
curl -S -o example.com http://example.com/downloads/example.zip &
CURL_PID=$!
echo -e "CURL PID = $CURL_PID"
while :
do
sleep 1
if [ -n $(ps -p$CURL_PID -o pid=) ]; then
echo "CURL NOT FINISHED"
else
echo "CURL FINISHED"
break
fi
done
Note on OSX's version of Bash when I run this:
#!/bin/bash
PIDX=1
if [ -n $(ps -p$PIDX -o pid=) ]; then
echo "PROCESS 1 IS THERE"
else
echo "PROCESS 1 IS NOT THERE"
fi
...it says Process 1 is there. (Everyone has a PID 1, so this is just an example.) So, I know that my if
statement is correct. No double quotes necessary on the if
line.
Note that I can't use wait on the $CURL_PID
because what you don't see here is that I also am using OSX's osascript
command to show a dialog that says "Downloading...", which also has a Cancel button on it and its own $DLG_PID
, and so I'm looping endlessly until either they cancel the dialog (meaning $DLG_PID
points is gone) or $CURL_PID
is gone (meaning the download finally completed so I can run kill $DLG_PID
now).
On OSX, note I'm doing this as well before the curl
statement.
osascript -e 'tell app "System Events" to display dialog "Downloading..." with title "My App Installer" buttons {"Cancel"}' &
So, if someone cancels the dialog, I kill the curl by PID and exit the infinite loop (and exit the bash script). If they don't cancel that dialog, and the curl finishes, then I kill the dialog by PID and exit the bash script.
The portable way for polling a backgrounded job is to use the kill
builtin, and send the signal 0
to see if it's deliverable. kill -0 $pid
(where $pid
is the PID of a child process) will return zero if the child process is still running, and nonzero if it has already died. Note that this is safe and only safe (from PID recycling) for a child process (rather than some random process started elsewhere, with PID written to a PID file), for reasons outlined here:
Each UNIX process also has a parent process. This parent process is the process that started it, but can change to the init process if the parent process ends before the new process does. (That is,
init
will pick up orphaned processes.) Understanding this parent/child relationship is vital because it is the key to reliable process management in UNIX. A process's PID will NEVER be freed up for use after the process dies UNTIL the parent process waits for the PID to see whether it ended and retrieve its exit code. If the parent ends, the process is returned toinit
, which does this for you.This is important for one major reason: if the parent process manages its child process, it can be absolutely certain that, even if the child process dies, no other new process can accidentally recycle the child process's PID until the parent process has waited for that PID and noticed the child died. This gives the parent process the guarantee that the PID it has for the child process will ALWAYS point to that child process, whether it is alive or a "zombie". Nobody else has that guarantee.
Of course, newer versions of OS X don't use init
(in its place is launchd
), but the principle is the same.
By the way, the whole page is worth a read: http://mywiki.wooledge.org/ProcessManagement.
In light of that, here's an example script that does what you want (it takes one URL argument — the URL to download). Bug me if something's unclear.
#!/usr/bin/env bash
osascript -e 'tell app "System Events" to display dialog "Downloading..." with title "Downloader" buttons {"Cancel"}' &>/dev/null &
dialog_pid=$!
curl -sSLO "$1" &
curl_pid=$!
timer=0
while kill -0 "$curl_pid" &>/dev/null; do
kill -0 "$dialog_pid" &>/dev/null || { echo "User cancelled download from dialog."; kill "$curl_pid" &>/dev/null; exit 1; }
sleep 1
(( timer++ ))
echo "Been downloading for $timer seconds..."
done
echo "Finished."
kill "$dialog_pid" &>/dev/null
wait &>/dev/null
Run it:
> ./download https://github.com/torvalds/linux/archive/v4.4-rc2.tar.gz
Been downloading for 1 seconds...
Been downloading for 2 seconds...
<omitted>
Been downloading for 38 seconds...
Finished.
Cancelling midway:
> ./download https://github.com/torvalds/linux/archive/v4.4-rc2.tar.gz
Been downloading for 1 seconds...
Been downloading for 2 seconds...
Been downloading for 3 seconds...
User cancelled download from dialog.
The ugly thing is that killing the PID of the osascript
job doesn't dismiss the dialog box... Which I'm not in the position to solve because I absolutely dread AppleScript.