我有test.sh
正在运行的 shell 脚本。
我希望在这个脚本的开头,如果有旧的test.sh
正在运行,请先杀死它们。
如果我杀死test.sh
所有最新进程也被杀死。
我有什么简单的方法可以做到吗?
答案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
该脚本(下面有一个更简单的版本)安装了一个信号处理程序,该函数将在收到信号term_handler
后退出脚本TERM
如果变量ok_to_exit
的值为1
。
脚本主体中有一个循环,模拟正在执行的某种形式的工作。重要的是,在调用之前pkill
(您可以将其更改为您的killall
命令),它设置ok_to_exit
为0
,然后立即将其设置为1
。
当向所有匹配的进程发出TERM
信号时,它本身会收到信号,但会拒绝退出。任何其他匹配过程,如果它不处于完全相同的状态(如果您同时启动脚本多次,则可能会发生这种情况),将终止。
运行时,该脚本将输出
refusing to exit
working...
working...
working...
当脚本的另一个副本启动时,它会输出exiting
然后终止。
同一脚本的更简单版本:
#!/bin/sh
trap '' TERM
pkill -f test.sh
trap - TERM
while true; do
echo 'working...'
sleep 2
done
这简直就是忽略TERM
调用期间的信号,pkill
然后在继续之前恢复默认信号处理程序。
答案2
不使用信号处理程序的一个好方法是向除您自己之外的每个进程发出信号:
for pid in $(pgrep ${0##*/}); do
if [[ $pid -ne $$ ]]; then
kill -s TERM $pid
fi
done
如果您对 bash 不太熟悉,${0##*/}
则会为您提供 的基本名称$0
(脚本调用的名称),以便“/path/to/test.sh”变为“test.sh”。
答案3
启动两个以上的实例时会遇到一些麻烦,但您可以像这样使用pgrep
/ :pkill
#!/bin/sh
while [ "$(pgrep -c -f test.sh)" -gt 1 ]
do
pkill --oldest -f test.sh
done
# rest of your script
调整“test.sh”参数以更精确地匹配进程名称本身!
举些例子:
/bin/sh ./test.sh
或者
/bin/sh /full/path/to/test.sh
或者
/bin/bash ./test.sh
或者
sh test.sh
...这样您就不会意外匹配其他任何内容。
答案4
如果全部你的处决test.sh
总是通过这样的看守检查,那么你可以这样做:
pid=$(pgrep -o -f "/test.sh( |$)") && [ ${pid} -ne ${$} ] && kill $pid
上面的行仅杀死最旧的执行,除非它也是当前的执行,无论它是如何被调用的。
但为了万一你想绝对确保以前的执行没有漏过检查,你可以将上面的行放入一个while
循环中,如下所示:
while pid=$(pgrep -o -f "/test.sh( |$)") && [ ${pid} -ne ${$} ] ; do
kill $pid
done
以上杀任何以前的处决test.sh
,但他们被称为。
根据您的需要,您可能需要一个还考虑命令的特定文字参数的选择标准。
您可以通过在双引号中包含您想要检查的任何参数来完成此操作,如下所示:
(注意:我只显示了pgrep
上面代码中使用的部分)
pgrep -o -f "/test.sh +arg1 +arg2 *$"
额外的+
和*$
必须与这些确切的参数匹配。 (在此示例中,文字字符串arg1
和arg2
)。
您还可以传递变量,因此,例如,一种理想的变体可能是仅终止那些使用与当前执行相同的参数运行的先前执行:
pgrep -o -f "/test.sh +${*} *$"
这里${*}
翻译为当前执行的所有参数。
基本上,双引号内的字符串可以是任何有效的正则表达式,这可以帮助您匹配要终止的确切执行,这对于避免终止外部进程非常重要。