109

I want to start process (eg. myCommand) and get its pid (to allow to kill it later).

I tried ps and filter by name, but I can not distinguish process by names

myCommand
ps ux | awk '/<myCommand>/ {print $2}' 

Because processes names are not unique.

I can run process by:

myCommand &

I found that I can get this PID by:

echo $!

Is there any simpler solution?

I would be happy to execute myCommand and get its PID as a result of one line command.

rafalmag
  • 1,201
  • 2
  • 9
  • 8

8 Answers8

119

What can be simpler than echo $!? As one line:

myCommand & echo $!
Jacek Konieczny
  • 3,597
  • 2
  • 21
  • 22
  • 2
    Thank you merging these commands with "&" helped me a lot. – rafalmag Nov 27 '10 at 12:09
  • 10
    in a bash script, in a loop which starts programs, $! is not accurate. Sometimes it returns pid of the script itself, sometimes of grep or awk run from the script any solution to specifically get the pid of the process just launched in this scenario? something like pid=`myprogram` would have been awesome –  Apr 26 '14 at 05:59
  • 4
    NB that this requires you to start the command using a `&` as the previous line, otherwise the echo basically returns blank. – rogerdpack Feb 04 '15 at 18:56
  • 3
    assigning to variable like `command & echo $!` freezes the execution at this step :( – Shashank Vivek Jul 15 '16 at 09:41
  • This doesn't work if you don't explicitly background the process! For instance, your process may background itself after it has done something you need to wait for before continuing. – Michael Apr 16 '20 at 00:10
  • In ruby, $$ prints the PID. So the ruby pid should match what's printed by echo `ruby -e "p($$) && sleep(3)" & echo $!`, but Ruby pid is completely different from what `echo $!` says. But the answer below, which translates to `sh -c 'echo $$ ; exec ruby -e "p($$) && sleep(3)"'` works! – S.Goswami Sep 13 '20 at 06:35
67

You can use sh -c and exec to get the command's PID even before it runs.

To start myCommand, so that its PID is printed before it begins to run, you can use:

sh -c 'echo $$; exec myCommand'

How it works:

This starts a new shell, prints the PID of that shell, and then uses the exec builtin to replace the shell with your command, ensuring it has the same PID. When your shell runs a command with the exec builtin, your shell is actually becoming that command, rather than the more common behavior of forking a new copy of itself, which has its own separate PID and which then becomes the command.

I find this to be much simpler than alternatives involving asynchronous execution (with &), job control, or searching with ps. Those approaches are fine, but unless you have a specific reason to use them--for example, perhaps the command is already running, in which case searching for its PID or using job control would make sense--I suggest considering this way first. (And I would certainly not consider writing a complex script or other program to achieve this).

This answer includes an example of this technique.


Parts of that command could occasionally be omitted, but not usually.

Even if the shell you're using is a Bourne-style and thus supports the exec builtin with these semantics, you generally shouldn't try to avoid using sh -c (or equivalent) to create a new, separate shell process for this purpose, because:

  • Once the shell has become myCommand, there's no shell waiting to run subsequent commands. sh -c 'echo $$; exec myCommand; foo would not be able to attempt to run foo after replacing itself with myCommand. Unless you're writing a script that runs this as its last command, you can't just use echo $$; exec myCommand in a shell where you are running other commands.
  • You cannot use a subshell for this. (echo $$; exec myCommand) may be syntactically nicer than sh -c 'echo $$; exec myCommand', but when you run $$ inside ( ), it gives the PID of the parent shell, not of the subshell itself. But it is the subshell's PID that will be the PID of the new command. Some shells provide their own non-portable mechanisms for finding the subshell's PID, which you could use for this. In particular, in Bash 4, (echo $BASHPID; exec myCommand) does work.

Finally, note that some shells will perform an optimization where they run a command as if by exec (i.e., they forgo forking first) when it is known that the shell will not need to do anything afterward. Some shells try to do this anytime it is the last command to be run, while others will only do it when there are no other commands before or after the command, and others will not do it at all. The effect is that if your forget to write exec and just use sh -c 'echo $$; myCommand' then it will sometimes give you the right PID on some systems with some shells. I recommend against ever relying on such behavior, and instead always including exec when that's what you need.

Eliah Kagan
  • 775
  • 5
  • 13
  • Before I can run `myCommand`, I need to set a number of environment variables in my bash script. Will these carry over to the environment in which the `exec` command is running? – user5359531 May 11 '18 at 15:41
  • Looks like my environment does carry over into the `exec` command. However, this approach does not work when `myCommand` starts other processes, which are the ones you need to work with; when I issue a `kill -INT ` where `pid` was obtained this way, the signal does not reach the sub-processes started by `myCommand`, whereas if I run `myCommand` in the current session and Ctrl+C, the signals propagate correctly. – user5359531 May 11 '18 at 16:43
  • 1
    I tried this, but the pid of the myCommand process seems to be the pid output by echo $$ +1. Am I doing something wrong? – crobar Aug 28 '18 at 10:13
  • My command looks like this: `sh -c 'echo $$; exec /usr/local/bin/mbdyn -f "input.file" -o "/path/to/outputdir" > "command_output.txt" 2>&1 &'` – crobar Aug 28 '18 at 10:51
  • 3
    This is brilliant, but it's not working for me when I try to get the echoed value into a variable so I can actually use it later to kill the process, e.g. `PID=$(sh -c 'echo $$; exec myCommand')` just hangs, whereas if I remove the `PID=$(...)` wrapper it displays the PID and continues immediately! – Michael Apr 16 '20 at 00:22
34

Wrap the command in a small script

#!/bin/bash
yourcommand &
echo $! >/path/to/pid.file
user9517
  • 114,104
  • 20
  • 206
  • 289
7

I do not know of any simpler solution, but isn't using $! good enough? You can always assign the value to some other variable if you need it later, as said by others.

As a side note, instead of piping from ps you could use pgrep or pidof.

carlpett
  • 896
  • 8
  • 17
  • 28
6

use exec from a bash script after registering the pid to a file:

example:

suppose you have a script named "forever.sh" that you want to run with args p1,p2,p3

forever.sh sourcecode:

#!/bin/sh

while [ 1 -lt 2 ] ; do
    logger "$0 running with parameters \"$@\""
    sleep 5
done

create a reaper.sh:

#!/bin/sh

echo $$ > /var/run/$1.pid
exec "$@"

run forever.sh through reaper.sh:

./reaper.sh ./forever.sh p1 p2 p3 p4 &

forever.sh does nothing more than logging a line to syslog each 5 seconds

you now have the pid in /var/run/forever.sh.pid

cat /var/run/forever.sh.pid 
5780

and forever.sh is running aok. syslog grep:

Nov 24 16:07:17 pinkpony cia: ./forever.sh running with parameters "p1 p2 p3 p4"

you can see it in the process table:

ps axuwww|grep 'forever.sh p1' |grep -v grep
root      5780  0.0  0.0   4148   624 pts/7    S    16:07   0:00 /bin/sh ./forever.sh p1 p2 p3 p4
user237419
  • 1,663
  • 8
  • 8
5

In the bash shell an alternative to $! might be the jobs -p built-in. In some cases the ! in $! gets interpreted by the shell before (or instead of) the variable expansion, leading to unexpected results.

This, for example, won't work:

((yourcommand) & echo $! >/var/run/pidfile)

while this will:

((yourcommand) & jobs -p >/var/run/pidfile)
mustaccio
  • 252
  • 3
  • 10
1

You can use something like:

$ myCommand ; pid=$!

Or

$ myCommand && pid=$!

The two commands can be joints using ; or &&. In the second case, the pid will be set only if the first command succeeds. You can get the process id from $pid.

Khaled
  • 35,688
  • 8
  • 69
  • 98
  • 3
    OP wants to get the PID so he can kill it later. ; and && require the original process to exit before the echo $! is executed. – user9517 Nov 24 '10 at 08:58
  • Yes, you are right. This will give you the pid after myCommand has terminated. – Khaled Nov 24 '10 at 09:02
  • 8
    Referencing `$!` after `&&` or `;` will never give you the PID of the process started for the left-hand side of the command separator. `$!` is only set for processes launched asynchronously (e.g. usually with `&` but some shells also have other methods). – Chris Johnsen Nov 24 '10 at 09:47
  • it is helpful, and why no one vote up excep me? good job though :) – temple Sep 28 '16 at 21:14
1

This is a bit of a hack-y answer and won't likely work for most people. It's also a huge security risk methinks, so don't do it unless you're sure you'll be safe and the inputs are sanitized and...well, you get the idea.

Compile the little C program here into a binary called start (or whatever you want), then run your program as ./start your-program-here arg0 arg1 arg2 ...

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    if (argc >= 2)
    {
        printf("%lu\n", (long unsigned) getpid());
        if (execvp(argv[1], &argv[1]) < 0)
        {
            perror(NULL);
            return 127;
        }
    }
    return 0;
}

Long story short, this will print the PID to stdout, then load your program into the process. It should still have the same PID.

tonysdg
  • 131
  • 6
  • How can I record the PID number returned by this code in a bash variable? For a reason unclear to me, `stdout` does not seem to be recorded in this example: `RESULT="$(./start_and_get_pid.out echo yo)"; echo "$RESULT"` – Tfb9 Nov 08 '18 at 01:27
  • @Tfb9: I'd honestly recommend one of the other approaches; I wrote this 2+ years ago and it's by far one of the hackier/error-prone methods presented (or so I think). – tonysdg Nov 08 '18 at 01:30
  • Thanks for the note. I think I solved my problem with this `sleep 10 & PID_IS=$!; echo $PID_IS` – Tfb9 Nov 09 '18 at 00:33