35

I am trying to get the pid of a currently executing subshell - but $$ is only returning the parent pid:

#!/usr/bin/sh

x() {
  echo "I am a subshell x echo 1 and my pid is $$"
}

y() {
  echo "I am a subshell y echo 1 and my pid is $$"
}


echo "I am the parent shell and my pid is $$"
x &
echo "Just launched x and the pid is $! "

y &
echo "Just launched y and the pid is $! "

wait

Output

I am the parent shell and my pid is 3107
Just launched x and the pid is 3108
I am a subshell x echo 1 and my pid is 3107
Just launched y and the pid is 3109
I am a subshell y echo 1 and my pid is 3107

As you can see above, when I run $$ from the function that I've backgrounded, it does not display the PID as when I do $! from the parent shell.

fduff
  • 3,671
  • 2
  • 30
  • 39
awojo
  • 907
  • 3
  • 11
  • 21
  • Possible duplicate to http://stackoverflow.com/questions/7858191/difference-between-bash-pid-and – Robin Hsu Nov 17 '14 at 08:00
  • possible duplicate of [$$ in a script vs $$ in a subshell](http://stackoverflow.com/questions/5615570/in-a-script-vs-in-a-subshell) – bain Apr 25 '15 at 13:08

6 Answers6

48

Modern bash

If you are running bash v4 or better, the PID of the subshell is available in $BASHPID. For example:

$ echo $$ $BASHPID ; ( echo $$ $BASHPID  )
32326 32326
32326 1519

In the main shell, $BASHPID is the same as $$. In the subshell, it is updated to the subshell's PID.

Old bash (Version 3.x or Earlier)

Pre version 4, you need a workaround:

$ echo $$; ( : ; bash -c 'echo $PPID' )
11364
30279

(Hat tip: kubanczyk)

Why the colon?

Notice that, without the colon, the work-around does not work:

$ echo $$; ( bash -c 'echo $PPID' )
11364
11364

It appears that, in the above, a subshell is never created and hence the second statement returns the main shell's PID. By contrast, if we put two statements inside the parens, the subshell is created and the output is as we expect. This is true even if the other statement is a mere colon, :. In shell, the : is a no-operation: it does nothing. It does, in our case however, force the creation of the subshell which is enough to accomplish what we want.

Dash

On debian-like systems, dash is the default shell (/bin/sh). The PPID approach works for dash but with yet another twist:

$ echo $$; (  dash -c 'echo $PPID' ) 
5791
5791
$ echo $$; ( : ; dash -c 'echo $PPID' )
5791
5791
$ echo $$; (  dash -c 'echo $PPID'; : )   
5791
20961

With dash, placing the : command before the command is not sufficient but placing it after is.

POSIX

PPID is included in the POSIX specification.

Portability

mklement0 reports that the following works as is with bash, dash, and zsh but not ksh:

echo $$; (sh -c 'echo $PPID' && :)
Community
  • 1
  • 1
John1024
  • 109,961
  • 14
  • 137
  • 171
  • 2
    How complicated can be `bash -c 'echo $PPID'` – kubanczyk Mar 04 '16 at 17:44
  • @kubanczyk Thanks for the suggestion. You are right that that is a good solution. I added it to the answer, along with an explanation of why `echo $$; ( bash -c 'echo $PPID' )` does not work as we might want it to. – John1024 Mar 06 '16 at 17:46
  • 1
    Nicely done; turns out that placing the `:` _after_ the `dash -c` is sufficient (no need to call external utility `ls`). Given that `$$PID` is a POSIX-mandated variable, the following command therefore works _as-is_ with `dash`, `bash`, and `zsh` (though, curiously, _not_ with `ksh`): `echo $$; (sh -c 'echo $PPID' && :)` – mklement0 Mar 07 '16 at 00:09
  • @mklement0 Interesting! Answer updated. While I can accept ksh's peculiar interpretation of `PPID`, I find dash's behavior truly puzzling. – John1024 Mar 07 '16 at 00:26
  • Thanks; re `dash`: I am puzzled myself. Note that it's _not_ `ksh` interpreting `$PPID`, however: it's `sh` (which typically isn't `ksh`); without having delved deeper, it seems that `ksh` avoids subshells in many scenarios where other shells do use them. – mklement0 Mar 07 '16 at 00:30
  • It seems to me that the behavior of dash is fairly straightforward, and I’m surprised that somebody (with the initials “SC”) hasn’t stepped in and explained it (maybe he believes he’s explained it enough times in other threads).  The thing is that `dash` treats ``(cmd₁; cmd₂; cmd₃; cmd₄)`` as ``(cmd₁; cmd₂; cmd₃; exec cmd₄)``, so as to avoid one fork — you can test / demonstrate this with `(ps; ps; ps; ps)`— so the *last* command inside `(`…`)` is not run in a subshell.  For uniformity, you might want to put the **`:`** *after* the `bash -c` too. – G-Man Says 'Reinstate Monica' Jun 19 '19 at 21:10
  • Please see my answer below, use `$(exec sh -c 'echo $PPID')`, other variants not work in modern shells (in the middle of 2019). – Kirill Frolov Jul 07 '19 at 10:39
  • **Warning!** `pstree -p | grep --color=always $BASHPID` highlites the `grep`, not the executing `bash`, as it is the same as `pstree -p | ( exec grep --color=always $BASHPID )`. This result is very logic, but may come a bit unexpected. Lesson learned: Always unambiguously save `$BASHPID` in something like a variable first, example: `x=$BASHPID; pstree -p | grep --color=always $x` vs. `pstree -p | ( x=$BASHPID; exec grep --color=always $x )` (this makes it clear what is meant) – Tino Oct 28 '21 at 12:06
6

The output is correct.

Here's from the man page of bash.

Special Parameters

The shell treats several parameters specially. These parameters may only be referenced; assignment to them is not allowed.

* Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, it expands to a single word with the value of each parameter separated by the first character of the IFS special variable. That is, "$*" is equivalent to "$1c$2c...", where c is the first character of the value of the IFS variable. If IFS is unset, the parameters are separated by spaces. If IFS is null, the parameters are joined without intervening separators.
@ Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, each parameter expands to a separate word. That is, "$@" is equivalent to "$1" "$2" ... If the double-quoted expansion occurs within a word, the expansion of the first parameter is joined with the beginning part of the original word, and the expansion of the last parameter is joined with the last part of the original word. When there are no positional parameters, "$@" and $@ expand to nothing (i.e., they are removed).
# Expands to the number of positional parameters in decimal.
? Expands to the exit status of the most recently executed foreground pipeline.
- Expands to the current option flags as specified upon invocation, by the set builtin command, or those set by the shell itself (such as the -i option).
$ Expands to the process ID of the shell. In a () subshell, it expands to the process ID of the current shell, not the subshell.
! Expands to the process ID of the most recently executed background (asynchronous) command.

To get the PID inside of a subshell, you may use BASHPID. This is a bash only env variable.

Your new script will look like this.

#!/bin/bash

x() {
  echo "I am a subshell x echo 1 and my pid is $BASHPID"
}

y() {
  eval echo "I am a subshell y echo 1 and my pid is $BASHPID"
}


echo "I am the parent shell and my pid is $$"
x &
echo "Just launched x and the pid is $! "

y &
echo "Just launched y and the pid is $! "

wait
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
alvits
  • 6,550
  • 1
  • 28
  • 28
  • I do not doubt that it's correct - my current question stands is there a way to get the PID of a subshell from inside that subshell? – awojo Dec 22 '13 at 03:32
  • I updated my reply. You can use BASHPID to get the PID of the subshell in a bash script. – alvits Dec 22 '13 at 03:55
1

If you use Linux-kernel, you can use Linux-kernel's /proc/self feature to do this:

In simplest form: cd -P /proc/self && basename "${PWD}"

To keep the PWD and OLDPWD variable: PWD_BACKUP="${PWD}";OLDPWD_BACKUP="${OLDPWD}";cd -P /proc/self && basename "${PWD}";cd "${PWD_BACKUP}";OLDPWD="${OLDPWD_BACKUP}"

For example:

$ cd -P /proc/self && basename "${PWD}"
26758
$ (cd -P /proc/self && basename "${PWD}")
26959
$ (cd -P /proc/self && basename "${PWD}")
26961
$ 
illiterate
  • 289
  • 2
  • 12
1

It's better to use $(exec sh -c 'echo $PPID') rather than tricks shown in answer by John1024 (those tricks not work witn modern shells at present time, when I writing my reply).

See this question: https://unix.stackexchange.com/questions/484442/how-can-i-get-the-pid-of-a-subshell

Kirill Frolov
  • 401
  • 4
  • 10
  • This looks to be basically the same method as in my answer and, in my tests, it gives the same results. With what "modern shells" (name and version) do you get different results? – John1024 Jul 08 '19 at 05:42
0

Environment:

SUSE Linux Enterprise Server 10 SP2 (i586)

GNU bash, version 3.1.17(1)-release (i586-suse-linux) Copyright (C) 2005 Free Software Foundation, Inc.

#!/usr/bin/sh

x() {
  mypid=$(awk 'BEGIN {print PROCINFO["ppid"] ; exit}')
  echo "I am a subshell x echo 1 and my pid is $mypid"
}

y() {
  mypid=$(awk 'BEGIN {print PROCINFO["ppid"] ; exit}')
  echo "I am a subshell y echo 1 and my pid is $mypid"
}


echo "I am the parent shell and my pid is $$"
x &
echo "Just launched x and the pid is $! "

y &
echo "Just launched y and the pid is $! "

wait

Result:

I am the parent shell and my pid is 27645
Just launched x and the pid is 27646
Just launched y and the pid is 27647
I am a subshell y echo 1 and my pid is 27647
I am a subshell x echo 1 and my pid is 27646
jramos
  • 128
  • 1
  • 6
0

@John1024 's answer is great, but there's a little problem in there.

when a command run like (...), the command will run in a new subprocess, so

( : ; bash -c 'echo $PPID' ) 

will return process_id of (...), not the function's process id that call (...)

if you want the function's process_id, you can just run:

$SHELL -c 'echo $PPID'

and the process_id will be outputed to stderr

if you want get the function's process_id form a variable, you can run:

$SHELL -c 'echo $PPID' | read -s func_pid

then you can get the pid from variable ${func_pid}

note: don't run this command in (...), otherwise it'll return the process_id of (...)

Panda
  • 101
  • 1
  • 10
  • 2
    (1) I don’t understand the point you intend to make.  The question asks how to find the PID of the *current **subshell**.*  I guess that that is what you mean by “the process_id of **(...)**” — so, it appears that you are complaining that John1024’s answer does what the question asks for.  (2) BTW, the whole premise of the question is that `echo $$` will tell you the PID of your current *main shell process;* so I don’t see any need or use for ```$SHELL -c 'echo $PPID'``` outside of parentheses. … (Cont’d) – G-Man Says 'Reinstate Monica' Jun 19 '19 at 21:11
  • 1
    (Cont’d) …  (3) Your final offering, `$SHELL -c 'echo $PPID' | read -s func_pid`; doesn’t work (or at least not in bash; `cmd | read var` generally doesn’t work). – G-Man Says 'Reinstate Monica' Jun 19 '19 at 21:11
  • Downvoted, all in this answer seems to be quite too odd. First, we want the PID of the subshell, as it is exactly is the problem here to get the PID of the shell running inside `(...)` and not the PID of the shell running outside of `(...)`. Also `mypid=wrong; $SHELL -c 'echo $PPID' | read -s mypid; echo $mypid` outputs `wrong` as pipes run in a subshell (at least in most shells like bash). Use `mypid=$(exec sh -c 'echo $PPID')` instead like in: `( mypid=$(exec sh -c 'echo $PPID'); pstree -p | grep --color=always $mypid )`. This also is very portable if you use backticks instead of `$(..)` – Tino Oct 28 '21 at 12:30