【问题标题】:How to make sure only one instance of a Bash script is running at a time?如何确保一次只运行一个 Bash 脚本实例?
【发布时间】:2011-09-22 21:07:58
【问题描述】:

我想制作一个在任何时候最多只能运行一次的 sh 脚本。

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

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

【问题讨论】:

    标签: linux bash concurrency scheduled-tasks


    【解决方案1】:

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

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

    【讨论】:

    • 如果您的意思是在 kill 命令上,-0 表示该进程仍在运行,但如果它运行则不会触及它。 2>/dev/null 将丢弃任何不需要的噪音,以防进程实际上没有运行。
    • 我已经读到 PID 文件不是解决这个问题的好方法......鉴于某些进程可以采用相同的 PID 等......?
    • 进程ID可以被重用,这是真的。如果你真的很担心这个问题,你可以使用 Peter Anvin 的flock(1) 程序,它随 util-linux 发行版一起提供。
    【解决方案2】:

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

    第二篇使用“手工锁文件”,展示了如何捕捉脚本终止和释放锁;尽管在大多数情况下使用 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 可以创建多个锁定文件,因此有一个参数可以指导它应该等待多长时间才能重试获取它需要的下一个文件(-&lt;sleep before retry, seconds&gt;-r &lt;number of retries&gt;)。还有一个参数-s &lt;suspend seconds&gt; 用于强制解除锁定时的等待时间(这是对强制解除锁定之前用于等待的超时时间的一种补充)。

    【讨论】:

    • 很棒的链接,我以“set -x”和“set -e”发誓。
    • 有趣,以前从未尝试过。谢谢!!愚蠢的问题......我假设如果持有锁的进程被杀死/死亡,那么锁就会被释放而无需任何黑客攻击等......?
    • 看起来锁定文件将在这种情况下保留。但是,有一个参数(-s XX)来指定一个超时时间,在该超时时间之后,无论进程是否这样做,锁都会被删除。
    • 好的,参数可能有误。在我的盒子上是-l。我猜使用超时是一个设计问题——你的脚本/进程是否可以处理错误并且不会意外死亡。您还可以让另一个进程监视您的主进程并管理锁定文件。 =)
    • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review
    【解决方案3】:

    您可以使用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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 仅仅检查 pid 是否有效可能容易出错,因为可能有一个不相关的进程正在使用该 pid 运行。使用ps 检查进程是否实际相关可能会变得混乱。如果同一脚本的两个实例可以运行,但参数不同,该怎么办?
      • 绝对不是万无一失的。
      【解决方案5】:
      (
              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 脚本中使用非常实用。

      【讨论】:

        【解决方案6】:

        我刚刚写了一个工具来做到这一点: 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
        
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-01-08
          • 1970-01-01
          • 2010-09-16
          • 2011-04-27
          • 1970-01-01
          • 2023-04-09
          • 2012-01-09
          • 2011-01-15
          相关资源
          最近更新 更多