3

Conditional execution is used in Bash to control the flow of a script on the basis of the exit status of a subprocess. However, there are situations when a subprocess (application) is launched in the background, to be used for interactive purposes, so there is no exit and no exit status, until it is closed.

Consider some examples, say launching a pdf viewer, mupdf. Consider the first case with a file that exists, example.pdf:

$ mupdf example.pdf &
[1] 16220

That works, so let's try conditional execution:

$ mupdf example.pdf & && echo TRUE
bash: syntax error near unexpected token '&&'

Hmmm. Ok, let's try another (contrived) way, trying to avoid errors:

$ if mupdf example.pdf &; then echo TRUE; fi
bash: syntax error near unexpected token ';'

How about:

$ if (mupdf example.pdf &); then echo TRUE; fi
TRUE

That looks promising, But then we try the second case, with a file that does not exist:

$ mupdf nofile.pdf &
[1] 16282
$ error: cannot open nofile.pdf
error: cannot load document 'nofile.pdf'
mupdf: error: cannot open document

$ if (mupdf nofile.pdf &); then echo TRUE; fi
TRUE
$  error: cannot open nofile.pdf
error: cannot load document 'nofile.pdf'
mupdf: error: cannot open document

That construct is not working either. So the usual approach to conditional execution does not work because there is no exit status. But I noted that a pid is initially created in both successful and unsuccessful cases (mupdf does not open in the second case - launching ultimately fails).

So try another approach, wait to see what happens with the background pid, so we put together the following script:

#!/usr/bin/env bash

# usage: ./show <file>.pdf

mupdf "$1" &>/dev/null &
sleep 2
if kill -0 $! &>/dev/null
then 
  echo TRUE
else
  echo FALSE
fi

# end file

And test the first case:

$ ./show example.pdf  # case 1
[1] 16602
TRUE

That's OK. Now the second case:

$ ./show nofile.pdf   # case 2
[2] 16699
[2]+  Exit 1                  mupdf nofile.pdf &> /dev/null
FALSE

Well that works too. But this seems a very convoluted way to check the process and may fail if the launch takes more time (more than 2 seconds). So my question becomes, is there a better (cleaner) way to solve this problem?

  • 1
    are you trying to get the exit status of a command before it exits? – adonis Oct 16 '15 at 13:37
  • @adonis Trying to get the launch status before it exits, to see if it launched successfully. For example a pdf viewer that would remain open to display while the rest of the script continues, I'm trying to ensure that it launches successfully before moving on to the rest of the script. – AsymLabs Oct 16 '15 at 15:18
  • are you sure you're not trying to test if nofile.pdf exists? – mikeserv Oct 19 '15 at 7:52
  • 1
    if they're your routines then you know best, but it seems like if they are then you should integrate a little better and actually get them to report to the parent in some definitely identifiable way. in the event of an immediate error you should at least, i think, raise the signal rather than simply test for existence of the process - like { mupdf some.pdf || kill -"$(($?&127))" "$$"; } & – mikeserv Oct 19 '15 at 8:52
  • 1
    Well, POSIX states that all returns above 128 are reserved to indicate receipt of a signal. And so the & bitwise op will strip any eighth bit which might have been added to the return. Since you're raising with kill anyway, whatever you do is going to be signal - you can't help that. You really should do more testing than that - for example kill -127 0; command ""; echo "$?". My linux system only handles signal numbers 1-64, so kill -"$(($?&63))" pid...? Try kill -l for a name list, or set -x "$((x=0))"; while trap "" "$((x+=1))"; do trap; trap - "$x"; done" maybe. – mikeserv Oct 19 '15 at 9:48
1

I've located a number of similar topics to this one that consider using such things as xdotool and wmctl for the purpose of testing the launch of a persistent windowed application or other persistent sub-process. But using these seems like an unnecessary complication. The solution below would permit conditional execution of a persistent sub-process. It utilizes a sleep duration of half a second. If the sub-process of interest were to require longer to successfully launch then this duration would need to be increased accordingly.

Bash source file launch

#/usr/bin/env bash

# launch : will allow launching a windowed application or persistent
# sub-process in the background so that conditional execution can be
# applied to determine whether or not it has launched successfully -
# dependencies: GNU coreutils (sleep), util-Linux (kill)

# usage: ./launch process arg_1 .. arg_n && echo TRUE || echo FALSE

$@ &>/dev/null & sleep 0.5 && kill -0 $! 2>/dev/null

# end file

So that:

$ ./launch mupdf example.pdf && echo TRUE || echo FALSE
TRUE

$ ./launch mupdf nofile.pdf && echo TRUE || echo FALSE
FALSE

As a postcript to this information, I thought it would be useful to include the advice of mikeserv in the comments to the opening post.

Whereas the above construct accomplishes the immediate task, mikeserv points out that it could lead to other problems in that it does not propagate the signal to the calling script; it instead ignores and discards it. This is a very important observation.

Signals are a key element of interprocess communication, and can lead to the possibility of event driven scripts. So for more robust and effective scripts, mikeserv suggests the following to catch the signal so that it can be propagated:

{ mupdf some.pdf || kill -"$(($?&127))" "$$"; } &

where $?&127 permits actioning 1 to 128 possible signals (POSIX), my Debian system has 64, so the signal mask could be $?&63 instead.


So as a concluding remark, for very simple scripts, where interprocess communication is not of interest, the proposed launch can be useful, but for anything more, especially where interprocess communication is important, capture (and propagate) the signal, as advised by mikeserv.

-1

As a one-liner:

mupdf example.pdf & echo mupdf is now in the background; wait $! && echo FINISHED successfully
  • I believe that you didn’t read the same question that I did. – G-Man Feb 16 '18 at 5:06
  • You are correct. I got so focused on OP's difficulty in controlling background processes that I forgot about addressing getting its exit status. Will edit. – Bernd Jendrissek Feb 16 '18 at 23:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.