如何检查另一个实例是否正在运行我的shell脚本

56

GNU bash,版本1.14.7(1)

我有一个名为"abc.sh"的脚本, 我必须仅从abc.sh脚本检查它... 在里面,我编写了以下语句

status=`ps -efww | grep -w "abc.sh" | grep -v grep | grep -v $$ | awk '{ print $2 }'`
if [ ! -z "$status" ]; then
        echo "[`date`] : abc.sh : Process is already running"
        exit 1;
fi
我知道这是错误的,因为每次它在“ps”中找到自己的进程时都会退出。该如何解决?如何仅从该脚本检查脚本是否正在运行?

正在运行的进程的PID存储在$$中,请从列表grep -v $$中忽略它。 - lc2817
1
使用一些锁定文件会更简单。如果文件存在,则表示另一个副本正在运行。只需确保删除该文件即可。 - Grzegorz Żur
在我的情况下,检查文件的方式不安全,所以我打算使用这种方式。 - Jatin Bodarya
这个脚本有什么问题?它看起来是正确的,但你忽略了当前进程的PID,并且只检查是否有其他abc.sh进程在运行。 - Adam Siemion
显示剩余3条评论
17个回答

72

检查进程是否已经在执行的更简单方法是使用pidof命令。

if pidof -x "abc.sh" >/dev/null; then
    echo "Process already running"
fi

或者,当您的脚本执行时创建一个PID文件。这样,只需检查PID文件的存在性即可确定进程是否已在运行。

#!/bin/bash
# abc.sh

mypidfile=/var/run/abc.sh.pid

# Could add check for existence of mypidfile here if interlock is
# needed in the shell script itself.

# Ensure PID file is removed on program exit.
trap "rm -f -- '$mypidfile'" EXIT

# Create a file with current PID to indicate that process is running.
echo $$ > "$mypidfile"

...
更新: 现在问题已经改变,需要从脚本本身检查。在这种情况下,我们期望始终至少看到一个 abc.sh 正在运行。如果有多个 abc.sh,那么我们就知道该进程仍在运行。我仍建议使用 pidof 命令,如果进程已经在运行,该命令将返回 2 个 PID。您可以使用 grep 过滤掉当前 PID,循环使用 Shell,甚至可以倒回到只用计算 PIDs 的 wc 来检测多个进程。

以下是示例:

#!/bin/bash

for pid in $(pidof -x abc.sh); do
    if [ $pid != $$ ]; then
        echo "[$(date)] : abc.sh : Process is already running with PID $pid"
        exit 1
    fi
done

1
如果同时运行另一个脚本"xyzabc.sh"会怎么样呢?这个答案虽然不错,但并不是很安全。 - Jatin Bodarya
1
至少在我的机器上,如果运行 pidof -x abc.shpidof 将无法找到 xyzabc.sh。如果你想要安全,你需要一个系统范围的锁资源,这就是为什么人们建议使用 PID 文件或唯一的目录名称。使用进程列表中的名称不是一种分类检查进程是否正在运行的好方法,因为名称可以被欺骗。 - Austin Phillips
1
@Pureferret 请查看bash手册中的“特殊参数”部分,其中指出$将扩展为Shell进程的进程ID。在()子shell中,它将扩展为当前Shell的进程ID,而不是子shell的进程ID。 - Austin Phillips
pidof 似乎不是一个标准工具。它在我的 RedHat 版本中不可用。 - swdev
3
替代方案为 $(basename $0)for pid in $(pidof -x $(basename $0)); do - Suuuehgi
显示剩余5条评论

40

如果您需要 "pidof" 方法,这里是窍门:

    if pidof -o %PPID -x "abc.sh">/dev/null; then
        echo "Process already running"
    fi

-o %PPID参数告知省略调用shell或shell脚本的pid。更多信息请参阅pidof手册页。


1
这对我来说似乎是最干净的答案。 - drgibbon
2
对于/system/bin/sh(Android默认SHELL),我不得不使用“-o $$”进行修改,因此我的代码看起来像这样: if pidof -o $$ -x“$0”> /dev/null; then echo“进程已在运行中”; fi 不确定为什么“-o %PPID”不能排除我的脚本。 - Vaibhav S

13

解决方案:

if [[ `pgrep -f $0` != "$$" ]]; then
        echo "Another instance of shell already exist! Exiting"
        exit
fi

编辑:我最近查看了一些评论,因此我尝试进行了一些调试。我也会解释它。

说明:

  • $0 给出了你的运行脚本的文件名。
  • $$ 给出了你的运行脚本的 PID。
  • pgrep 搜索进程名称并返回 PID。
  • pgrep -f $0 通过文件名搜索,$0 是当前 bash 脚本的文件名,并返回其 PID。

因此,pgrep 检查你的脚本 PID($0)是否等于当前运行的脚本 ($$)。如果是,则脚本正常运行。如果不是,则意味着有另一个具有相同文件名的 PID 正在运行,因此退出。我使用 pgrep -f $0 而不是 pgrep bash 的原因是你可能同时运行多个 bash 实例,从而返回多个 PID。按文件名搜索只返回一个 PID。

异常情况:

  1. 要使用 bash script.sh 而不是 ./script.sh,因为后者没有 shebang 时不起作用。
    • 修复方法:在开头使用 #!/bin/bash shebang。
  2. sudo 不起作用的原因是它返回 pgrep 返回的 bash 和 sudo 的 PID,而不是 bash 的 PID。
    • 修复方法:
#!/bin/bash
pseudopid="`pgrep -f $0 -l`"
actualpid="$(echo "$pseudopid" | grep -v 'sudo' | awk -F ' ' '{print $1}')"

if [[ `echo $actualpid` != "$$" ]]; then
    echo "Another instance of shell already exist! Exiting"
    exit
fi

while true
do
    echo "Running"
    sleep 100
done
  1. 即使脚本未在运行,脚本也会退出。这是因为有另一个进程使用相同的文件名。尝试执行vim script.sh然后运行bash script.sh,它将失败,因为vim已经打开了同名文件。
    • 修复方法:使用唯一的文件名。

1
这是最好的解决方案。我不明白为什么这没有得到更多的赞。 - ChaosSpeeder
1
哎呀,当我使用sudo时这并不起作用,这是为什么? - Stavros Korokithakis
我使用了这个解决方案来确保cron作业不会在脚本已经运行时启动它。不幸的是,如果有人在vi中打开了脚本,该条件也会阻止脚本的另一个执行 ;-) - dokaspar
在我的Mac OS上,从zsh提示符运行似乎对我无效。 - StevieD

11

以下是你在各个地方看到的一个技巧:

status=`ps -efww | grep -w "[a]bc.sh" | awk -vpid=$$ '$2 != pid { print $2 }'`
if [ ! -z "$status" ]; then
    echo "[`date`] : abc.sh : Process is already running"
    exit 1;
fi

方括号[a](或选择其他字母)的使用防止grep找到自己。这使得grep -v grep无必要。我还删除了grep -v $$并修复了awk部分以实现相同的功能。


10

如果我说错了,请有人来打倒我

我知道mkdir操作是原子性的,所以您可以创建一个锁定目录

#!/bin/sh
lockdir=/tmp/AXgqg0lsoeykp9L9NZjIuaqvu7ANILL4foeqzpJcTs3YkwtiJ0
mkdir $lockdir  || {
    echo "lock directory exists. exiting"
    exit 1
}
# take pains to remove lock directory when script terminates
trap "rmdir $lockdir" EXIT INT KILL TERM

# rest of script here

2
这个方法可行,但如果它崩溃了(可能性很大),我们就不知道进程是否真的在运行了。 - Mike Q
1
@MikeQ,我们还有很多其他的步骤可以完成。例如,在目录中编写一个pidfile,如果无法创建该目录因为它已经存在,那么验证pidfile中的pid是否确实对应于程序的运行实例。 - glenn jackman
是的,我曾经简单考虑过那个,但不确定它的可行性如何。我正在尝试让我的脚本工作来检查虚拟主机域,并发现"pgrep -f"(在另一篇文章中提到)到目前为止是最好的选择。 - Mike Q
1
你无法捕获一个KILL。 - Tiana

9

以下是我在bash脚本中的做法:

if ps ax | grep $0 | grep -v $$ | grep bash | grep -v grep
then
    echo "The script is already running."
    exit 1
fi

这使得我可以在任何bash脚本中使用此代码片段。但是,当与cron一起使用时,它会创建另一个进程来使用/bin/sh执行它,因此需要使用grep bash。


2
这是我发现的唯一在Mac OS X上有效的解决方案。 - johnbaltis

7

我认为@Austin Phillips的答案非常准确。我会做一个小小的改进,即添加-o(忽略脚本本身的pid),并使用basename匹配脚本(即相同的代码可以放入任何脚本中):

if pidof -x "`basename $0`" -o $$ >/dev/null; then
    echo "Process already running"
fi

你也可以使用特殊的pid %PPID。来自pidof手册页面:特殊的pid %PPID可用于命名pidof程序的父进程,换句话说就是调用shell或shell脚本。 - Pat
谢谢,我认为这是最整洁、最便携的版本。 - DAB

5

pidof 对我来说无法正常工作,于是我继续搜索,发现了 pgrep

for pid in $(pgrep -f my_script.sh); do
    if [ $pid != $$ ]; then
        echo "[$(date)] : my_script.sh : Process is already running with PID $pid"
        exit 1
    else
      echo "Running with PID $pid"
    fi  
done

以下内容部分引用自上面的答案和https://askubuntu.com/a/803106/802276


这是唯一一个真正适合我的方法,其他方法需要跳过一些起始障碍(即如何调用脚本),使用锁文件可以避免由于锁的存在导致程序崩溃后无法重新启动。 - Mike Q
在 Bash 脚本中,pidof -x 对我来说失败了,但是这个方法却可以无缝运行。 - gomfy

4

可以稍微不同的方式使用PS命令来忽略子进程:

ps -eaf | grep -v grep | grep $PROCESS | grep -v $$

2

我不想在检查中硬编码abc.sh,所以我使用了以下内容:

MY_SCRIPT_NAME=`basename "$0"`
if pidof -o %PPID -x $MY_SCRIPT_NAME > /dev/null; then
    echo "$MY_SCRIPT_NAME already running; exiting"
    exit 1
fi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接