0
  • I have a macbook (default shell is zsh)

  • I have an executable python script (srcript1.py).

  • I use another executable script (called starter) that runs script1.py when my computer starts, the terminal window that runs starter opens and closes automatically and the script1.py process remains running as a separate process. (starter is runed automatically through an instance of a szh shell when my computer is turned on). The original starter script file is as follows:

#!/bin/bash

cd /script1; nohup ./script1.py &

This script works okay and everything is good.

However, I'm trying to understand what happens when I use (&&) instead of (;) in my script. i.e

#!/bin/bash

cd /scritp1 && nohup ./script1.py &

My issue is that this also works but whenever I try to kill my python script's process I notice that the terminal instance of szh or something else (probably a bash process) is in running as a process. I.e if I call ps in terminal I get

ps -ef | grep "script1" 
123 14679     1   0  2:12PM ??       0:00.00 /bin/bash /script1 starter
123 14680 14679   0  2:12PM ??       0:03.46 /PythonFolder/python ./script1.py
123 14690 14683   0  2:12PM ttys000  0:00.00 grep scrip1

For the script starter that uses &&

and

ps -ef | grep "script1" 
123 14644     1   0  2:08PM ??       0:03.46 /PythonFolder/python ./script1.py
123 14652 14647   0  2:08PM ttys000  0:00.00 grep scrip1

For the starter script using ;

Why do I get two processes in one version and only one in the other? I'm trying to understand why does the szh that runs starter creates a child process running my python script when I use && and why does it terminate when my starter script uses ; and leaves the python script running as a single process.

When I run starter with the script that uses && the terminal app appears and closes but leaves two running processes (14679 and 14680 in this case).

If I use the starter script using ; and I want to kill my python.py process I would just call

kill 14644

But if I use the starter script that uses && and I want to kill both processes I noticed that killing the child process or killing the parent and then the child process work, ie

kill 14680

or

kill 14679; kill 14680

I also noted that I can kill the parent process

kill 14679

and my python script would continue running as usual.

2 Answers 2

2

The key is in how lists of commands work (quoting Bash manual here because you are using #!/bin/bash as the shebang line):

A list is a sequence of one or more pipelines separated by one of the operators ‘;’, ‘&’, ‘&&’, or ‘||’, and optionally terminated by one of ‘;’, ‘&’, or a newline.

Of these list operators, && and || have equal precedence, followed by ; and &, which have equal precedence.

(And a pipeline is a sequence of one or more commands separated by | or |&).

This means that in

cd /script1; nohup ./script1.py &

because of the precedence rules among operators, the shell sees the list cd /script1, terminated by ;, which is then run synchronously in the "main" shell, and the list nohup ./script1.py, terminated by &, which is then run asynchronously in a spawned separate process.

On the other hand, in

cd /scritp1 && nohup ./script1.py &

the shell sees the AND list cd /scritp1 && nohup ./script1.py, terminated by &, and runs the whole of it asynchronously. This requires spawning a new bash process to execute the list itself (in the background), which in turn spawns a separate process for your Python script.

About kill: killing the parent process does not kill its children processes automatically. This is standard shell behavior. In the case of your && list, killing the child process allows its parent to terminate, because all the parent process was doing was to wait for the child process to return.

If you want to conditionally execute nohup ./script1.py based on the success of cd /script1 and avoid spawning a shell for the AND list, you can enclose the second element in curly braces:

cd /scritp1 && { nohup ./script1.py & }

or use a conditional block:

if
  cd /scritp1
then
  nohup ./script1.py &
fi
7
  • I tried running sleep 2 && sleep 4 & but I only get the second command to run asynchronously in the background. Could you please explain a little bit more why this happens? I was expecting all of it to go the background
    – rafagarci
    Oct 19, 2020 at 23:50
  • My bad I was using zsh shell, what you said works perfectly in Bash, thanks you!
    – rafagarci
    Oct 20, 2020 at 0:04
  • @RafaelGarcía Yeah, that is explicitly noted in the zsh documentation: see "note the difference from other shells which execute the whole sublist in the background" near the end of the Simple Commands & Pipelines chapter.
    – fra-san
    Oct 20, 2020 at 9:14
  • I have though about this for a while and I am still a litte bit confused about this, I will edit this question shortly to reflect some interesting things that I found that make this work with simpler code. I still do not see why the original shell terminates and the resulting process is inherited by INIT. I wanted to mention though that this link helped me a lot create unatached processes. Using (... &) creates a child and a grandchild. as child exits INIT <- Grandchil
    – rafagarci
    Feb 3, 2021 at 4:16
  • What I though is a possible explanation to my problem is that when you have parent, child, and granchild processes and an upper process is only waiting for an inferior process to terminate the parent process terminates and the other are inherited by INIT. This maybe to not have to many redundant parent processes. Maybe I am forcing the child process to not terminate in my code above so at the end the child and granchild processes are both able to run. Is my logic correct?
    – rafagarci
    Feb 3, 2021 at 5:15
0

I found an easy way to create a process not dependant on the parent process

(<command here> &)

this spawns a new shell which in turn spawns another background shell running the command. Closing the main shell running the above command does not stop the process. The process running the command has 1 as its PPID (init).

This happens because the second shell is the parent process of the third one and it finishes its execution without wating for its child to finish (it's only job was to spawn the third shell). Then the child becomes an orphan and gets handled by init.

As mentioned here

A process can exit using the _exit system call, this will free up the resources that process was using for reallocation. So when a process is ready to terminate, it lets the kernel know why it's terminating with something called a termination status. Most commonly a status of 0 means that the process succeeded. However, that's not enough to completely terminate a process. The parent process has to acknowledge the termination of the child process by using the wait system call and what this does is it checks the termination status of the child process. I know it's gruesome to think about, but the wait call is a necessity, after all what parent wouldn't want to know how their child died?

Orphan Processes

When a parent process dies before a child process, the kernel knows that it's not going to get a wait call, so instead it makes these processes "orphans" and puts them under the care of init (remembermother of all processes). Init will eventually perform the wait system call for these orphans so they can die.

Zombie Processes

What happens when a child terminates and the parent process hasn't called wait yet? We still want to be able to see how a child process terminated, so even though the child process finished, the kernel turns the child process into a zombie process. The resources the child process used are still freed up for other processes, however there is still an entry in the process table for this zombie. Zombie processes also cannot be killed, since they are technically "dead", so you can't use signals to kill them. Eventually if the parent process calls the wait system call, the zombie will disappear, this is known as "reaping". If the parent doesn't perform a wait call, init will adopt the zombie and automatically perform wait and remove the zombie. It can be a bad thing to have too many zombie processes, since they take up space on the process table, if it fills up it will prevent other processes from running.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .