1

I have a shell script that does the following:

export FOO=foo           # step 1
/usr/bin/java my-server  # step 2

This shell script is launched by a parent program that needs to know the PID of the process started on Step 2. This is currently achieved by running exec on the server launch command to replace the shell with command in Step 2. Thus, the PID of the process that runs the script becomes the PID of the “my-server” process.

Now I have to add another command to this shell script that quickly validates the server launched in Step 2. So my program now should look like the following:

export FOO=foo                      # step 1
exec /usr/bin/java my-server &      # step 2
/usr/bin/java my-validation         # step 3

Can I add Step 3 as shown above? What are the alternatives to this approach?

  • By [launch server], do you mean [launch application]? Can you show what the actual lines in the script look like so that what you are doing is clear? – Nasir Riley Aug 17 '18 at 20:09
  • Yes, launch an application. Sure, will edit the question. – prgrmmr Aug 17 '18 at 20:10
  • Then yes, that will work as long as the environment variables are being set for the user who is launching and running the application and as long as your validate command is functioning properly. Have you tested it to make sure? – Nasir Riley Aug 17 '18 at 20:13
  • 2
    You already have a script that handles everything so you can't get much simpler than that. You've verified that it's working properly so there's no need to fix what isn't broken. – Nasir Riley Aug 17 '18 at 20:37
  • 1
    In any case, the third line won't be executed. – jlliagre Aug 17 '18 at 20:42
3

To combine what the other answer and the comments said, and then add a bit:

  • exec your_command & doesn’t make sense.  The command line will run in an asynchronous subshell, and the exec will essentially be ignored.
    • That’s a slight oversimplification.  If we say
      (command1; command2) &
      then command1 and command2 will run (command2 after command1 completes) in an asynchronous subshell.  But if we say
      (exec command1; command2) &
      then command1 will run but command2 will not.
  • Anything after a correctly formed exec command (that doesn't consist only of redirections) in a script will be ignored.  Even if the exec fails, the shell will exit without running the subsequent lines (one can use command exec ... or bash's execfail option to prevent exec from exiting the shell upon failure).
  • if the exec runs in a subshell, like in your exec cmd & or exec cmd | cmd2 or (exec cmd3), then only that subshell exits.
  • If your validation command needs to know the PID of the server process, you may be out of luck.
  • Otherwise, if your validation command automatically waits a while for the server to start, you can do

    export FOO=foo                      # step 1
    /usr/bin/java my-validation &       # step 1½
    exec /usr/bin/java my-server        # step 2
    
  • Otherwise, you can do

    export FOO=foo                              # step 1
    (sleep 10; /usr/bin/java my-validation) &   # step 1½
    exec /usr/bin/java my-server                # step 2
    

    Warning: if your “my-server” process terminates quickly, then it is quite possible that your script will exit, and the parent process will regain control and resume running, before your “my-validation” command runs.  If the parent process and the “my-validation” command both write output to the same place, they may be intermixed.

    But if the parent process isn’t waiting for the script to exit, this might not be an issue.

1

exec program replaces the process of the script with the new program, so should be used only where that makes sense. That is, the current script goes away and will not be able to run any subsequent commands (unless exec program fails).

exec program as the last line of a shell script is useful where program runs for long periods of time and you do not want a useless shell process dangling around in the process tree until program exits. Use pstree or other process tools to inspect the difference between:

#!/bin/sh
echo $$
sleep 9999

and

#!/bin/sh
echo $$
exec sleep 9999

exec program is not at all useful if you want to run other shell commands after the exec, as those shell commands are not reached (unless exec fails).

To validate something after launch is actually fairly tricky, as /usr/bin/java my-server will take time to launch (and Java can be excruciatingly slow in this regard; I recall one Java process that took upwards of 45 seconds to become available or shut itself down...) and depending on how busy the system is or the scheduler the validation process may run before or after the my-server runs. As a kluge you can delay the validation step with something like

export FOO=foo
/usr/bin/java my-server &
sleep 10
exec /usr/bin/java my-validation

which gives 10 seconds for my-server to launch itself, which may or may not be enough depending on how busy the system is and how long my-server takes to start. Again, I've seen Java processes take upwards of 45 seconds to start, and that was not on a busy system where the startup time could be even longer. The sleep may not be necessary if my-validation is intelligent enough to wait for my-server to get up and running.

It may make more sense to only start the server:

#!/bin/sh
export FOO=foo
exec /usr/bin/java my-server

and do the validation that the service is running via a monitoring framework, elsewhere.

Yet another approach would be to integrate with systemd or some other modern init framework that offers better process control than a shell script does. For example a process can communicate back to systemd whether it has started properly, or systemd can automatically restart the service under various conditions, and so forth.

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.