5

For example:

$ cat foo.sh
#!/usr/bin/env bash
while true; do sleep 1 ; done
$ ./foo.sh &
$ pgrep foo.sh
$

Contrast with:

$ cat bar.sh
#!/bin/bash
while true; do sleep 1 ; done
$ ./bar.sh &
$ pgrep bar.sh
21202

The process started by env bash shows up in the output of ps aux as:

terdon 4203  0.0  0.0  26676  6340 pts/3    S    17:23   0:00 /bin/bash

while the one started with /bin/bash shows as

terdon  9374  0.0  0.0  12828  1392 pts/3    S    17:27   0:00 /bin/bash ./bar.sh

which probably explains why it the first is not being caught by pgrep. So, questions are:

  • Why does the name of the script not show up when called through env?
  • Does pgrep simply parse the output of ps?
  • Is there any way around this so that pgrep can show me scripts started via env?
terdon
  • 242,166

2 Answers2

5

Q#1

Why does the name of the script not show up when called through env?

From the shebang wikipedia article:

Under Unix-like operating systems, when a script with a shebang is run as a program, the program loader parses the rest of the script's initial line as an interpreter directive; the specified interpreter program is run instead, passing to it as an argument the path that was initially used when attempting to run the script.

So this means that the name of the script is what's known by the kernel as the name of the process, but then immediately after it's invoked, the loader then execs the argument to #! and passes the rest of the script in as an argument.

However env doesn't do this. When it's invoked, the Kernel knows the name of the script and then executes env. env then searches the $PATH looking for the executable to exec.

It is then env that executes the interpreter. It knows nothing of the original name of the script, only the Kernel knows this. At this point env is parsing the rest of the file and passing it to interpreter that it just invoked.

Q#2

Does pgrep simply parse the output of ps?

Yes, kind of. It's calling the same C libraries that ps is making use of. It's not simply a wrapper around ps.

Q#3

Is there any way around this so that pgrep can show me scripts started via env?

I can see the name of the executable in the ps output.

$ ps -eaf|grep 32405
saml     32405 24272  0 13:11 pts/27   00:00:00 bash ./foo.sh
saml     32440 32405  0 13:11 pts/27   00:00:00 sleep 1

In which case you can use pgrep -f <name> to find the executable, since it will search the entire command line argument, not just the executable.

$ pgrep -f foo
32405

References

slm
  • 369,824
  • Apparently the #/usr/bin/env kind of works too. I didn't get an error message any way when I ran it. – slm Aug 06 '13 at 17:56
  • Yes, I just found out. I had actually not used the ! and much to my surprise it worked. – terdon Aug 06 '13 at 18:14
3

pgrep doesn't parse ouput of ps, it will look through /proc/<PID>/status and /proc/<PID>/cmdline to finding matching for given pattern. Do some strace:

getpid()                                = 6572
stat("/proc/self/task", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
openat(AT_FDCWD, "/proc", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
getdents(3, /* 267 entries */, 32768)   = 6792
stat("/proc/1", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
open("/proc/1/status", O_RDONLY)        = 4
read(4, "Name:\tinit\nState:\tS (sleeping)\nT"..., 1023) = 750
close(4)                                = 0
open("/proc/1/cmdline", O_RDONLY)       = 4
read(4, "/sbin/init", 2047)             = 10
close(4)                                = 0
stat("/proc/2", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
open("/proc/2/status", O_RDONLY)        = 4
read(4, "Name:\tkthreadd\nState:\tS (sleepin"..., 1023) = 518
close(4)                                = 0
open("/proc/2/cmdline", O_RDONLY)       = 4
read(4, "", 2047)                       = 0
close(4)                                = 0
stat("/proc/3", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
open("/proc/3/status", O_RDONLY)        = 4
read(4, "Name:\tksoftirqd/0\nState:\tS (slee"..., 1023) = 521
close(4)                                = 0
open("/proc/3/cmdline", O_RDONLY)       = 4
read(4, "", 2047)                       = 0
close(4)                                = 0
stat("/proc/6", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
open("/proc/6/status", O_RDONLY)        = 4
read(4, "Name:\tmigration/0\nState:\tS (slee"..., 1023) = 519
close(4)                                = 0
open("/proc/6/cmdline", O_RDONLY)       = 4
read(4, "", 2047)                       = 0
close(4)                                = 0

By default, pgrep only matches the process name (which is bash in your case). From man pgrep:

 -f     The pattern is normally only matched against the process name.
        When -f is set, the full command line is used.

So in your case, if you want to show script started via env, try using:

pgrep -f foo.sh
cuonglm
  • 153,898