4

I've created a bash script which counts launched instances of itself.

Here it is (in this example, I'm showing the instances rather than counting them with wc -l) :

#!/bin/bash
nb=`ps -aux | grep count_itself.sh`
echo "$nb"
sleep 20

(Of course, my script is named count_itself.sh)

Upon executing it, I expect it to return two lines, but it returns three :

root@myserver:/# ./count_itself.sh
root    16225   0.0 0.0 12400   1176 pts/0  S+  11:46   0:00    /bin/bash ./count_itself.sh
root    16226   0.0 0.0 12408   564 pts/0   S+  11:46   0:00    /bin/bash ./count_itself.sh
root    16228   0.0 0.0 11740   932 pts/0   S+  11:46   0:00    grep count_itself.sh

Upon executing it with the & flag (i.e. in background, and manually executing the ps -aux bit, it returns two, which is what I want :

root@myserver:/# ./count_itself.sh &
[1] 16233
root@myserver:/# ps -aux | grep count_itself.sh
root     16233  0.0  0.0  12408  1380 pts/0    S    11:48   0:00 /bin/bash ./count_itself.sh
root     16240  0.0  0.0  11740   944 pts/0    S+   11:48   0:00 grep --color=auto count_itself.sh

My question is : Why does the ps -aux execution inside the script return one line more than expected ?

Or, in another words, why is the process with the id 16226 created in my first example ?

EDIT (as most people seem to misunderstand my question) :

I'm wondering why the bash execution returns two instances of /bin/bash ./count_itself.sh, not why it returns grep count_itself.sh.

EDIT 2 :

And of course, I'm looking for a way to avoid this behaviour and have the script return /bin/bash ./count_itself.sh only once.

roberto06
  • 3,844
  • 1
  • 18
  • 29
  • 2
    `\`ps -aux | grep count_itself.sh\`` is executed in a sub-shell i.e. in a sub-process. Thus the shell executing your script is forked and the child then runs the `ps -aux | grep count_itself.sh` command. – Leon Nov 17 '16 at 10:46
  • I suggest: nb=`ps -aux | grep [c]ount_itself.sh – Cyrus Nov 17 '16 at 10:46
  • 2
    when you execute `ps -aux | grep count_itself.sh` your grep count it like a instance of count_itself. You need to improve you regex – Stargateur Nov 17 '16 at 10:48
  • @Leon but the line displayed twice is `/bin/bash ./count_itself.sh` , not `ps -aux | grep count_itself.sh` – roberto06 Nov 17 '16 at 10:48
  • @Cyrus ? What is that supposed to do ? It returns the same result. – roberto06 Nov 17 '16 at 10:49
  • @roberto06 `ps -aux` and `grep count_itself.sh` are then run as sub-processes of the sub-shell – Leon Nov 17 '16 at 10:50
  • Consider using `pgrep` instead of parsing the output of `ps`. – Aaron Nov 17 '16 at 10:51
  • @Leon could you elaborate your thinking in an answer? I guess you're on the right track, but since I'm pretty new to shellscripting, I don't fully understand your reasoning. – roberto06 Nov 17 '16 at 11:02
  • Related: [bash: differing newline count when assigning result to variable](http://stackoverflow.com/q/37132097/1983854). Specially interesting are the comments by triplee: _The `var=$(ps | grep -e "$0")` runs a subshell which is also returned_. This should be it. – fedorqui Jan 04 '17 at 10:14

4 Answers4

5

This is a standard issue with greping the output of ps.

One solution is to add some square brackets around a character

nb=$(ps -aux | grep '[c]ount_itself.sh')

This means that your grep instance doesn't match itself, because the name of the process with its arguments contains square brackets but the pattern that it matches doesn't.

As mentioned in the comments, you should use double quotes around your variables in order to preserve whitespace.

The reason why you have appear to have two instances of the same shell in your results is that the command substitution is executed within a subshell. For details on only showing the parent process, see this question.

Community
  • 1
  • 1
Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
2

Process substitution requires the parent shell to start a sub-shell, i.e. to fork and execute the specified commands in a child shell. This is necessary so that the parent shell is unaffected by any changes to the environment (variables, current working directory, traps), that the script enclosed in $(...) makes.

Example:

$ cat test.sh 
#!/bin/bash

a=1
b="$(a=2; echo abc)"
echo "a=$a"
echo "b=$b"
$ ./test.sh 
a=1           # Note that the variable 'a' preserved its value
b=abc

It is as a result of forking that you are seeing an extra instance of your script.

I don't think that it is possible to reliably eliminate those unwanted processes from your output, since, in principle, a script may legitimately start another instance of itself (which will run as a subprocess), and you cannot distinguish between those two cases.

One hacky solution is to have the script create at a designated location (e.g. in /tmp/your_script_name) a PID file upon invocation and remove it upon termination.

Leon
  • 31,443
  • 4
  • 72
  • 97
2

I suggest next way:

Exclude all process that parent are myself:

 ps --pid $$ -N -a | grep count_itself.sh

This means show all commands that parent are not myself (so this exclude your grep process and your fork process to execute counter sentence)

Joan Esteban
  • 1,006
  • 12
  • 23
  • It still echoes two lines with only the `--pid $$` flag, and it echoes nothing if I add the `-N` flag. I'm looking for a result somewhere in the middle :P – roberto06 Nov 17 '16 at 11:28
  • I guess that if with `-N` flag is returning nothing it's because you are the only instance running, may be, if you want to count yourself, you can add 1 to the result. – Joan Esteban Nov 17 '16 at 11:43
1

Finally found a way, albeit an ugly one, partially inspired from the question @TomFenech linked in his answer:

#!/bin/bash
nb=$(ps f | grep '[c]ount_itself.sh' | grep -v '    \\_')
echo "$nb"
sleep 20

Execution :

root@myserver:/# ./count_itself.sh
17725 pts/1    S+     0:00  \_ /bin/bash ./count_itself.sh

Execution with one already running in bg :

root@myserver:/# ./count_itself.sh &
[1] 17733
root@myserver:/# ./count_itself.sh
17733 pts/1    S      0:00  \_ /bin/bash ./count_itself.sh
17739 pts/1    S+     0:00  \_ /bin/bash ./count_itself.sh

Explanation (from what I've understood) :

  • ps f returns the tree of active processes
  • grep '[c]ount_itself.sh' restricts the previous command to only showing instances of count_itself.sh

Returns

17808 pts/1    S+     0:00  \_ /bin/bash ./count_itself.sh
17809 pts/1    S+     0:00      \_ /bin/bash ./count_itself.sh
  • grep -v ' \\_' excludes rows containing 4 spaces (equivalent of a tab) then \_, which correspond to subprocesses
Community
  • 1
  • 1
roberto06
  • 3,844
  • 1
  • 18
  • 29