0

I have a bash script with roughly the following structure:

function download {
    # download a big file
}

function prepare_stuff {
    # prepare some stuff
}

function process_download {
    # process the downloaded file
}

download & prepare_stuff & wait
process_download

The first thing it does is to download a file of several hundred megabytes. While the download is in progress, some other things are prepared in the background. When both of these have finished, the download is processed.

download may finish in three different ways:

  1. The download failed (e.g. server not reachable)
  2. The file was downloaded successfully
  3. The file has not changed on the server since the last download

Case 1 is an error condition (in which case the function should return something different from zero), while 2 and 3 are not (i.e. the return value should be zero).

Now, I want process_download to skip the actual processing when case 1 or 3 is encountered, so I need to pass some kind of status back from download. Since download runs in a subshell, a variable will not work (assignments take place in the subshell and are not passed back to the parent shell).

How can I pass some kind of value from a function in a subshell back to the parent shell?

user149408
  • 5,385
  • 4
  • 33
  • 69
  • It all comes down to the tools used in the function, i.e. more specifically the exit-code of the last command executed in the function. – Inian Jan 27 '17 at 19:25
  • Do you already have a way to test for condition 3, or are you looking on feedback for this? – Fred Jan 27 '17 at 19:34
  • @Fred The download function can identify all three conditions. – user149408 Jan 27 '17 at 19:38
  • So you only need to check for non-zero return code from the download for both conditions that would require bypassing the final processing? – Fred Jan 27 '17 at 19:42
  • Not as I’d originally intended it (an unchanged file would have returned zero, just like a successful download, while a failed download would have returned an error code), but that’s what I ended up doing. – user149408 Jan 27 '17 at 19:53

2 Answers2

2

You can do this :

download &
download_pid=$!
prepare_stuff &
prepare_pid=$!
result=0
wait $download_pid || result=$?
wait $prepare_pid

Then, result will contain the return code of your previous download command, and both background jobs will be finished, and you can do something like :

[[ $result = 0 ]] || process_download

With clarifications regarding your third condition, I could make the answer more complete.

Fred
  • 6,590
  • 9
  • 20
  • I know, I misread the question first. I updated my answer. That being said, I am unclear as to if the OP wants a suggestion to check for condition 3 or not, so my updated proposal does not handle that at this point. – Fred Jan 27 '17 at 19:32
  • 1
    @codeforester It does. The `prepare_stuff` function is called while the download is being performed in the background. The `wait` commands, which cause the script to block, happen when both functions are running in parallel. – Fred Jan 27 '17 at 19:36
  • Can there be a situation where download process finishes even before we could wait for it? In such a case, `wait` is going to fail because of wrong pid. So, it does seem that we should run download in a subshell. I deleted my answer because `$!` wasn't set after `{ }` may be because of `&&`. – codeforester Jan 27 '17 at 19:44
  • 1
    @codeforester It will work. Processes status remains available after they terminate. Try `true & { sleep 2 ; wait $! ; }` to see it in action in a trivial case. – Fred Jan 27 '17 at 19:48
  • Just tested that: I simulated running time with `sleep` and inserted an even longer `sleep` before the first `wait`—works just as well, even if the function has finished by the time we get to `wait` for it. – user149408 Jan 27 '17 at 19:49
  • If it did not work, honestly the `wait` would be basically useless for lack of reliability. – Fred Jan 27 '17 at 19:53
0

I ended up doing something similar to what Fred suggested.

First of all, I abandoned the idea of “file not changed on server = no error” and now use the return code simply to express whether we have a download to process—0 if a new file was downloaded, 1 if the download failed or there were no changes. It’s much simpler to handle that way.

Then:

function download {
    # download a big file
    # return nonzero if download failed
    # else return nonzero if file has not changed
    # else return 0 (download successful, file has changed)
}

function prepare_stuff {
    # prepare some stuff
}

function process_download {
    # process the downloaded file
}

download &
PID_DOWNLOAD=$!
prepare_stuff &
PID_PREPARE_STUFF=$!
wait $PID_PREPARE_STUFF
wait $PID_DOWNLOAD && process_download || echo "Nothing to process."
user149408
  • 5,385
  • 4
  • 33
  • 69