4

let's say I want to see how many copies of a program are already running. I could do something like this:

ps ax | grep -c "$0"

that command by itself produces the expected result. BUT if I attempt to assign the output to a variable, it gets incremented by one! No matter how I try it:

var=$(ps ax | grep "$0" | sed -n '$=')
var=`ps ax | grep -c "$0"`

can someone please show me the right way to capture the correct output?

it would also be great to know why this is happening..

UPDATE after the first response from @fedorqui I realize I wasn't clear enough. let me elaborate:

I am running all three commands above in the same bash script. When I run the first one, it prints out the number 2: the program itself and the grep process with that program as an argument. when I run those same commands within variable assignments, the number 3 is stored.

please note that I am using two different methods of counting lines, grep and sed. in both cases they return 3 instead of the correct answer, 2.

here is a consolidated example to try in a test.sh file:

echo -n "without assignment: "
ps ax | grep -c "$0"
var=$(ps ax | grep "$0" | sed -n '$=')
echo "using sed method: $var"
var=`ps ax | grep -c "$0"`
echo "using grep method: $var"

the results on my debian box:

without assignment: 2
using sed method: 3
using grep method: 3

the questions again: why is this happening, and how to prevent or work around?

Eric Jensen
  • 453
  • 4
  • 14
  • To me, running the script with `./script.sh` solves the problem. If I run with `bash script.sh` it gets one extra process. – fedorqui May 10 '16 at 08:18
  • Also, I am wondering: why do you want to know how many instances of your script are running? If you want to avoid concurrency, there are other safer ways to do it. For example, creating a dummy directory when you launch it, so that another instance seeing it will determine that the script is running. When you finish, you delete the dir. – fedorqui May 10 '16 at 08:33
  • @fedorqui basically the lock file concept right? maybe I should go that route. I'd love to see some reading material on lock files vs process monitoring... not for this project but just in general – Eric Jensen May 11 '16 at 05:41
  • @triplee note this is not a duplicate, check the sample given at the end of the question. The problem lies in the fact that `ps -ef | grep $0` returns one process less than `var=$(ps -ef | grep $0)`. – fedorqui May 11 '16 at 08:27
  • there is a super interesting thread about this in [Correct locking in shell scripts?](http://unix.stackexchange.com/q/22044/40596). I thought creating a dir, being an atomic action, was the best approach, but over there I see many other (maybe better) solutions. – fedorqui May 11 '16 at 08:29
  • @fedorqui Looks like a perfect duplicate to me. The `var=$(ps | grep -e "$0")` runs a subshell which is also returned. – tripleee May 11 '16 at 12:00
  • @tripleee the reason is described in the other question, that for sure. However, the approaches there do not work here: there is no way to use the `grep [h]ello` approach here, since `$0` is not known. – fedorqui May 11 '16 at 13:52
  • `grep "[${0:0:1}]${0:1}"`? – tripleee May 11 '16 at 17:13

3 Answers3

3

Quoting Siegex:

Because the grep process itself is being returned by ps.

You can either of these:

"trick" grep to not match itself by surrounding one of the search characters in a character class [ ] which doesn't change the functionality:

Or, in this case,

Pipe to grep -v grep, so that the process doesn't match:

var=$(ps ax | grep -v grep | grep "$0")

See an example. Here we have a process sleep:

$ sleep 20 &
[1] 5602

If we check for it in the output of ps it appears twice!

$ ps -ef| grep sleep
me   5602  5433  0 09:49 pts/2    00:00:00 sleep 20
me   5607  5433  0 09:49 pts/2    00:00:00 grep --colour=auto sleep

So we can either use a character class:

$ ps -ef| grep [s]leep
me   5602  5433  0 09:49 pts/2    00:00:00 sleep 20

Or grep out the grep process:

$ ps -ef| grep sleep | grep -v grep
me   5602  5433  0 09:49 pts/2    00:00:00 sleep 20
Community
  • 1
  • 1
fedorqui
  • 275,237
  • 103
  • 548
  • 598
  • 1
    what if $0 is `grep` ;-) – blackSmith May 10 '16 at 07:47
  • I really appreciate the comprehensive answer, but this isn't the solution. I am running all 3 lines in the same bash script. When I run the first one, it prints out 2: as you say the program itself and the grep process with that program as an argument. when I run those commands as variable assignments, the number 3 is stored. – Eric Jensen May 10 '16 at 07:55
  • @blackSmith good point! Even though I don't think that can happen many times, since `grep` will be running just for a moment instead of the long time running processes we usually check with `ps`. – fedorqui May 10 '16 at 07:55
  • @EricJensen this is quite weird. I checked this with `sleep 200 &` and checking `sleep` instead of `$0` with your code and it always returns 2. Mind to give more details? – fedorqui May 10 '16 at 07:57
  • @fedorqui I just updated the question – Eric Jensen May 10 '16 at 08:00
  • @EricJensen ok I am checking this and I can reproduce the behaviour. It must be related to some subprocesses being opened by the pipes. I am investigating. – fedorqui May 10 '16 at 08:11
  • @fedorqui I have provided a test script, which I should have done in the beginning! – Eric Jensen May 10 '16 at 08:17
  • @EricJensen I saw it, thanks! Note that the assignment opens a subshell. You can see it by changing `ps ax` with `ps -ef`. Check for example `r=$(ps -ef | grep "$0")` and then print it: `echo "$r"`. – fedorqui May 10 '16 at 08:19
  • @EricJensen : since it's certain that `grep "$0"` will always appear in the `ps ax` output, why simply decrement the `var` by one. It will remove complexity IMHO. – blackSmith May 10 '16 at 08:22
  • @fedorqui it was the assignment opening a subshell that I missed. the other answer said this first, so I accepted it, but I upvoted yours too. – Eric Jensen May 11 '16 at 05:37
  • @blackSmith good point I could do that or just compare the result against against '3' which is the normal number of occurrences in the process table for one instance of the program at that moment. I mainly wanted to understand why this was happening. – Eric Jensen May 11 '16 at 05:38
  • @EricJensen of course, heemayl's answer describes better the problem! I would even remove my answer because it does not really focus properly in the problem, but I think we have some very valuable comments here. – fedorqui May 11 '16 at 08:30
  • @fedorqui agree! also your point about grep process will be useful to others. – Eric Jensen May 14 '16 at 00:21
1
  • Command substitution itself runs in a subshell so thats one bash process

  • your search for bash ($0) i.e. grep -c bash also ends up in the process table at that time so thats another process (grep) containing string bash. Note that, this might not show up in the process table at the time of running, depending on how busy your system is.

  • And you have two (or whatever) actual bash processes (sessions) running presumably are the rest

You can use a Regex trick to get rid of the false positive i.e. grep one from count:

ps ax | grep -c "[b]ash"

It would still count the subshell while doing command substitution:

var=$(ps ax | grep -c "[b]ash")

So you need to manually remove one from this count.

Example:

$ var=$(ps ax | grep -c "bash")    
$ echo $var
4

$ var=$(ps ax | grep -c "[b]ash")   
$ echo $var
3
heemayl
  • 39,294
  • 7
  • 70
  • 76
  • are you saying that using var='' or var=$() runs another bash shell? so if the commands are being run inside a script there will be two running at that time? that could be the answer, however I still need a solution that searches for running instances of the script itself. any better ideas than searching for $0? each time the program runs it needs to make sure the previous instance isn't still running. – Eric Jensen May 10 '16 at 08:12
  • @EricJensen In `$(foobar)`, the command `foobar` runs in a subshell..You can find the PID of the current process e.g. your script by `$$` .. also as your script will be run as an argument to `bash`, you should probably search for the script name and combine with PPID and `$$` to get what you want.. – heemayl May 10 '16 at 08:31
0

Your command counts the grep command line too.

ps ax | grep -v grep | grep -c "$0"

should omit the grep from the count

louigi600
  • 716
  • 6
  • 16