如何确保一次只运行一个Bash脚本实例?

时间:2011-06-26 07:00:22

标签: linux bash concurrency scheduled-tasks

我想创建一个只能在任何时候运行一次的sh脚本。

说,如果我执行脚本,那么我再次执行脚本,如何使脚本如果脚本的第一个exec仍在工作,第二个脚本将失败并出现错误。即在做任何事情之前,我需要检查脚本是否在其他地方运行。我该怎么做呢?

我运行的脚本运行时间很长(即永远运行)。我想使用像cron之类的东西每隔15分钟调用一次脚本,这样如果进程失败,它将由下一个cron运行脚本重新启动。

6 个答案:

答案 0 :(得分:15)

你想要一个pid文件,可能是这样的:

pidfile=/path/to/pidfile
if [ -f "$pidfile" ] && kill -0 `cat $pidfile` 2>/dev/null; then
    echo still running
    exit 1
fi  
echo $$ > $pidfile

答案 1 :(得分:9)

我认为你需要使用lockfile命令。请参阅using lockfiles in shell scripts (BASH)http://www.davidpashley.com/articles/writing-robust-shell-scripts.html

第二篇文章使用“手工制作的锁文件”,并展示如何捕获脚本终止&amp;释放锁;虽然在大多数情况下使用lockfile -l <timeout seconds>可能是一个很好的选择。

没有超时的使用示例:

lockfile script.lock
<do some stuff>
rm -f script.lock

确保在此期间启动的任何第二个脚本将无限期地等待文件被删除,然后再继续。

如果我们知道脚本的运行时间不应超过X秒,并且script.lock仍然存在,则可能意味着脚本的先前实例在删除script.lock之前被终止。在这种情况下,我们可以告诉lockfile在超时后强制重新创建锁(X = 10以下):

lockfile -l 10 /tmp/mylockfile
<do some stuff>
rm -f /tmp/mylockfile

由于lockfile可以创建多个锁定文件,因此有一个参数可指导它在重试获取所需的下一个文件(-<sleep before retry, seconds>-r <number of retries>)之前应等待多长时间。强制删除锁定时,还有一个参数-s <suspend seconds>用于等待时间(这种补充是强制破解锁定之前等待的超时时间)。

答案 2 :(得分:2)

将进程ID写入文件,然后在新实例启动时,检查文件以查看旧实例是否仍在运行。

答案 3 :(得分:2)

(
        if flock -n 9
        then
                echo 'Not doing the critical operation (lock present).'
                exit;
        fi

        # critical section goes here

) 9>'/run/lock/some_lock_file'
rm -f '/run/lock/some_lock_file'

来自flock(1)手册页中的示例。在shell脚本中使用非常实用。

答案 4 :(得分:1)

您可以使用run-one包,该包提供run-onerun-this-onekeep-one-running

套餐:https://launchpad.net/ubuntu/+source/run-one

介绍它的博客:http://blog.dustinkirkland.com/2011/02/introducing-run-one-and-run-this-one.html

答案 5 :(得分:0)

我刚写了一个工具来做到这一点: https://github.com/ORESoftware/quicklock

写一个好的一个需要大约15个loc,所以不想要包含在每个shell脚本中。

基本上是这样的:

$ ql_acquire_lock

以上调用此bash函数:

function ql_acquire_lock {
  set -e;
  name="${1:-$PWD}"  # the lock name is the first argument, if that is empty, then set the lockname to $PWD
  mkdir -p "$HOME/.quicklock/locks"
  fle=$(echo "${name}" | tr "/" _)
  qln="$HOME/.quicklock/locks/${fle}.lock"
  mkdir "${qln}" &> /dev/null || { echo "${ql_magenta}quicklock: could not acquire lock with name '${qln}'${ql_no_color}."; exit 1; }
  export quicklock_name="${qln}";  # export the var *only if* above mkdir command succeeds
  trap on_ql_trap EXIT;
}

当脚本退出时,它会使用陷阱

自动释放锁定
function on_ql_trap {
   echo "quicklock: process with pid $$ was trapped.";
   ql_release_lock
}

要随意手动释放锁定,请使用ql_release_lock

function ql_maybe_fail {
  if [[ "$1" == "true" ]]; then
      echo -e "${ql_magenta}quicklock: exiting with 1 since fail flag was set for your 'ql_release_lock' command.${ql_no_color}"
      exit 1;
  fi
}

function ql_release_lock () {

   if [[ -z "${quicklock_name}" ]]; then
     echo -e "quicklock: no lockname was defined. (\$quicklock_name was not set).";
     ql_maybe_fail "$1";
     return 0;
   fi

   if [[ "$HOME" == "${quicklock_name}" ]]; then
     echo -e "quicklock: dangerous value set for \$quicklock_name variable..was equal to user home directory, not good.";
     ql_maybe_fail "$1"
     return 0;
   fi

   rm -r "${quicklock_name}" &> /dev/null &&
   { echo -e "quicklock: lock with name '${quicklock_name}' was released.";  } ||
   { echo -e "quicklock: no lock existed for lockname '${quicklock_name}'."; ql_maybe_fail "$1"; }
   trap - EXIT  # clear/unset trap

}