4

I have shell script test.sh that I running.

I want that in the beginning of this script ,if there are older test.sh that running ,kill them first.

If I killall test.sh the newest process killed too.

I there any simple way to do it ?

1
#!/bin/sh

term_handler () {
    if [ "$ok_to_exit" -eq 1 ]; then
        echo 'exiting'
        exit
    else
        echo 'refusing to exit'
    fi
}

trap term_handler TERM

ok_to_exit=0
pkill -f test.sh
ok_to_exit=1

while true; do
    echo 'working...'
    sleep 2
done

This script (of which there is a simpler version below) installs a signal handler, the term_handler function, which will exit the script upon receiving a TERM signal if the ok_to_exit variable's value is 1.

In the body of the script is a loop simulating some form of work being carried out. The important bit is that before calling pkill (you may change this to your killall command), it sets ok_to_exit to 0, and directly afterwards it sets it to 1.

When sending out the TERM signal to all matching processes, it will receive the signal itself, but it will refuse to exit. Any other matching processes, if it's not also in the exact same state (which could happen if you started the script more than once simultaneously), would terminate.

When running, this script would output

refusing to exit
working...
working...
working...

When another copy of the script is started, it outputs exiting and then terminates.

A simpler version of the same script:

#!/bin/sh

trap '' TERM
pkill -f test.sh
trap - TERM

while true; do
    echo 'working...'
    sleep 2
done

This simply ignores the TERM signal during the call to pkill, and then reinstates the default signal handler before continuing.

1

A nice way without playing around with signal handlers is to signal every process but yourself:

for pid in $(pgrep ${0##*/}); do
    if [[ $pid -ne $$ ]]; then
        kill -s TERM $pid
    fi
done

If you're not terribly familiar with bash ${0##*/} gives you the basename of $0, the name of your script invocation, so that "/path/to/test.sh" becomes "test.sh".

1

You'd have some trouble getting more than two instances of these started, but you could use pgrep/pkill like this:

#!/bin/sh

while [ "$(pgrep -c -f test.sh)" -gt 1 ]
do
  pkill --oldest -f test.sh
done

# rest of your script

Adjust the "test.sh" parameter to more precisely match the process name itself!

For examples:

/bin/sh ./test.sh

or

/bin/sh /full/path/to/test.sh

or

/bin/bash ./test.sh

or

sh test.sh

... so that you're not accidentally matching anything else.

1

If all your executions of test.sh always pass through such a watchguard check then you could just do:

pid=$(pgrep -o -f "/test.sh( |$)") && [ ${pid} -ne ${$} ] && kill $pid

The above line kills only the oldest execution unless it is exactly also the present execution, however it has been called.

But just in case you want to be absolutely sure that no previous executions ever slipped the check, you can put the above line in a while loop, like so:

while pid=$(pgrep -o -f "/test.sh( |$)") && [ ${pid} -ne ${$} ] ; do
    kill $pid
done

The above kills any previous executions of test.sh, however they have been called.

Depending on your needs, you might like a selection criteria that also considers specific literal arguments of the command.

You may do so by including whatever arguments you want to check within the double-quotes, like so:

(note: I'm showing only the pgrep part to use in the above code)

pgrep -o -f "/test.sh +arg1 +arg2 *$"

The extra + and *$ are required to match no more and no less than those exact arguments. (In this example the literal strings arg1 and arg2).

You may also pass variables in, so for instance one desirable variant might be to kill only those previous executions that were run with the same arguments as the current execution:

pgrep -o -f "/test.sh +${*} *$"

Here the ${*} translates to all arguments of the current execution.

Basically, the string within double-quotes can be any valid regular-expression, and this helps you match the exact execution(s) you want to kill, which is very important in order to avoid killing foreign processes.

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.