1

I have a bash script (ScreamDaemon.sh) inside which a check that example of it isn't running already is added.

numscr=`ps aux | grep ScreamDaemon.sh | wc -l`;
if [ "${numscr}" -gt "2" ]; then
  echo "an instance of ScreamDaemon still running";
  exit 0;
fi

Normally, if there are no another copy of script running, ps aux | grep ScreamDaemon.sh | wc -l should return 2 (it should find itself and grep ScreamDaemon.sh), but it returns 3.

So, I try to analyse what happens and after adding some echoes see this:

there are lines I have added into the script

ps aux | grep ScreamDaemon.sh
ps aux | grep ScreamDaemon.sh | wc -l
str=`ps aux | grep ScreamDaemon.sh`
echo $str
numscr=`ps aux | grep ScreamDaemon.sh | wc -l`;
echo $numscr

there is an output:

pamela   27894  0.0  0.0 106100  1216 pts/1    S+   13:41   0:00 /bin/bash ./ScreamDaemon.sh
pamela   27899  0.0  0.0 103252   844 pts/1    S+   13:41   0:00 grep ScreamDaemon.sh
2
pamela 27894 0.0 0.0 106100 1216 pts/1 S+ 13:41 0:00 /bin/bash ./ScreamDaemon.sh pamela 27903 0.0 0.0 106100 524 pts/1 S+ 13:41 0:00 /bin/bash ./ScreamDaemon.sh pamela 27905 0.0 0.0 103252 848 pts/1 S+ 13:41 0:00 grep ScreamDaemon.sh
3

I also tried to add the sleep command right inside `ps aux | grep ScreamDaemon.sh; sleep 1m` and see from the parallel terminal how many instances ps aux|grep ScreamDaemon.sh shows:

[pamela@pm03 ~]$ ps aux | grep ScreamDaemon.sh
pamela   28394  0.0  0.0 106100  1216 pts/1    S+   14:23   0:00 /bin/bash ./ScreamDaemon.sh
pamela   28403  0.0  0.0 106100   592 pts/1    S+   14:23   0:00 /bin/bash ./ScreamDaemon.sh
pamela   28408  0.0  0.0 103252   848 pts/9    S+   14:23   0:00 grep ScreamDaemon.sh

So, it seems that str=`ps aux | grep ScreamDaemon.sh` contrary to ps aux | grep ScreamDaemon.sh found two instances of ScreamDaemon.sh, but why? Where this additional copy of ScreamDaemon.sh come from?

This is an output of pstree -ap command

  │   ├─sshd,27806
  │   │   └─sshd,27808
  │   │       └─bash,27809
  │   │           └─ScreamDaemon.sh,28731 ./ScreamDaemon.sh
  │   │               └─ScreamDaemon.sh,28740 ./ScreamDaemon.sh
  │   │                   └─sleep,28743 2m
Vitaly
  • 113
  • 1
  • 4
  • it excludes *grep ScreamDaemon.sh* from search results, but it still find **2** instances instead of **1** – Vitaly Jan 02 '16 at 14:59
  • Can you type and paste back the output of `pstree -ap` ? (you can trim off not needed lines) – Gilles Quénot Jan 02 '16 at 15:09
  • Can't find a way how to put it here in the readable form :( – Vitaly Jan 02 '16 at 15:18
  • So, something like this: `sshd,27806->sshd,27808->bash,27809->ScreamDaemon.sh,28731 ./ScreamDaemon.sh->ScreamDaemon.sh,28740 ./ScreamDaemon.sh->sleep,28743 2m` – Vitaly Jan 02 '16 at 15:27
  • Put this in the original message – Gilles Quénot Jan 02 '16 at 15:42
  • The title still says that you're asking about `ps aux` and `ps -aux`, but the body text doesn't appear to support this. – Charles Duffy Jan 02 '16 at 15:47
  • That said -- the extra instance is a subshell. Using command substitutions or pipelines will create them, among other things. – Charles Duffy Jan 02 '16 at 15:48
  • Anyhow, this is the wrong way to ensure that only one copy of a daemon is running; if you were doing it the right way, you wouldn't need `ps` involved at all. – Charles Duffy Jan 02 '16 at 15:48
  • @Charles Duffy, Thank You, I fixed the title – Vitaly Jan 02 '16 at 15:49
  • @Charles Duffy, What is a better way to do it then? Not necessary to describe everything just a key phrase to me to google it. – Vitaly Jan 02 '16 at 15:54
  • I included links to some existing StackOverflow questions and answers on the subject in my answer. I strongly suggest using one of the answers involving `flock`; many of the others are flawed (contain race conditions -- though relying on `ps` also has race conditions as well). – Charles Duffy Jan 02 '16 at 15:55
  • @Charles Duffy, Thank You! It is very useful info! – Vitaly Jan 02 '16 at 16:00
  • BTW, use `echo "$str"`, not `echo $str`, if you want whitespace, newlines, &c. to be preserved. This may be a good example to you as to why it's best practice to double-quote all expansions unless you have a specific reason not to. [Try your original code when the command that `ps` is finding has `' * '` amongst its arguments to see another example of why quotes are important]. – Charles Duffy Jan 02 '16 at 16:13

2 Answers2

2

Why can a single bash script show up multiple times in ps?

This is typical when any constructs which implicitly create a subshell are in play. For instance, in bash:

echo foo | bar

...creates a new forked copy of the shell to run the echo, with its own ps instance. Similarly:

( bar; echo done )

...creates a new subshell, has that subshell run the external command bar, and then has the subshell perform the echo.

Similarly:

foo=$(bar)

...creates a subshell for the command substitution, runs bar in there (potentially exec'ing the command and consuming the subshell, but this is not guaranteed), and reads its output into the parent.

Now, how does this answer your question? Because

result=$(ps aux | grep | wc)

...runs that ps command in a subshell, which itself creates an extra bash instance.


How can I properly ensure that only one copy of my script is running?

Use a lockfile.

See for instance:

Note that I strongly suggest use of a flock-based variant.

Community
  • 1
  • 1
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
0

Of course, the reason you find an additional process is because:

One process is running the sub-shell (of the command execution `..`)
included in your line: numscr=`ps aux | grep ScreamDaemon.sh | wc -l`;

that's the simplest answer.


However I would like to make some additional suggestions about your code:

First, quote your expansions, it should be: echo "$str".
Not doing so is making several lines collapse into a long one.

Second, you may use: grep [S]creamDaemon.sh to avoid matching the grep command itself.

Third, capture the command just once in a variable, then count lines from the variable. In this case it presents no problem, but for dynamic processes, one capture and the following capture to count could give different results.

Fourth, make an habit of using $(...) command substitutions instead of the more error prone (especially when Nesting) `...`.

### Using a file as the simplest way to capture the output of a command
### that is running in this shell (not a subshell).
ps aux | grep "[S]creamDaemon.sh" > "/tmp/tmpfile$$.txt"  

str="$(< "/tmp/tmpfile$$.txt")"                ### get the value of var "str"
rm "/tmp/tmpfile$$.txt"                        ### erase the file used ($$ is pid).

numscr="$(echo "$str" | wc -l)"                ### count the number of lines.
echo "$numscr"                                 ### present results.
echo "$str"

str="$( ps aux | grep "[S]creamDaemon.sh" )"   ### capture var "str".
numscr="$(echo "$str" | wc -l)"                ### count the number of lines.
echo "$numscr"                                 ### present results.
echo "$str"

### The only bashim is the `$(<...)`, change to `$(cat ...)` if needed.

@CharlesDuffy covered the point of a flock quite well, please read it.

Community
  • 1
  • 1