如何确保我的 bash 脚本尚未运行?

Posted

技术标签:

【中文标题】如何确保我的 bash 脚本尚未运行?【英文标题】:How do I make sure my bash script isn't already running? 【发布时间】:2010-11-29 06:49:00 【问题描述】:

我有一个 bash 脚本,我想从 cron 每 5 分钟运行一次...但有可能该脚本的上一次运行尚未完成...在这种情况下,我希望新的运行退出.我不想只依赖 /tmp 中的一个锁定文件。我想确保在我尊重锁定文件(或其他任何东西)之前该进程正在实际运行......

这是我到目前为止从互联网上偷来的东西......我如何让它变得更聪明一点?还是有更好的完全不同的方法?

if [ -f /tmp/mylockFile ] ; then
  echo 'Script is still running'
else
  echo 1 > /tmp/mylockFile
  /* Do some stuff */
  rm -f /tmp/mylockFile
fi

【问题讨论】:

Quick-and-dirty way to ensure only one instance of a shell script is running at a time 的可能重复项 【参考方案1】:

如果要检查进程是否存在,只需查看

的输出

ps aux | grep your_script_name

如果它在那里,它还没有死……

正如 cmets 和其他答案中所指出的,使用存储在锁定文件中的 PID 更安全,并且是大多数应用程序采用的标准方法。我这样做是因为它很方便,而且在实践中我几乎从未见过极端情况(例如,在执行 cron 时编辑文件)。

【讨论】:

另外,您可以将当前 BASH 进程的 PID 存储在该锁定文件中。 BASH 提供了一个变量 '$$' (减去引号),它给出了那个数字。请参阅此处:tldp.org/LDP/abs/html/internalvariables.html 了解其中一些变量 -1 如果“emacs your_script_name”是正在运行的进程之一怎么办? 你说的都对。存储在文件中的 PID 将是一种更好的方法。我只是倾向于这样做,因为我很懒惰,我的脚本不是关键任务,而且它通常可以工作(很少见我会在 cron 执行时实际编辑脚本)。【参考方案2】:

将您的 pid 存储在 mylockFile 中。当您需要检查时,使用您从文件中读取的 pid 查找 ps 进程。如果存在,则说明您的脚本正在运行。

【讨论】:

【参考方案3】:

你总是可以:

 if ps -e -o cmd | grep scriptname > /dev/null; then 
     exit
 fi

但我自己喜欢锁文件,所以如果没有锁文件我也不会这样做。

【讨论】:

根据需要的可移植性,您可能需要调整 ps 选项,并且您可以将 -q 添加到 grep 而不是 /dev/null 不够同事证明:有人可能正在运行“更少的脚本名称”,这足以阻止脚本运行 mobrule:是的,从来没想过,因为我永远不会这样做 ;-) 锁文件更好。【参考方案4】:
# Use a lockfile containing the pid of the running process
# If script crashes and leaves lockfile around, it will have a different pid so
# will not prevent script running again.
# 
lf=/tmp/pidLockFile
# create empty lock file if none exists
cat /dev/null >> $lf
read lastPID < $lf
# if lastPID is not null and a process with that pid exists , exit
[ ! -z "$lastPID" -a -d /proc/$lastPID ] && exit
echo not running
# save my pid in the lock file
echo $$ > $lf
# sleep just to make testing easier
sleep 5

此脚本中至少存在一个竞争条件。不要将它用于生命支持系统,哈哈。但它应该适用于您的示例,因为您的环境不会同时启动两个脚本。有很多方法可以使用更多的原子锁,但它们通常取决于选择安装特定的东西,或者在 NFS 上以不同的方式工作,等等......

【讨论】:

为什么是cat /dev/null &gt;&gt; $lf 而不是touch $lf?没有循环时为什么要sleep touch(1) 也不错。睡眠只是为了演示,让我的脚本片段可以自己测试。 (也就是说,脚本一结束,pid就会消失)【参考方案5】:

如果您有幸在您的发行版中获得它,您可能想查看flock 命令的手册页。

姓名 群 - 从 shell 脚本管理锁 概要 群 [-sxon] [-w 超时] 锁定文件 [-c] 命令...

【讨论】:

与其他一些建议的解决方案不同,此解决方案最干净地避免了检查和获取锁之间的竞争条件。如果你有 'flock(1)'(在 debian/Ubuntu 等中带有 util-linux),一定要使用它。对于 OP 用例,您需要一个短暂的超时时间(例如 -w 1)。如果命令已经在运行,flock 会在 1 秒后放弃,不会运行命令,否则会获取锁并启动命令。 OP 可以使用 -n, --nb, --nonblock Fail (with an exit code of 1) rather than wait if the lock cannot be immediately acquired. 代替使用 -w 1 超时并在 1 秒后退出,请参阅我的答案中的示例,该示例在此主题处于活动状态后不幸添加了。【参考方案6】:

在某些情况下,您可能希望能够区分谁在运行脚本并允许一些并发但不是全部。在这种情况下,您可以使用 per-user、per-tty 或 cron-specific 锁。

您可以使用 $USER 等环境变量或tty 等程序的输出来创建文件名。对于cron,您可以在crontab 文件中设置一个变量并在您的脚本中对其进行测试。

【讨论】:

【参考方案7】:

永远不要使用锁文件总是使用锁目录。 在您的特定情况下,它并不那么重要,因为脚本的启动时间间隔为 5 分钟。但是,如果您曾经将此代码重用于网络服务器 cgi 脚本,那您就完蛋了。

if mkdir /tmp/my_lock_dir 2>/dev/null
then
   echo "running now the script"
   sleep 10
   rmdir /tmp/my_lock_dir
fi

如果您有一个陈旧的锁,这就会出现问题,这意味着锁存在但没有关联的进程。你的 cron 永远不会运行。

为什么要使用目录?因为 mkdir 是原子操作。一次只有一个进程可以创建目录,所有其他进程都会出错。这甚至适用于共享文件系统,甚至可能适用于不同的操作系统类型。

【讨论】:

锁定文件在 NFS 中不起作用——这在共享主机中很常见。出于同样的原因,我们经常使用锁定目录。【参考方案8】:

如果您使用锁定文件,则应确保始终删除锁定文件。你可以用'trap'来做到这一点:

if ( set -o noclobber; echo "locked" > "$lockfile") 2> /dev/null; then
  trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
  echo "Locking succeeded" >&2
  rm -f "$lockfile"
else
  echo "Lock failed - exit" >&2
  exit 1
fi

noclobber 选项使 lockfile 的创建原子化,就像使用目录一样。

【讨论】:

【参考方案9】:

你可以用这个:

pgrep -f "/bin/\w*sh .*scriptname" | grep -vq $$ && exit

【讨论】:

【参考方案10】:

作为一个单行,如果您不想使用锁定文件(例如,只读文件系统的 b/c/ 等)

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

它会检查带有脚本名称的 PID 的完整列表是否等于当前 PID。 "-x" 还检查 shell 脚本的名称。

Bash 让它变得更短更快:

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

【讨论】:

【参考方案11】:

我今天试图解决这个问题,我想出了以下内容:

COMMAND_LINE="$0 $*"
JOBS=$(SUBSHELL_PID=$BASHPID; ps axo pid,command | grep "$COMMAND_LINE" | grep -v $$ | g    rep -v $SUBSHELL_PID | grep -v grep)
if [[ -z "$JOBS" ]]
then
    # not already running
else
    # already running
fi

这依赖于$BASHPID,它包含子shell 中的PID(子shell 中的$$ 是父pid)。但是,这依赖于 Bash v4,我需要在具有 Bash v3.2.48 的 OSX 上运行它。我最终想出了另一个解决方案,它更干净:

JOBS=$(sh -c "ps axo pid,command | grep \"$COMMAND_LINE\" | grep -v grep | grep -v $$")

【讨论】:

【参考方案12】:

由于尚未提及套接字解决方案,因此值得指出的是,套接字可以用作有效的互斥体。套接字创建是一个原子操作,就像 Gunstick 指出的 mkdir 一样,因此套接字适合用作锁或互斥体。

Tim Kay 的 Perl 脚本 'Solo' 是一个非常小而有效的脚本,可确保在任何时候只能运行一个脚本的副本。它是专门为与 cron 作业一起使用而设计的,尽管它也适用于其他任务,而且我已经非常有效地将它用于非 crob 作业。

Solo 与目前提到的其他技术相比有一个优势,即检查是在您只想运行一份副本的脚本之外完成的。如果脚本已经在运行,那么该脚本的第二个实例将永远不会启动。这与在受锁保护的脚本内隔离代码块相反。编辑:如果 flock 用于 cron 作业,而不是在脚本内部,那么您也可以使用它来防止脚本的第二个实例启动 - 请参见下面的示例。

这是一个如何将它与 cron 一起使用的示例:

*/5 * * * * solo -port=3801 /path/to/script.sh args args args

# "/path/to/script.sh args args args" is only called if no other instance of
# "/path/to/script.sh" is running, or more accurately if the socket on port 3801
# is not open. Distinct port numbers can be used for different programs so that
# if script_1.sh is running it does not prevent script_2.sh from starting, I've
# used the port range 3801 to 3810 without conflicts. For Linux non-root users 
# the valid port range is 1024 to 65535 (0 to 1023 are reserved for root).

* * * * * solo -port=3802 /path/to/script_1.sh
* * * * * solo -port=3803 /path/to/script_2.sh

# Flock can also be used in cron jobs with a distinct lock path for different
# programs, in the example below script_3.sh will only be started if the one
# started a minute earlier has already finished.

* * * * * flock -n /tmp/path.to.lock -c /path/to/script_3.sh

链接:

个人网页:http://timkay.com/solo/ 单独脚本:http://timkay.com/solo/solo

希望这会有所帮助。

【讨论】:

【参考方案13】:

您可以使用this。

我将在这里无耻地复制粘贴解决方案,因为它是两个问题的答案(我认为它实际上更适合这个问题)。

用法

    包括 sh_lock_functions.sh 使用 sh_lock_init 初始化 使用 sh_acquire_lock 锁定 使用 sh_check_lock 检查锁 使用 sh_remove_lock 解锁

脚本文件

sh_lock_functions.sh

#!/bin/bash

function sh_lock_init 
    sh_lock_scriptName=$(basename $0)
    sh_lock_dir="/tmp/$sh_lock_scriptName.lock" #lock directory
    sh_lock_file="$sh_lock_dir/lockPid.txt" #lock file


function sh_acquire_lock 
    if mkdir $sh_lock_dir 2>/dev/null; then #check for lock
        echo "$sh_lock_scriptName lock acquired successfully.">&2
        touch $sh_lock_file
        echo $$ > $sh_lock_file # set current pid in lockFile
        return 0
    else
        touch $sh_lock_file
        read sh_lock_lastPID < $sh_lock_file
        if [ ! -z "$sh_lock_lastPID" -a -d /proc/$sh_lock_lastPID ]; then # if lastPID is not null and a process with that pid exists
            echo "$sh_lock_scriptName is already running.">&2
            return 1
        else
            echo "$sh_lock_scriptName stopped during execution, reacquiring lock.">&2
            echo $$ > $sh_lock_file # set current pid in lockFile
            return 2
        fi
    fi
    return 0


function sh_check_lock 
    [[ ! -f $sh_lock_file ]] && echo "$sh_lock_scriptName lock file removed.">&2 && return 1
    read sh_lock_lastPID < $sh_lock_file
    [[ $sh_lock_lastPID -ne $$ ]] && echo "$sh_lock_scriptName lock file pid has changed.">&2  && return 2
    echo "$sh_lock_scriptName lock still in place.">&2
    return 0


function sh_remove_lock 
    rm -r $sh_lock_dir

使用示例

sh_lock_usage_example.sh

#!/bin/bash
. /path/to/sh_lock_functions.sh # load sh lock functions

sh_lock_init || exit $?

sh_acquire_lock
lockStatus=$?
[[ $lockStatus -eq 1 ]] && exit $lockStatus
[[ $lockStatus -eq 2 ]] && echo "lock is set, do some resume from crash procedures";

#monitoring example
cnt=0
while sh_check_lock # loop while lock is in place
do
    echo "$sh_scriptName running (pid $$)"
    sleep 1
    let cnt++
    [[ $cnt -gt 5 ]] && break
done

#remove lock when process finished
sh_remove_lock || exit $?

exit 0

特点

使用文件、目录和进程 ID 的组合来锁定以确保进程尚未运行 您可以检测脚本是否在解除锁定之前停止(例如进程终止、关闭、错误等) 您可以检查锁定文件,并在锁定丢失时使用它来触发进程关闭 详细,输出错误消息以便于调试

【讨论】:

以上是关于如何确保我的 bash 脚本尚未运行?的主要内容,如果未能解决你的问题,请参考以下文章

在 linux bash 中运行 php 脚本(php 函数)

Bash:如何在使用微调器时获取命令的退出代码?

如何在 Mac 上从 Dockerfile 运行 bash 脚本

如何在 metalsmith 中运行 bash 脚本

考虑到它的参数,如何检测bash脚本是不是已经在运行[重复]

如何确保我的 get 查询不会受到 COSMOS db 中长时间运行的更新的影响?