starting a new process group from bash script

17.5k views Asked by At

I basically want to run a script (which calls more scripts) in a new process group so that I can send signal to all the processes called by the script.

In Linux, I found out setsid helps me in doing that, but this is not available on FreeBSD.

Syntax for setsid (provided by util-linux-ng).

setsid /path/to/myscript

I, however learnt that session and process group are not the same. But starting a new session also solves my problem.

4

There are 4 answers

3
Filipe Gonçalves On

Sessions and groups are not the same thing. Let's make things clean:

A session consists of one or more process groups, and can have a controlling terminal. When the session has a controlling terminal, the session has, at any moment, exactly one foreground process group and one or more background process groups. In such a scenario, all terminal-generated signals and input is seen by every process in the foreground process group.

Also, when a session has a controlling terminal, the shell process is usually the session leader, dictating which process group is the foreground process group (implicitly making the other groups background process groups). Processes in a group are usually put there by a linear pipeline. For example, ls -l | grep a | sort will typically create a new process group where ls, grep and sort live.

Shells that support job control (which also requires support by the kernel and the terminal driver), as in the case of bash, create a new process group for each command invoked -- and if you invoke it to run in the background (with the & notation), that process group is not given the control of the terminal, and the shell makes it a background process group (and the foreground process group remains the shell).

So, as you can see, you almost certainly don't want to create a session in this case. A typical situation where you'd want to create a session is if you were daemonizing a process, but other than that, there is usually not much use in creating a new session.

You can run the script as a background job, as I mentioned, this will create a new process group. Since fork() inherits the process group ID, every process executed by the script will be in the same group. For example, consider this simple script:

#!/bin/bash

ps -o pid,ppid,pgid,comm | grep ".*"

This prints something like:

  PID  PPID  PGID COMMAND
11888 11885 11888 bash
12343 11888 12343 execute.sh
12344 12343 12343 ps
12345 12343 12343 grep

As you can see, execute.sh, ps and grep are all on the same process group (the value in PGID).

So all you want is:

/path/to/myscript &

Then you can check the process group ID of myscript with ps -o pid,ppid,pgid,comm | grep myscript. To send a signal to the group, use kill with the negative of the process group ID (PGID is the PID of the leader of the group). A signal sent to a group is delivered to every process in that group. In the above example, to send SIGTERM to every process started by execute.sh, including the script itself, you would use kill -- -12343. (Note that sending a signal to the whole group is different from sending a signal to just the group leader: kill 12343 and kill -- -12343 are different!)

0
DenisKolodin On

This is not exactly answer, but is an alternative approach based on names.

You can have a common part of name for all process. For example we have my_proc_group_29387172 part for all the following processes:

-rwxrwxr-x.   my_proc_group_29387172_microservice_1
-rwxrwxr-x.   my_proc_group_29387172_microservice_2
-rwxrwxr-x.   my_proc_group_29387172_data_dumper

Spawn all of them (and as much as you want):

ADDR=1 ./my_proc_group_29387172_microservice_1
ADDR=2 ./my_proc_group_29387172_microservice_1
ADDR=3 ./my_proc_group_29387172_microservice_2
./my_proc_group_29387172_data_dumper

When you want to kill all processes you can use pkill command (pattern kill) or killall with --regexp parameter:

pkill my_proc_group_29387172

Benefit :) - you can start as many process as you want at any time (or any day) from any script.

Drawback :( - you can kill innocent processes if they has common part of name with your pattern.

0
taroj On

Using FreeBSD you may try using the script command that will internally execute the setsid command.

stty -echo -onlcr   # avoid added \r in output
script -q /dev/null /path/to/myscript
stty echo onlcr
# sync  # ... if terminal prompt does not return
0
Alek On

Use job control

You can temporarily enable job control in bash with set -m. When it is enabled, every child process is created in a separate process group. But job control also enables some more features:

  • reporting when a jobs gets terminated
  • activation of Ctrl+Z, Ctrl+Y shortcuts

And probably something else, which might not be desired in a script. Therefore you can disable job control right after the child process is forked:

#!/bin/bash

set -m
/path/to/myscript &
set +m

This works with the compound commands as well, for example:

#!/bin/bash

set -m
{
    while true; do
        /bin/some_server
    done
} &
set +m
server_pgid=$!

...

kill -- -$server_pgid

In this example /bin/some_server process will be killed as well as the wrapper process. Note that the process group killing method is used here (PGID is prefixed with -).

You can read more about the job control in the bash manual page.

Determining PGID of the specific PID

To determine the process group id (PGID) which the process belongs to, you can use this command:

ps j $PID

which prints detailed info about the specified PID, including PGID. In scripts you can use command ps ho pgid:1 $PID which only prints PGID value, for example like this:

PGID=`ps ho pgid:1 $PID`

here h disables headers in ps output, o pgid sets output of PGID field only and :1 specifies the width of the column, without it small PGIDs will be prefixed with spaces