特定の時間にシェル スクリプトのインスタンスが 1 つしか実行されないようにする簡単な方法は何ですか?
41 に答える
flock(1)
ファイル記述子で排他的スコープ ロックを作成するために使用します。このようにして、スクリプトのさまざまな部分を同期することもできます。
#!/bin/bash
(
# Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
flock -x -w 10 200 || exit 1
# Do stuff
) 200>/var/lock/.myscript.exclusivelock
(
これにより、との間のコードが)
一度に 1 つのプロセスによってのみ実行され、プロセスがロックを長時間待機しないことが保証されます。
警告: この特定のコマンドは の一部ですutil-linux
。Linux 以外のオペレーティング システムを実行している場合は、利用できる場合と利用できない場合があります。
「ロックファイル」の存在をテストする素朴なアプローチには欠陥があります。
なんで?ファイルが存在するかどうかをチェックせず、単一のアトミック アクションでファイルを作成するためです。このため; 相互排除の試みを中断させる競合状態があります。
代わりに、 を使用できますmkdir
。 mkdir
ディレクトリがまだ存在しない場合は作成し、存在する場合は終了コードを設定します。さらに重要なことは、これらすべてを単一のアトミック アクションで実行できるため、このシナリオに最適です。
if ! mkdir /tmp/myscript.lock 2>/dev/null; then
echo "Myscript is already running." >&2
exit 1
fi
すべての詳細については、優れた BashFAQ を参照してください: http://mywiki.wooledge.org/BashFAQ/045
古くなったロックを処理したい場合は、fuser(1)が便利です。ここでの唯一の欠点は、操作に約 1 秒かかるため、すぐに実行できないことです。
これは、フューザーを使用して問題を解決する、私が一度書いた関数です。
# mutex file
#
# Open a mutual exclusion lock on the file, unless another process already owns one.
#
# If the file is already locked by another process, the operation fails.
# This function defines a lock on a file as having a file descriptor open to the file.
# This function uses FD 9 to open a lock on the file. To release the lock, close FD 9:
# exec 9>&-
#
mutex() {
local file=$1 pid pids
exec 9>>"$file"
{ pids=$(fuser -f "$file"); } 2>&- 9>&-
for pid in $pids; do
[[ $pid = $$ ]] && continue
exec 9>&-
return 1 # Locked by a pid.
done
}
次のようなスクリプトで使用できます。
mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }
移植性を気にしない場合 (これらのソリューションはほとんどすべての UNIX ボックスで動作するはずです)、Linux のfuser(1)はいくつかの追加オプションを提供し、flock(1)もあります。
これは、ロックファイルを使用し、それに PID をエコーする実装です。これは、 pidfileを削除する前にプロセスが強制終了された場合の保護として機能します。
LOCKFILE=/tmp/lock.txt
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
echo "already running"
exit
fi
# make sure the lockfile is removed when we exit and then claim it
trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT
echo $$ > ${LOCKFILE}
# do stuff
sleep 1000
rm -f ${LOCKFILE}
ここでのトリックは、kill -0
シグナルを送信せず、指定された PID を持つプロセスが存在するかどうかを確認するだけです。また、 を呼び出すと、プロセスが強制終了された場合でもロックファイルtrap
が確実に削除されます ( を除く)。kill -9
flock(2) システムコールの周りには、想像を絶する flock(1) と呼ばれるラッパーがあります。これにより、クリーンアップなどを心配することなく、排他ロックを確実に取得することが比較的簡単になります。シェル スクリプトでの使用方法については、man ページに例があります。
ロックの信頼性を高めるには、不可分操作が必要です。上記の提案の多くはアトミックではありません。提案されたlockfile(1)ユーティリティは、マンページで言及されているように、その「NFS耐性」として有望に見えます。OSがlockfile(1)をサポートしておらず、ソリューションがNFSで動作する必要がある場合、選択肢は多くありません。
NFSv2には2つのアトミック操作があります。
- シンボリックリンク
- 名前を変更
NFSv3では、create呼び出しもアトミックです。
ディレクトリ操作は、NFSv2およびNFSv3ではアトミックではありません(Brent Callaghanによる「NFSIllustrated」という本、ISBN 0-201-32570-5を参照してください。BrentはSunのNFSベテランです)。
これを知っていると、ファイルとディレクトリのスピンロックを実装できます(PHPではなくシェルで):
現在のディレクトリをロックします。
while ! ln -s . lock; do :; done
ファイルをロックする:
while ! ln -s ${f} ${f}.lock; do :; done
現在のディレクトリのロックを解除します(実行中のプロセスが実際にロックを取得したと仮定します):
mv lock deleteme && rm deleteme
ファイルのロックを解除します(実行中のプロセスが実際にロックを取得したと仮定します)。
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Removeもアトミックではないため、最初に名前の変更(アトミック)を実行してから、removeを実行します。
シンボリックリンクと名前変更の呼び出しでは、両方のファイル名が同じファイルシステムに存在する必要があります。私の提案:単純なファイル名(パスなし)のみを使用し、ファイルとロックを同じディレクトリに配置します。
flock のようなアトミック操作が必要です。そうしないと、最終的に失敗します。
しかし、群れが利用できない場合はどうすればよいですか。さて、mkdirがあります。それもアトミック操作です。mkdir が成功するプロセスは 1 つだけで、他のプロセスはすべて失敗します。
したがって、コードは次のとおりです。
if mkdir /var/lock/.myscript.exclusivelock
then
# do stuff
:
rmdir /var/lock/.myscript.exclusivelock
fi
古くなったロックを処理する必要があります。そうしないと、クラッシュ後にスクリプトが再び実行されなくなります。
別のオプションは、 を実行してシェルのnoclobber
オプションを使用することset -C
です。>
ファイルが既に存在する場合は失敗します。
簡単に言えば:
set -C
lockfile="/tmp/locktest.lock"
if echo "$$" > "$lockfile"; then
echo "Successfully acquired lock"
# do work
rm "$lockfile" # XXX or via trap - see below
else
echo "Cannot acquire lock - already locked by $(cat "$lockfile")"
fi
これにより、シェルは次を呼び出します。
open(pathname, O_CREAT|O_EXCL)
ファイルをアトミックに作成するか、ファイルが既に存在する場合は失敗します。
BashFAQ 045のコメントによると、これは で失敗する可能性がありksh88
ますが、私のすべてのシェルで動作します:
$ strace -e trace=creat,open -f /bin/bash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3
$ strace -e trace=creat,open -f /bin/zsh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_LARGEFILE, 0666) = 3
$ strace -e trace=creat,open -f /bin/pdksh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC|O_LARGEFILE, 0666) = 3
$ strace -e trace=creat,open -f /bin/dash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3
pdksh
フラグを追加するのは興味深いですがO_TRUNC
、明らかに冗長
です。空のファイルを作成しているか、何もしていないかのどちらかです。
どのように行うかは、rm
汚れた出口をどのように処理するかによって異なります。
クリーンな終了時に削除
クリーンでない終了の原因となった問題が解決され、ロックファイルが手動で削除されるまで、新しい実行は失敗します。
# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"
任意の出口で削除
スクリプトがまだ実行されていなければ、新しい実行は成功します。
trap 'rm "$lockfile"' EXIT
GNU Parallel
として呼び出されたときにミューテックスとして機能するため、これに使用できますsem
。したがって、具体的には、次を使用できます。
sem --id SCRIPTSINGLETON yourScript
タイムアウトも必要な場合は、次を使用します。
sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript
タイムアウト <0 は、セマフォがタイムアウト内に解放されない場合、スクリプトを実行せずに終了することを意味し、タイムアウト >0 は、とにかくスクリプトを実行することを意味します。
名前を付ける必要があることに注意してください( を使用--id
)。そうしないと、デフォルトで制御端末になります。
GNU Parallel
ほとんどの Linux/OSX/Unix プラットフォームに簡単にインストールできます。これは単なる Perl スクリプトです。
シェル スクリプトの場合、ロックの移植性が向上するため、私はmkdir
overを使用する傾向があります。flock
いずれにせよ、使用set -e
するだけでは十分ではありません。コマンドが失敗した場合にのみスクリプトを終了します。あなたのロックはまだ取り残されます。
適切なロックのクリーンアップのために、トラップを次の擬似コードのようなものに設定する必要があります (持ち上げられ、単純化され、テストされていませんが、アクティブに使用されているスクリプトから):
#=======================================================================
# Predefined Global Variables
#=======================================================================
TMPDIR=/tmp/myapp
[[ ! -d $TMP_DIR ]] \
&& mkdir -p $TMP_DIR \
&& chmod 700 $TMPDIR
LOCK_DIR=$TMP_DIR/lock
#=======================================================================
# Functions
#=======================================================================
function mklock {
__lockdir="$LOCK_DIR/$(date +%s.%N).$$" # Private Global. Use Epoch.Nano.PID
# If it can create $LOCK_DIR then no other instance is running
if $(mkdir $LOCK_DIR)
then
mkdir $__lockdir # create this instance's specific lock in queue
LOCK_EXISTS=true # Global
else
echo "FATAL: Lock already exists. Another copy is running or manually lock clean up required."
exit 1001 # Or work out some sleep_while_execution_lock elsewhere
fi
}
function rmlock {
[[ ! -d $__lockdir ]] \
&& echo "WARNING: Lock is missing. $__lockdir does not exist" \
|| rmdir $__lockdir
}
#-----------------------------------------------------------------------
# Private Signal Traps Functions {{{2
#
# DANGER: SIGKILL cannot be trapped. So, try not to `kill -9 PID` or
# there will be *NO CLEAN UP*. You'll have to manually remove
# any locks in place.
#-----------------------------------------------------------------------
function __sig_exit {
# Place your clean up logic here
# Remove the LOCK
[[ -n $LOCK_EXISTS ]] && rmlock
}
function __sig_int {
echo "WARNING: SIGINT caught"
exit 1002
}
function __sig_quit {
echo "SIGQUIT caught"
exit 1003
}
function __sig_term {
echo "WARNING: SIGTERM caught"
exit 1015
}
#=======================================================================
# Main
#=======================================================================
# Set TRAPs
trap __sig_exit EXIT # SIGEXIT
trap __sig_int INT # SIGINT
trap __sig_quit QUIT # SIGQUIT
trap __sig_term TERM # SIGTERM
mklock
# CODE
exit # No need for cleanup code here being in the __sig_exit trap function
これが何が起こるかです。すべてのトラップは出口を生成するため、__sig_exit
ロックをクリーンアップする関数が常に発生します (SIGKILL を除く)。
注: 私の終了値は低い値ではありません。なんで?さまざまなバッチ処理システムが、0 から 31 までの数字を作成または想定しています。これらを別の値に設定すると、スクリプトとバッチ ストリームを前のバッチ ジョブまたはスクリプトに応じて反応させることができます。
本当に速くて本当に汚い?スクリプトの上部にあるこのワンライナーは機能します。
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
もちろん、スクリプト名が一意であることを確認してください。:)
これは、アトミック ディレクトリ ロックと PID による古いロックのチェックを組み合わせ、古い場合は再起動するアプローチです。また、これはバシズムに依存していません。
#!/bin/dash
SCRIPTNAME=$(basename $0)
LOCKDIR="/var/lock/${SCRIPTNAME}"
PIDFILE="${LOCKDIR}/pid"
if ! mkdir $LOCKDIR 2>/dev/null
then
# lock failed, but check for stale one by checking if the PID is really existing
PID=$(cat $PIDFILE)
if ! kill -0 $PID 2>/dev/null
then
echo "Removing stale lock of nonexistent PID ${PID}" >&2
rm -rf $LOCKDIR
echo "Restarting myself (${SCRIPTNAME})" >&2
exec "$0" "$@"
fi
echo "$SCRIPTNAME is already running, bailing out" >&2
exit 1
else
# lock successfully acquired, save PID
echo $$ > $PIDFILE
fi
trap "rm -rf ${LOCKDIR}" QUIT INT TERM EXIT
echo hello
sleep 30s
echo bye
既知の場所にロック ファイルを作成し、スクリプトの開始時に存在を確認しますか? PID をファイルに入れると、誰かがスクリプトの実行を妨げている誤ったインスタンスを追跡しようとしている場合に役立ちます。
lockfile-progs
Debian マシンを対象とする場合、このパッケージは優れたソリューションであることがわかりました。procmail
ツールも付いていlockfile
ます。ただし、これらのどちらにもこだわらないことがあります。
mkdir
これは、アトミック性と PID ファイルを使用して古いロックを検出する私のソリューションです。このコードは現在、Cygwin のセットアップで運用されており、うまく機能します。
exclusive_lock_require
それを使用するには、何かへの排他的アクセスが必要なときに呼び出すだけです。オプションのロック名パラメーターを使用すると、異なるスクリプト間でロックを共有できます。より複雑なものが必要な場合に備えて、2 つの低レベル関数 (exclusive_lock_try
および) もあります。exclusive_lock_retry
function exclusive_lock_try() # [lockname]
{
local LOCK_NAME="${1:-`basename $0`}"
LOCK_DIR="/tmp/.${LOCK_NAME}.lock"
local LOCK_PID_FILE="${LOCK_DIR}/${LOCK_NAME}.pid"
if [ -e "$LOCK_DIR" ]
then
local LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"
if [ ! -z "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2> /dev/null
then
# locked by non-dead process
echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"
return 1
else
# orphaned lock, take it over
( echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null && local LOCK_PID="$$"
fi
fi
if [ "`trap -p EXIT`" != "" ]
then
# already have an EXIT trap
echo "Cannot get lock, already have an EXIT trap"
return 1
fi
if [ "$LOCK_PID" != "$$" ] &&
! ( umask 077 && mkdir "$LOCK_DIR" && umask 177 && echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null
then
local LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"
# unable to acquire lock, new process got in first
echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"
return 1
fi
trap "/bin/rm -rf \"$LOCK_DIR\"; exit;" EXIT
return 0 # got lock
}
function exclusive_lock_retry() # [lockname] [retries] [delay]
{
local LOCK_NAME="$1"
local MAX_TRIES="${2:-5}"
local DELAY="${3:-2}"
local TRIES=0
local LOCK_RETVAL
while [ "$TRIES" -lt "$MAX_TRIES" ]
do
if [ "$TRIES" -gt 0 ]
then
sleep "$DELAY"
fi
local TRIES=$(( $TRIES + 1 ))
if [ "$TRIES" -lt "$MAX_TRIES" ]
then
exclusive_lock_try "$LOCK_NAME" > /dev/null
else
exclusive_lock_try "$LOCK_NAME"
fi
LOCK_RETVAL="${PIPESTATUS[0]}"
if [ "$LOCK_RETVAL" -eq 0 ]
then
return 0
fi
done
return "$LOCK_RETVAL"
}
function exclusive_lock_require() # [lockname] [retries] [delay]
{
if ! exclusive_lock_retry "$@"
then
exit 1
fi
}
このスレッドの他の場所で既に説明されている flock の制限が問題にならない場合は、次のようにします。
#!/bin/bash
{
# exit if we are unable to obtain a lock; this would happen if
# the script is already running elsewhere
# note: -x (exclusive) is the default
flock -n 100 || exit
# put commands to run here
sleep 100
} 100>/tmp/myjob.lock
スクリプトの先頭にこの行を追加します
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
man flock の定型コードです。
さらにログが必要な場合は、これを使用してください
[ "${FLOCKER}" != "$0" ] && { echo "Trying to start build from queue... "; exec bash -c "FLOCKER='$0' flock -E $E_LOCKED -en '$0' '$0' '$@' || if [ \"\$?\" -eq $E_LOCKED ]; then echo 'Locked.'; fi"; } || echo "Lock is free. Completing."
flock
これは、ユーティリティを使用してロックを設定およびチェックします。このコードは、FLOCKER 変数をチェックして初めて実行されたかどうかを検出します。スクリプト名に設定されていない場合は、flock を使用して FLOCKER 変数を初期化して再帰的にスクリプトを再起動しようとします。FLOCKER が正しく設定されている場合は、前の反復で flock します。成功し、続行しても問題ありません。ロックがビジーの場合、構成可能な終了コードで失敗します。
Debian 7 では動作しないようですが、実験的な util-linux 2.25 パッケージで再び動作するようです。「flock: ... Text file busy」と書かれています。スクリプトの書き込み権限を無効にすることでオーバーライドできます。
私はロックファイル、ロックディレクトリ、特別なロック プログラムを廃止したいと考えていpidof
ました。また、可能な限り単純なコード (または少なくとも可能な限り少ない行) が必要でした。1 行の最も単純if
なステートメント:
if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi
古いロック ファイルを処理する単純なアプローチを使用します。
pid を格納する上記のソリューションの一部は、pid がラップアラウンドできるという事実を無視していることに注意してください。そのため、保存された pid を持つ有効なプロセスがあるかどうかを確認するだけでは、特に長時間実行されるスクリプトの場合は十分ではありません。
noclobber を使用して、一度に 1 つのスクリプトだけが開いてロック ファイルに書き込むことができるようにします。さらに、プロセスを一意に識別するのに十分な情報をロックファイルに保存します。プロセスを一意に識別するデータのセットを pid、ppid、lstart と定義します。
新しいスクリプトの起動時に、ロック ファイルの作成に失敗した場合は、ロック ファイルを作成したプロセスがまだ残っていることを確認します。そうでない場合は、元のプロセスが不審な死を遂げ、古いロック ファイルが残っていると見なされます。その後、新しいスクリプトがロック ファイルの所有権を取得し、すべてがうまくいきます。
複数のプラットフォームにわたって複数のシェルで動作するはずです。高速、ポータブル、シンプル。
#!/usr/bin/env sh
# Author: rouble
LOCKFILE=/var/tmp/lockfile #customize this line
trap release INT TERM EXIT
# Creates a lockfile. Sets global variable $ACQUIRED to true on success.
#
# Returns 0 if it is successfully able to create lockfile.
acquire () {
set -C #Shell noclobber option. If file exists, > will fail.
UUID=`ps -eo pid,ppid,lstart $$ | tail -1`
if (echo "$UUID" > "$LOCKFILE") 2>/dev/null; then
ACQUIRED="TRUE"
return 0
else
if [ -e $LOCKFILE ]; then
# We may be dealing with a stale lock file.
# Bring out the magnifying glass.
CURRENT_UUID_FROM_LOCKFILE=`cat $LOCKFILE`
CURRENT_PID_FROM_LOCKFILE=`cat $LOCKFILE | cut -f 1 -d " "`
CURRENT_UUID_FROM_PS=`ps -eo pid,ppid,lstart $CURRENT_PID_FROM_LOCKFILE | tail -1`
if [ "$CURRENT_UUID_FROM_LOCKFILE" == "$CURRENT_UUID_FROM_PS" ]; then
echo "Script already running with following identification: $CURRENT_UUID_FROM_LOCKFILE" >&2
return 1
else
# The process that created this lock file died an ungraceful death.
# Take ownership of the lock file.
echo "The process $CURRENT_UUID_FROM_LOCKFILE is no longer around. Taking ownership of $LOCKFILE"
release "FORCE"
if (echo "$UUID" > "$LOCKFILE") 2>/dev/null; then
ACQUIRED="TRUE"
return 0
else
echo "Cannot write to $LOCKFILE. Error." >&2
return 1
fi
fi
else
echo "Do you have write permissons to $LOCKFILE ?" >&2
return 1
fi
fi
}
# Removes the lock file only if this script created it ($ACQUIRED is set),
# OR, if we are removing a stale lock file (first parameter is "FORCE")
release () {
#Destroy lock file. Take no prisoners.
if [ "$ACQUIRED" ] || [ "$1" == "FORCE" ]; then
rm -f $LOCKFILE
fi
}
# Test code
# int main( int argc, const char* argv[] )
echo "Acquring lock."
acquire
if [ $? -eq 0 ]; then
echo "Acquired lock."
read -p "Press [Enter] key to release lock..."
release
echo "Released lock."
else
echo "Unable to acquire lock."
fi
一部の UNIXlockfile
には、前述のflock
.
マンページから:
lockfile を使用して、1 つ以上のセマフォ ファイルを作成できます。lock-file が指定されたすべてのファイルを (指定された順序で) 作成できない場合、sleeptime (デフォルトは 8) 秒待機し、最後に失敗したファイルを再試行します。失敗が返されるまでの再試行回数を指定できます。再試行回数が -1 (デフォルト、つまり -r-1) の場合、lockfile は永久に再試行します。
実際、bmdhacksの答えはほぼ良いですが、最初にロックファイルをチェックした後、それを書き込む前に2番目のスクリプトが実行される可能性がわずかにあります。したがって、両方ともロックファイルを書き込み、両方とも実行されます。確実に機能させる方法は次のとおりです。
lockfile=/var/lock/myscript.lock
if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null ; then
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
else
# or you can decide to skip the "else" part if you want
echo "Another instance is already running!"
fi
このnoclobber
オプションは、ファイルがすでに存在する場合にリダイレクトコマンドが失敗することを確認します。したがって、リダイレクトコマンドは実際にはアトミックです。1つのコマンドでファイルを書き込んで確認します。ファイルの最後にあるロックファイルを削除する必要はありません。トラップによって削除されます。これが後で読む人に役立つことを願っています。
PSたとえば、Ctrl-Cでスクリプトを停止した後にロックファイルが残る可能性を減らすためのトラップコマンドが含まれていなかったにもかかわらず、Mikelがすでに質問に正しく答えていることはわかりませんでした。これが完全なソリューションです
少なくとも私のユースケースでは、bmdhack のソリューションが最も実用的であることがわかりました。flock と lockfile の使用は、スクリプトの終了時に rm を使用してロックファイルを削除することに依存しており、これは常に保証されるわけではありません (たとえば、kill -9)。
bmdhack の解決策について 1 つの小さな点を変更します。このセマフォを安全に動作させるために不要であるとは述べずに、ロック ファイルを削除することを強調します。彼の kill -0 の使用により、デッドプロセスの古いロックファイルが単純に無視/上書きされることが保証されます。
したがって、私の簡単な解決策は、シングルトンの先頭に次を追加することです。
## Test the lock
LOCKFILE=/tmp/singleton.lock
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
echo "Script already running. bye!"
exit
fi
## Set the lock
echo $$ > ${LOCKFILE}
もちろん、このスクリプトには、同時に開始される可能性が高いプロセスが競合するという欠陥があります。ロック テストと設定操作は単一のアトミック アクションではないためです。しかし、mkdir を使用するために lhunath によって提案された解決策には、強制終了されたスクリプトがディレクトリに残り、他のインスタンスが実行できなくなるという欠陥があります。
PID とロックファイルは間違いなく最も信頼できるものです。プログラムを実行しようとすると、ロックファイルが存在するかどうかを確認できます。存在するps
場合は、プロセスがまだ実行されているかどうかを確認するために使用できます。そうでない場合は、スクリプトを開始して、ロックファイル内の PID を独自のものに更新できます。
プロセスのロックを使用すると、はるかに強力になり、不正な終了も処理されます。プロセスが実行されている限り、lock_file は開いたままになります。プロセスが存在すると (プロセスが強制終了された場合でも)、(シェルによって) 閉じられます。これは非常に効率的であることがわかりました:
lock_file=/tmp/`basename $0`.lock
if fuser $lock_file > /dev/null 2>&1; then
echo "WARNING: Other instance of $(basename $0) running."
exit 1
fi
exec 3> $lock_file
上記の回答を組み合わせた、より洗練された、フェイルセーフで、迅速で汚れた方法を次に示します。
使用法
- 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 の組み合わせを使用してロックし、プロセスがまだ実行されていないことを確認します
- ロック解除前にスクリプトが停止したかどうかを検出できます (例: プロセスの強制終了、シャットダウン、エラーなど)。
- ロックファイルを確認し、ロックが見つからない場合にプロセスのシャットダウンをトリガーするために使用できます
- 詳細なエラー メッセージを出力して、デバッグを容易にします。
群れの道は行く道です。スクリプトが突然死んだらどうなるか考えてみてください。群れの場合、群れをほぐすだけですが、それは問題ではありません。また、邪悪なトリックはスクリプト自体に群がることであることに注意してください..しかし、もちろん、それはパーミッションの問題に全速力で実行することを可能にします.
if [ 1 -ne $(/bin/fuser "$0" 2>/dev/null | wc -w) ]; then
exit 1
fi
速くて汚い?
#!/bin/sh
if [ -f sometempfile ]
echo "Already running... will now terminate."
exit
else
touch sometempfile
fi
..do what you want here..
rm sometempfile
この1行の回答は、 Ask Ubuntu Q&Aに関連する誰かからのものです。
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
# This is useful boilerplate code for shell scripts. Put it at the top of
# the shell script you want to lock and it'll automatically lock itself on
# the first run. If the env var $FLOCKER is not set to the shell script
# that is being run, then execute flock and grab an exclusive non-blocking
# lock (using the script itself as the lock file) before re-execing itself
# with the right arguments. It also sets the FLOCKER env var to the right
# value so it doesn't run again.
スクリプト名が一意の場合、これは機能します。
#!/bin/bash
if [ $(pgrep -c $(basename $0)) -gt 1 ]; then
echo $(basename $0) is already running
exit 0
fi
スクリプト名が一意でない場合、これはほとんどの Linux ディストリビューションで機能します。
#!/bin/bash
exec 9>/tmp/my_lock_file
if ! flock -n 9 ; then
echo "another instance of this script is already running";
exit 1
fi