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

时间:2009-09-17 19:49:25

标签: bash

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

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

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

13 个答案:

答案 0 :(得分:20)

# 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等方面的工作方式不同......

答案 1 :(得分:9)

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

NAME
       flock - Manage locks from shell scripts
SYNOPSIS
       flock [-sxon] [-w timeout] lockfile [-c] command...

答案 2 :(得分:7)

永远不要使用锁文件始终使用锁目录。 在您的特定情况下,它不是那么重要,因为脚本的开始是以5分钟的间隔安排的。但是,如果您将此代码重新用于Web服务器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是一个原子操作。一次只有一个进程可以创建目录,所有其他进程都会出错。这甚至适用于共享文件系统,甚至可能在不同的操作系统类型之间。

答案 3 :(得分:4)

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

的输出

ps aux | grep your_script_name

如果它在那里,它还没死......

正如评论和其他答案中所指出的,使用存储在lockfile中的PID更加安全,是大多数应用程序采用的标准方法。我这样做是因为它很方便,我几乎从来没有看到过角落的情况(例如在cron执行时编辑文件)。

答案 4 :(得分:4)

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

答案 5 :(得分:3)

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

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原子,就像使用目录一样。

答案 6 :(得分:3)

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

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

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

Bash使它更短更快:

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

答案 7 :(得分:1)

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

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

答案 8 :(得分:1)

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

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 $$")

答案 9 :(得分:0)

你可以随时:

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

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

答案 10 :(得分:0)

你可以使用这个:

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

答案 11 :(得分:0)

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

Tim Kay的Perl剧本&#39; Solo&#39;是一个非常小而有效的脚本,可确保一次只能运行一个脚本副本。它专门设计用于cron作业,虽然它也适用于其他任务,并且我非常有效地将它用于非crob作业。

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

以下是您如何在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

链接:

希望这有帮助。

答案 12 :(得分:0)

您可以使用this

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

用法

  1. 包含 sh_lock_functions.sh
  2. 使用 sh_lock_init
  3. 初始化
  4. 使用 sh_acquire_lock
  5. 锁定
  6. 使用 sh_check_lock
  7. 检查锁定
  8. 使用 sh_remove_lock
  9. 解锁

    脚本文件

    <强> 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的组合进行锁定以确保进程尚未运行
    • 您可以检测脚本在锁定删除之前是否停止(例如,进程终止,关闭,错误等)
    • 您可以检查锁定文件,并在缺少锁定时使用它来触发进程关闭
    • 详细,输出错误消息以便于调试