Shell-Script

正確鎖定 shell 腳本?

  • December 4, 2018

有時您必須確保只有一個 shell 腳本實例同時執行。

例如,通過 crond 執行的 cron 作業本身不提供鎖定(例如,預設的 Solaris crond)。

實現鎖定的常見模式是這樣的程式碼:

#!/bin/sh
LOCK=/var/tmp/mylock
if [ -f $LOCK ]; then            # 'test' -> race begin
 echo Job is already running\!
 exit 6
fi
touch $LOCK                      # 'set'  -> race end
# do some work
rm $LOCK

當然,這樣的程式碼有競爭條件。有一個時間視窗,兩個實例的執行都可以在第 3 行之後推進,然後一個實例才能接觸$LOCK文件。

對於 cron 作業,這通常不是問題,因為兩次呼叫之間有幾分鐘的間隔。

但是事情可能會出錯 - 例如當鎖定文件位於 NFS 伺服器上時 - 掛起。在這種情況下,幾個 cron 作業可以在第 3 行阻塞並排隊。如果 NFS 伺服器再次處於活動狀態,那麼您將擁有大量並行執行的作業。

在網上搜尋我發現工具lockrun似乎是解決該問題的好方法。使用它,您可以執行一個需要鎖定的腳本,如下所示:

$ lockrun --lockfile=/var/tmp/mylock myscript.sh

你可以把它放在一個包裝器中,​​或者從你的 crontab 中使用它。

如果可用,它使用lockf()(POSIX)並回退到flock()(BSD)。並且lockf()對 NFS 的支持應該相對廣泛。

有替代品lockrun嗎?

其他 cron 守護程序呢?是否有常見的 crond 支持以理智的方式鎖定?快速瀏覽一下 Vixie Crond 的手冊頁(Debian/Ubuntu 系統上的預設設置)並沒有顯示任何關於鎖定的資訊。

lockruncoreutils中包含一個類似的工具是個好主意嗎?

timeout在我看來,它實現了一個與,nice和朋友非常相似的主題。

這是另一種鎖定 shell 腳本的方法,可以防止您在上面描述的競爭條件,其中兩個作業可能都通過第 3 行。該noclobber選項將在 ksh 和 bash 中工作。不要使用set noclobber,因為您不應該在 csh/tcsh 中編寫腳本。;)

lockfile=/var/tmp/mylock

if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null; then

       trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT

       # do stuff here

       # clean up after yourself, and release your trap
       rm -f "$lockfile"
       trap - INT TERM EXIT
else
       echo "Lock Exists: $lockfile owned by $(cat $lockfile)"
fi

YMMV 鎖定 NFS(你知道,當 NFS 伺服器不可訪問時),但總的來說它比以前更健壯。(10年前)

如果您有多個伺服器同時執行相同操作的 cron 作業,但您只需要 1 個實例即可實際執行,那麼類似的方法可能對您有用。

我沒有使用 lockrun 的經驗,但是在腳本實際執行之前有一個預設的鎖定環境可能會有所幫助。或者它可能不會。您只是在包裝器中為腳本外部的 lockfile 設置測試,理論上,如果 lockrun 在完全相同的時間呼叫兩個作業,您就不能遇到相同的競爭條件,就像使用 ‘inside-腳本的解決方案?

無論如何,文件鎖定幾乎是尊重系統行為,並且任何在執行之前不檢查鎖定文件存在的腳本都會執行它們將要執行的任何操作。只需進行鎖定文件測試和適當的行為,您將解決 99% 的潛在問題,如果不是 100% 的話。

如果您經常遇到鎖文件競爭條件,這可能表明存在更大的問題,例如您的作業沒有正確計時,或者如果間隔不如作業完成重要,那麼您的作業可能更適合被守護.


在下面編輯 - 2016-05-06(如果您使用的是 KSH88)


基於@Clint Pachl 在下面的評論,如果您使用 ksh88,請使用mkdir而不是noclobber. 這主要緩解了潛在的競爭條件,但並沒有完全限制它(儘管風險很小)。有關更多資訊,請閱讀克林特在下面發布的連結

lockdir=/var/tmp/mylock
pidfile=/var/tmp/mylock/pid

if ( mkdir ${lockdir} ) 2> /dev/null; then
       echo $$ > $pidfile
       trap 'rm -rf "$lockdir"; exit $?' INT TERM EXIT
       # do stuff here

       # clean up after yourself, and release your trap
       rm -rf "$lockdir"
       trap - INT TERM EXIT
else
       echo "Lock Exists: $lockdir owned by $(cat $pidfile)"
fi

另外,如果您需要在腳本中創建 tmpfiles,您可以使用它們的lockdir目錄,因為它們會在腳本退出時被清理。

對於更現代的 bash,頂部的 noclobber 方法應該是合適的。

引用自:https://unix.stackexchange.com/questions/22044