0

Scenario

I'm trying add the following flag before the execution of main process in shell script to make sure that this script is running only once at a time by checking the number of pids of same-name script:

this_bash=$(basename $0)
this_pid=${$}
is_running="$(pidof -x $this_bash -o $this_pid | wc -l )"

I found it always returns 1, even when there is no other script with the same name running.

Investigation

For further information I tried this:

z=$(pidof -x $this_bash -o $this_pid)
echo "[$z]"
echo "[$(pidof -x $this_bash -o $this_pid)]"

echo "[$($z | wc -l )]"
echo "[$(pidof -x $this_bash -o $this_pid | wc -l )]"

the square brackets are to make sure there are no hidden white space chars.
The result was:

[]
[]
[0]
[1]

Question

I don't understand why storing pidof as variable returns the expected result, while piping the commands directly does not.

ceoper
  • 1
  • 3
  • If there is only one instance of the program running `pidof -x $this_bash -o $this_pid` is empty` resulting in `wc -l` attempting to read nothing and closing with an exit condition of `1`. Far better to prevent a second instance of the script from starting to begin with by using a *run file* if that is the goal. See [What is the best way to ensure only one instance of a Bash script is running?](https://stackoverflow.com/questions/1715137/what-is-the-best-way-to-ensure-only-one-instance-of-a-bash-script-is-running) – David C. Rankin May 01 '19 at 06:03
  • @DavidC.Rankin The `1` value of `is_running` isn't the exit code of `wc -l`. After all, `wc` has no issues printing `0` (*and* returning an exit code of `0`) for an empty input. For example, `wc -l – Mike Holt May 01 '19 at 06:14
  • I ran a test with just looking at how `pidof` behaved in `pidof -x "$0" -o "$pid"` (where `pid=$$`) and what I found was when you request the pid of the current shell and then `-o` (omit) that same pid, there is no output produced from the `pidof` command. – David C. Rankin May 01 '19 at 06:18
  • @DavidC.Rankin For a brief time, there are actually two instances of the script running, which seems to imply that `bash` is forking itself in order to carry out the process substitution, but only when there's a pipe involved. Another way to see this is to do `a=$(ps -ef); echo "$a" > a; b=$(ps -ef | cat -); echo "$b" > b; diff a b`. In which case you will see an extra copy of `bash ./`. – Mike Holt May 01 '19 at 06:19
  • Well yes, right, the pipe requires a subshell to tie `stdout` together with the next `stdin` as does the *command substitution*, e.g. `$(...stuff...)`. – David C. Rankin May 01 '19 at 06:20
  • Well it's that extra instance of the script that is causing `pidof` to print a pid when piped to `wc -l`, rather than just printing nothing. I'm just not sure why it's different between `$(pidof ...)` and `$(pidof ... | somecommand)`. The first involves one pipe (for the command substitution) and the second adds an additional pipe. Somehow the second example results in a child process with the same name as the parent script, but the first example doesn't. – Mike Holt May 01 '19 at 06:22
  • Since nobody pointed it out, is there a reason you are not using a lockfile? (You could check if a specific temporary file exists in the beginning of the script or use `flock`) – Simon Doppler May 01 '19 at 07:43
  • I see, so a piped process will create a subshell possessing the same name with this shell script. In that case I can do some work-around to my conflict-detecting algorithm. Thanks for your explanations and extra tests. If you are willing to aggregate the discussion above to an answer, I will make it the best answer, otherwise I will later write an answer as a documentation. – ceoper May 01 '19 at 08:27
  • @SimonDoppler Actually I never come up with this kind of idea. After some reading of `flock` it seems a good way to realize what I was trying to do, since the locked processes can be stacked and wait for it's turn instead of just exit. – ceoper May 01 '19 at 08:33
  • @ceoper The reason you're seeing extra matching PIDs when piping to `wc -l` is because `bash` runs external programs by first doing `fork()` to clone itself, then the child process runs `exec()` with the path of the program being called, which replaces the child process. But for a brief moment, after the `fork()` and before the `exec()`, that child process appears in the process tree as an exact clone of `./yourscript.sh`. This is what `pidof` is seeing. It's printing the PID of the child process that will eventually become `wc`. – Mike Holt May 01 '19 at 18:40
  • @ceoper There are ways you could avoid that problem and still use `pidof`, but using `flock` is a better overall solution. – Mike Holt May 01 '19 at 18:42

0 Answers0