【问题标题】:How to check if another instance of my shell script is running如何检查我的 shell 脚本的另一个实例是否正在运行
【发布时间】:2025-11-24 16:55:02
【问题描述】:

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”中找到自己的进程 如何解决? 如何检查脚本是否已在运行,仅from that script

【问题讨论】:

  • 运行进程的PID保存在$$中,从列表中忽略 grep -v $$
  • 使用一些锁文件比较简单。如果文件存在,另一个副本正在运行。只需确保文件已被删除。
  • 在我的情况下检查文件的方法不安全..所以我想使用这种方式
  • 这个脚本有什么问题?似乎是正确的,您忽略了当前进程的 PID,只检查是否有任何其他 abc.sh 进程。

标签: bash shell concurrency


【解决方案1】:

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 中循环,甚至恢复为仅使用 wc 计算 PID 以检测多个进程。

这是一个例子:

#!/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

【讨论】:

  • 如果另一个脚本“xyzabc.sh”同时运行怎么办?这个答案很好,但不是很安全。
  • @user95711 至少在我的机器上,如果pidof -x abc.sh 运行,pidof 将不会接收xyzabc.sh。如果你想要安全,你需要一个系统范围的锁资源,这就是人们建议使用 PID 文件或唯一目录名称的原因。使用进程列表中的名称不是明确检查进程是否正在运行的好方法,因为名称可能会被欺骗。
  • @Pureferret 请参阅 bash man page 的特殊参数部分,其中指出 $ Expands to the process ID of the shell. In a () subshell, it expands to the process ID of the current shell, not the subshell.
  • pidof 似乎不是标准工具。它在我的 RedHat 版本中不可用。
  • @frederickjh 或者$(basename $0): for pid in $(pidof -x $(basename $0)); do
【解决方案2】:

如果我错了,请有人把我击落

我知道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

【讨论】:

  • 这行得通,但它会让一个人处于不知道进程是否实际运行的位置,如果它(可能)崩溃?
  • @MikeQ 我们可以做很多额外的步骤。比如在目录下写一个pidfile,如果dir存在我们无法创建,那么验证pidfile中的pid实际上对应的是程序的运行实例。
  • 是的,我短暂地想到了这一点,但不确定它的效果如何,我正试图让它适用于我检查虚拟主机域并找到“pgrep -f”的脚本提到(在另一篇文章中)迄今为止的最佳选择。
  • 你不能捕获 KILL。
【解决方案3】:

这里有一个你会在不同地方看到的技巧:

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 部分以完成同样的事情。

【讨论】:

    【解决方案4】:

    我想要“pidof”方法,这是诀窍:

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

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

    【讨论】:

    • 这对我来说似乎是最干净的答案。
    • 对于 /system/bin/sh,(Android 默认 SHELL)我不得不使用“-o $$”修改它,所以我的代码看起来像这样 if pidof -o $$ -x "$0" > /dev/null; then echo "Process already running"; fi 不知道为什么“-o % PPID" 无法排除我的脚本。
    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

      我在执行期间创建了一个临时文件。

      这就是我的做法:

      #!/bin/sh
      # check if lock file exists
      if [ -e /tmp/script.lock ]; then
        echo "script is already running"
      else
      # create a lock file
        touch /tmp/script.lock
        echo "run script..."
      #remove lock file
       rm /tmp/script.lock
      fi
      

      【讨论】:

        【解决方案7】:

        这是我在 bash 脚本中的操作方式:

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

        这允许我将这个 sn-p 用于任何 bash 脚本。我需要 grep bash,因为与 cron 一起使用时,它会创建另一个使用 /bin/sh 执行它的进程。

        【讨论】:

        • 这是我发现的唯一适用于 Mac OS X 的解决方案。
        【解决方案8】:

        我发现使用反引号将命令输出捕获到变量中会产生太多的 ps aux 结果,例如对于 abc.sh 的单个运行实例:

        ps aux | grep -w "abc.sh" | grep -v grep | wc -l
        

        返回“1”。然而,

        count=`ps aux | grep -w "abc.sh" | grep -v grep | wc -l`
        echo $count
        

        返回“2”

        似乎使用反引号构造以某种方式临时创建了另一个进程。可能是 topicstarter 无法完成这项工作的原因。只需要减少 $count 变量。

        【讨论】:

          【解决方案9】:

          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

          【讨论】:

          • 这是唯一对我有用的方法,对于其他方法,您必须跳过一些启动箍(如何调用脚本)并使用锁定文件使其在崩溃时由于锁在那里,不会再次启动。
          • pidof -x 在 bash 脚本上对我来说失败了,但这可以无缝地工作。
          【解决方案10】:

          我不想在支票中硬编码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
          

          【讨论】:

            【解决方案11】:

            我发现@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 脚本。
            【解决方案12】:

            这是紧凑和通用的

            # exit if another instance of this script is running
            for pid in $(pidof -x `basename $0`); do
               [ $pid != $$ ] && { exit 1; }
            done
            

            【讨论】:

              【解决方案13】:

              [ "$(pidof -x $(basename $0))" != $$ ] && exit

              https://github.com/x-zhao/exit-if-bash-script-already-running/blob/master/script.sh

              【讨论】:

                【解决方案14】:

                工作解决方案:

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

                编辑:我最近检查了一些 cmets,所以我尝试进行一些调试。我也会解释的。

                解释:

                • $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/bashshebang。
                2. sudo 不起作用的原因是它返回 pgrep 返回 bash 和 sudo 的 PID,而不是返回 bash。
                  • 修复:
                #!/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 以相同的文件名打开
                  • 修复:使用唯一的文件名。

                【讨论】:

                • 这是最好的解决方案。无法理解,为什么这不被喜欢。
                • 嗯,我sudo时这不起作用,这是为什么?
                • 我使用这个解决方案来确保 cron 作业在脚本已经运行时不会启动它。不幸的是,如果有人在vi 中打开了脚本,该条件也会阻止脚本的再次执行;-)
                【解决方案15】:

                最干净最快的方法:

                processAlreadyRunning () {
                    process="$(basename "${0}")"
                    pidof -x "${process}" -o $$ &>/dev/null
                }
                

                【讨论】:

                  最近更新 更多