Disclaimer: This question came up much longer than expected. I split it in 5 sub-questions. I really tried to clarify my mind before opening it, but too many aspects are confusing me at the moment.
Trying to clarify my mind about how to correctly handle processes in Bash in a solid way, I stumbled on this Greg's Wiki article. There, rather at the beginning, there is this statement
If you're still in the parent process that started the child process you want to do something with, that's perfect. You're guaranteed the PID is your child process (dead or alive), for the reasons explained below. You can use
killto signal it, terminate it, or just check whether it's still running. You can use
waitto wait for it to end or to get its exit code if it has ended.
Towards the end of the page, the above-mentioned reasons explained below are found.
Each UNIX process also has a parent process. This parent process is the process that started it, but can change to the
initprocess if the parent process ends before the new process does. (That is,
initwill 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 to
init, 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.
Unfortunately, this guarantee doesn't apply to shell scripts. Shells aggressively reap their child processes and store the exit status in memory, where it becomes available to your script upon calling
wait. But because the child has already been reaped before you call
wait, there is no zombie to hold the PID. The kernel is free to reuse that PID, and your guarantee has been violated.
I read the above paragraphs several times by now, but I am still not sure I am grasping correctly the message behind it.
Question 1: From the second long quote, and in particular from its last paragraph, I would conclude that in shell (I am only interested to Bash) scripts I cannot be 100% sure that a PID I stored in a variable still refers to the background process I started, since it might be reused by the kernel for any other process (even not a child). Is this correct? Where in the system does the above-mentioned guarantee apply?
Question 2: It seems that the last paragraph of the second quote is in contradiction with the first quote. In general, is it true in shell scripts that "If you're still in the parent process that started the child process [...] You're guaranteed the PID is your child process (dead or alive)"?
Question 3: I tried to find other sources around in the web about this topic and, as always, it is hard to distinguish about truth and inaccurate statements. I got some confirmation but also some more doubts. Referring to this and this questions, it seems that the naive way of starting in background a process in a script, storing its PID in a variable, do some stuff and then use the PID in combination with
wait to get its exit code or with
kill to send signals might fail due to reusage by the kernel of the PID. Is there a general recipe?
Question 4: I also found this comment that suggests "to have background (process) store the return code in a file and have parent fetch it from file". Is this the reliable way to go?
Question 5: Are there caveats about the usage of
wait -n? I would think, if I do not explicitly give (potentially reused) PID to
wait, nothing wrong should happen. However, it seems that in Bash v4.4 the
-n option of
wait is useful with job control enabled,
set -m. Is it still the case in Bash v5.0?
Bonus question: This answer says something similar to Greg's Wiki.
There is only one case in which you can safely use the pid to send signals: when the target process is a direct child of the process that will be sending the signal, and the parent has not yet waited on it.
What is a direct child? Is it different from a child?