8

Until now I used

pidof -o %PPID -x "my-tool"

To detect the pid of an eventually running instance of my-tool.

This is the short version of my-tool file, an executable bash script

#!/bin/bash 

if pidof -o %PPID -x "my-tool"; then
   echo "Already running"
   exit 1
fi

 ... if not running go on 

But now I need to allow a single instance per user and multiple per machine, so we can have even 100 my-tool running in the same moment, but only 1 per user.

Note that I need a test to create something like a singleton. If the tool will be started and there is another instance running it will close itself.

In short: I need that a bash script could detect if itself is already running for the current user and in this case it must exit.

How to ?

  • You shouldn't use pidof, there are better tools lie pgrep – cat Jul 26 '16 at 22:53
12

Use pgrep instead:

pgrep -cxu $USER -f my-tool

The options used are:

   -c, --count
          Suppress  normal  output; instead print a count of matching pro‐
          cesses.  When count does not match anything, e.g. returns  zero,
          the command will return non-zero value.
   -x, --exact
          Only match processes whose names (or command line if -f is spec‐
          ified) exactly match the pattern.
   -u, --euid euid,...
          Only match processes whose effective user ID is listed.   Either
          the numerical or symbolical value may be used.

If you want to use this in a bash script that checks if it is already running, you could use $0. This expands to the path of the current script (e.g. /home/username/bin/foo.sh) but we only need foo.sh. To get that, we can remove everything up to the last / using bash's string manipulation tools: ${0##*/}. This means we can do something like:

## If there are more than 1 instances of the current script run
## by this user
if [[ $(pgrep -cxu "$USER" "${0##*/}") -gt 1 ]];
then
        echo "Script already running, exiting."
        exit
fi

You might also want to consider using lockfiles for this:

## If the lock file exists
if [ -e /tmp/$USER.foo.lock ]; then
    ## Check if the PID in the lockfile is a running instance
    ## of foo.sh to guard against crashed scripts
    if ps $(cat /tmp/$USER.foo.lock) | grep foo.sh >/dev/null; then
        echo "Script foo.sh is already running, exiting"
        exit
    else
        echo "Lockfile contains a stale PID, continuing"
        rm /tmp/$USER.foo.lock 
    fi
fi
## Create the lockfile by printing the script's PID into it
echo $$ > /tmp/$USER.foo.lock

## Rest of the script here

## At the end, delete the lockfile
rm /tmp/$USER.foo.lock
  • Let us continue this discussion in chat. – terdon Jul 26 '16 at 12:34
  • 1
    Rather than touch /tmp/$USER.foo.lock I would use the PID of the process echo $$ >/tmp/$USER.foo.lock Then logic could be included to test whether such a process actually exists. – Monty Harder Jul 26 '16 at 18:33
  • @MontyHarder indeed, very good suggestion. Answer edited, thanks. – terdon Jul 27 '16 at 8:42
  • @terdon, had problem with mobile, deleted that comment in the next second. I meant $(< pidfile) instead of $(cat pidfile) . – sdkks Jul 27 '16 at 9:15
7

You can use pgrep fo find if a process is being executed by a specific user and then start the process if not running already by the user:

#!/bin/bash
if pgrep -u "$USER" my-tool &>/dev/null; then
    echo 'You already have my-tool running'
else
    /path/to/my_tool
fi

The environment variable, $USER, will be expanded to the currently logged in user i.e. the user running the script. As we are only interested in whether the my-tool is running or not, so just using the exit status directly with if construct is enough.

Use this script as a wrapper for starting my-tool and make the users use this only or rename it as my-tool and rename the original my-tool to something else (and change the name inside the script too).

  • I cannot make a wrapper, long history, ... how can I exclude the current process PID ? – realtebo Jul 26 '16 at 12:26
  • @realtebo ummm..exclude PID meaning? – heemayl Jul 26 '16 at 12:53
2

Try it with this snippet:

#!/bin/bash
MyProcessName=$(ps -p $$ -o args=)
Mypid=$$
AllPids=$(pgrep -fu "$(whoami)" "$MyProcessName")
AllPids=$(tr "\n" ' ' <<<"$AllPids")
Pids=$(sed "s/$Mypid//" <<<"$AllPids")
echo "$$: Instances including itself: $AllPids"
echo "$$: Instances different from itself: $Pids"

It is important to not write pgrep|tr because this would fork into a same named shell.

2

Wrap the command you wish to run in a flock to ensure that only one copy runs at a time. Use a lock_file stored within a user's home directory tree so that each user has their own lock file. For example:

lockfile = "~/lockfile"
(
 if flock -n 200; then
    command
 else
    echo "Could not get lock $lock_file
 fi
) 200>$lock_file

'command' can be any bash command or script.

man flock gives examples of usage

  • Really, I didn't know flock command at all – realtebo Aug 9 '16 at 12:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.