Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I have this beautiful little code here that will exit from a script if there's another instance of it running:

single_instance(){
    if pidof -x "${0##*/}" -o %PPID >/dev/null; then
        exit 0
    fi
}

But what I'm looking for is a function that will exit only if the script has been called with the same arguments.

I know I could hack my way with a cat | grep | awk | cut | sed | tac | sort | uniq solution, but I wonder if there's a simple way of doing this with utilities like pidof, ps, etc.

How would you go about doing this?

share|improve this question
    
Hmm, you would need to keep track of all the pids of running instances, and then check their command line through procfs (assuming they didn't change them somehow), if you have that available. Otherwise your launch script would need to also keep a copy of the command lines associated with each pid. And you need to check if parameters are given in a different order. –  didierc Jun 22 at 15:42
    
And this is where the trouble starts: how do you know how to split a command line in several parameters? Is the filename associated with the previous option toggle, or is it a parameter on its own? It depends on the application.. –  didierc Jun 22 at 15:49
    
+1 to didierc's answer, but your deleted one also has potential -- check the output of ps -o cmd= -p $PID -- this is intended for use in code and will give you the executable name plus arguments. –  goldilocks Jun 22 at 16:35
    
Thanks @goldilocks, I updated my answer! –  Teresa e Junior Jun 22 at 17:04

4 Answers 4

up vote 2 down vote accepted
fn() { IFS='
';  set -- $(ps -o args= -C "${0##*/}")
    unset IFS
    [ $(($(printf $(printf %s\\n "$@" | sort | uniq -c | sort -rn)))) -gt 1 ] &&
        exit 0
}

This will exit 0 if two or more $0 processes are currently running that were invoked with the same arguments.

share|improve this answer
    
Thanks, @mikeserv! I don't know why, but ps -o args= -C ${0##*/} always returns empty. –  Teresa e Junior Jun 22 at 17:07
    
@TeresaeJunior - Weird, I don't either... What is the name of $0? –  mikeserv Jun 22 at 17:08
    
$(basename $0) -- Or is that the same as ${0##*/} ? –  goldilocks Jun 22 at 17:09
    
@goldilocks - maybe, but it shouldn't make any practical difference. I did just add "quotes" - it maybe there was $IFS in the name before and this should fix that. But otherwise I'm stumped as of now. –  mikeserv Jun 22 at 17:11
1  
Yes, now it does! –  Teresa e Junior Jun 22 at 17:38

You can do something like this:

#!/bin/bash

single_instance() {

   pid=$(pidof -x "${0##*/}" -o %PPID)

   if [[ $(xargs -0 < /proc/$pid/cmdline) == $@ ]]
   then
       echo QUITTING
       exit 1
   fi
}

single_instance $(xargs -0 < /proc/$$/cmdline)

while :
do
    sleep 10
done
share|improve this answer
    
+1 nice handling of /proc/$pid/cmdline :) –  Teresa e Junior Jun 22 at 17:25

I came up with this after studying man ps and adding some code from @goldilocks. It does a good job in handling arguments with spaces, and also works if script is called as bash scriptname:

single_instance(){
    if ps -efww | grep "$(ps -o cmd= -p $$)$" | grep -vq " $$ "; then
        exit 0
    fi
}
share|improve this answer
    
This will always quit script if you call with arguments. –  cuonglm Jun 22 at 16:11
    
@Gnouc fixed now! –  Teresa e Junior Jun 22 at 17:14
    
How about: ps -wwNp $$ -ocmd= | grep -Fxq "$(ps -wwp $$ -ocmd=)" && exit 0 –  rici Jun 22 at 23:55
    
@rici I like your command, but I couldn't figure out why it will exit many times, even if there is no instance of the script running. –  Teresa e Junior Jun 23 at 14:14
1  
@TeresaeJunior: Fascinating. It's a race condition; the first ps may encounter one of the other subprocesses in the cmd during the time between fork and exec. At that moment the subprocess still has the same commandline but it has a new pid. I don't know why your version doesn't exhibit the same race condition, or maybe it does but less frequently. –  rici Jun 23 at 14:58

Using /proc:

single_instance(){

   local tl=$(cat /proc/$$/cmdline)
   local l

   tl=${tl##*/}

   for pid in $(pidof -x "${0##*/}" -o %%PPID); do
        l=$(cat /proc/$pid/cmdline)
        if [ ${l##*/} = $tl ]; then
            echo "already running..."
            exit 0
        fi
    done
}

It does an exact comparison between command lines without the launch path (like you did in your script). The /proc/*/cmdline values don't have any space in them, so you can conpare them directly. If the parameter order changes though, it won't notice it.

share|improve this answer
1  
Thanks, @didierc! It worked when I replaced your pidof command with mine above. I also found it funny that /proc/$pid/cmdline seems to remove all spaces, but this is enough already! –  Teresa e Junior Jun 22 at 16:35
    
Yes, I was in the process of changing my script to take spaces in account, and when I got it working, I realised it wasn't necessary! –  didierc Jun 22 at 16:43

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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