On 01/03/2026 08:47, Robert Elz wrote:
Date: Sun, 1 Mar 2026 06:33:40 -0800
From: Ben Ashton <[email protected]>
Message-ID: <[email protected]>
| It's unrelated, but I'm curious what systems don't make the PGID the
| same as the PID of the leader? I thought that was a POSIX thing.
No. It is related to how pipelines are forked in the shell, To make
a new process group:
| The FreeBSD syscall manual for "setsid" states: "the setsid() system call
| returns the value of the process group ID of the new process group,
| which is the same� as�the� process� ID of the calling process."
that is absolutely correct. So it all depends how the processes
of a pipeline are forked (for anything other than a pipeline it makes
no difference, the shell forks once, that becomes the process group leader,
and its pid is $!).
It used to be the case once that the shell would fork once for pipelines
as well, and then that process would be both $! and the process group leader,
and because of posix rules, it would also become the rightmost process of
the pipeline, forking children for the other processes for the rest of the
pipe. But while not impossible, it is difficult, and defeats some
optimisations, to implement things that way on a system supporting the
pipefail option, which is required by POSIX now.
So, instead the parent shell (the one which is going to set $!) forks all
of the children, and adds each of them to the new process group. To do
that it needs to know the process group ID as each child is created (there's
no sane way to send the info to a process later, only by the memory copy from
the fork()). So the shell forks once, that child creates a new process
group, the parent knows what that is, as it is the pid of the child it just
created (which fork()) returns. Then it forks processes for the rest of
the pipeline, those processes know the pgrp id, and join that pgrp
immediately after starting after the fork().
Now it all depends which order the shell creates the pipeline, for which
there are 2 rational choices, left to right, or right to left; which is
simpler depends upon the data structure the shell's parser created when
it parsed the pipeline. If the pipeline is created right to left, then
the pgrp id is the rightmost process (the way it used to be in times past),
and that also becomes $!. If the pipeline is created left to right, then
the pgrp is is the pid of the leftmost process, but $! still needs to be the
pid of the rightmost process, hence $! and the pgrp ID are different.
kre
ps: the NetBSD shell has a builtin command which converts between pids and
pgrp ids of children of the shell, so scripts can easily do whatever it is
they really want to achieve, and the shell doesn't need to attempt to guess
what the script is intending (-$! is certainly not guaranteed to be the
pgrp ID of anything, and if it is, it isn't necessarily what you want, so
when a shell sees that, it has to attempt to intuit what was really meant).
Sorry, no markup survived my cut&paste from an xterm into my MUA in the
following:
jobid [-g|-j|-p] [job]
With no flags, print the process identifiers of the processes in
the job. If the job argument is omitted, the current job is used.
Any of the ways to select a job may be used for job, including the
'%' forms, or the process id of the job leader ('$!' if the job
was created in the background.)
If one of the flags is given, then instead of the list of process
identifiers, the jobid command prints:
-g the process group, if one was created for this job, or
nothing otherwise (the job is in the same process group as
the shell.)
-j the job identifier (using '%n' notation, where n is a
number) is printed.
-p only the process id of the process group leader is printed.
These flags are mutually exclusive.
jobid exits with status 2 if there is an argument error, status 1,
if with -g the job had no separate process group, or with -p there
is no process group leader (should not happen), and otherwise
exits with status 0.
So, "jobid -g $!" produces the process group ID from the $! value.
Just for completeness, jobs without process group ids of their own
are ones created when job control is disabled - which is the default
for scripts.
Thank you for the thorough explanation. Given the issue you describe
with pipelines, I can see why there are scenarios where the shell might
want to translate $! so that you can kill the process group of a
backgrounded job irrespective of the order in which the pipeline is
composed.
The issue is that within the subshell, backgrounded commands don't
automatically get their own process groups (despite job control being
enabled outside of the subshell), however that translation still occurs.
Inside the subshell running "sleep 5 &" will just create a child process
that belongs to the process group of the subshell. So that translation
will result in me killing the process group of the subshell. Surely we
only want that translation to occur when the shell created a new process
group for the command?
Thanks to your explanation I was able to simplify the reproduction quite
a lot. Here is what happens with bash and various other shells:
ben@work-laptop:~$ dash -c 'set -m; (setsid sleep 5 & pid=$!; sleep 2;
kill -TERM -$pid; echo here)'
here
ben@work-laptop:~$ ash -c 'set -m; (setsid sleep 5 & pid=$!; sleep 2;
kill -TERM -$pid; echo here)'
here
ben@work-laptop:~$ zsh -c 'set -m; (setsid sleep 5 & pid=$!; sleep 2;
kill -TERM -$pid; echo here)'
here
ben@work-laptop:~$ bash -c 'set -m; (setsid sleep 5 & pid=$!; sleep 2;
kill -TERM -$pid; echo here)'
Terminated
As you can see, bash is the only one that translates $! to the PGID of
the subshell. I confirmed with strace that the other shells ARE killing
the correct process group.
The reason I previously stated that it doesn't matter how I obtain the
PGID, is because it's not like the shell is performing any complex logic
to figure out that the number was derived from $!. I could have used
some external tool, or read /proc/PID/stat to determine the process
group that I wanted to kill. The problem is that the PID effectively
becomes a magic number that will be translated without my knowledge.