回答:
以下は、ロックファイルを使用して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(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以外のオペレーティングシステムを実行している場合、利用できない場合があります。
( command A ) command B
はのサブシェルを呼び出しますcommand A
。tldp.org/LDP/abs/html/subshells.htmlに文書化されています。私はサブシェルとコマンドBの呼び出しのタイミングではないことを確認、まだ午前
if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fi
タイムアウトが発生した場合(他のプロセスがファイルをロックしている場合)、このスクリプトは先に進んでファイルを変更しません。おそらく...反論は「10秒かかってもロックがまだ利用できない場合、決して利用可能になることはありません」、おそらくロックを保持しているプロセスが終了していないためです(おそらく実行されています)デバッガーの下で?)
exit
内側の部分からです(
)
。サブプロセスが終了すると、ロックを保持しているプロセスがないため、ロックは自動的に解放されます。
「ロックファイル」の存在をテストするすべてのアプローチには欠陥があります。
どうして?ファイルが存在するかどうかを確認し、1つのアトミックアクションで作成する方法がないためです。このため; 競合状態が存在しWILL相互排他ブレークであなたの試みを作るには。
代わりに、を使用する必要がありますmkdir
。 mkdir
ディレクトリがまだ存在しない場合は作成し、存在する場合は終了コードを設定します。さらに重要なことは、このすべてを1つのアトミックアクションで実行し、このシナリオに最適です。
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)もあります。
if ! mkdir
部分を組み合わせて、lockdir内に(正常な起動時に)保存されたPIDを持つプロセスが実際に実行されており、stalenes保護用のスクリプトと同一であるかどうかを確認できます。これは、再起動後にPIDを再利用することからも保護しますfuser
。
mkdir
は定義されておらず、「副作用」がファイルシステムの実装の詳細であることは確かに当てはまります。NFSはそれをアトミックな方法で実装しないと彼が言ったなら、私は彼を完全に信じています。私はあなた/tmp
がNFS共有であり、mkdir
アトミックに実装するfsによって提供される可能性があるとは思いませんが。
ln
に作成する方法があります。別のファイルからハードリンクを作成するために使用します。それを保証しない奇妙なファイルシステムがある場合は、後で新しいファイルのiノードをチェックして、元のファイルと同じかどうかを確認できます。
open(... O_CREAT|O_EXCL)
。lockfile-create
(in lockfile-progs
)やdotlockfile
(in liblockfile-bin
)などの適切なユーザープログラムが必要です。そして、適切にクリーンアップする(例trap EXIT
:)か、古くなったロックをテストする(例:で)ことを確認してください--use-pid
。
想像を超えてflock(1)と呼ばれるflock(2)システムコールのラッパーがあります。これにより、クリーンアップなどを気にすることなく、確実に排他ロックを比較的簡単に取得できます。man ページに、シェルスクリプトでの使用方法に関する例があります。
flock()
システムコールは、POSIXではなく、NFSマウント上のファイルでは動作しません。
flock -x -n %lock file% -c "%command%"
1つのインスタンスのみが実行されていることを確認しています。
flockのようなアトミック操作が必要です。そうしないと、最終的に失敗します。
しかし、flockが利用できない場合の対処方法。mkdirがあります。これもアトミック操作です。1つのプロセスのみがmkdirを成功させ、他のすべてのプロセスは失敗します。
したがって、コードは次のとおりです。
if mkdir /var/lock/.myscript.exclusivelock
then
# do stuff
:
rmdir /var/lock/.myscript.exclusivelock
fi
古くなったロックを処理する必要があります。そうしないと、クラッシュが発生してスクリプトが再び実行されることはありません。
sleep 10
before rmdir
を追加して再度カスケードを試行します。「リーク」するものはありません。
ロックの信頼性を高めるには、アトミック操作が必要です。上記の提案の多くはアトミックではありません。提案されたlockfile(1)ユーティリティは、マンページで述べられているように、その「NFS耐性」があることを期待しています。OSがlockfile(1)をサポートしておらず、ソリューションがNFSで動作する必要がある場合、多くのオプションはありません...
NFSv2には2つのアトミック操作があります。
NFSv3では、作成呼び出しもアトミックです。
NFSv2とNFSv3では、ディレクトリ操作はアトミックではありません(Brent Callaghan著の「NFS Illustrated」の本、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
削除もアトミックではないため、最初に名前変更(アトミック)してから削除します。
symlinkとrenameの呼び出しでは、両方のファイル名が同じファイルシステムに存在する必要があります。私の提案:単純なファイル名のみ(パスなし)を使用し、ファイルとロックを同じディレクトリに配置します。
lockfile
可能な場合は使用し、使用できsymlink
ない場合はこのメソッドにフォールバックします。
mv
、rm
)では、2つのプロセスP1、P2が競合している場合rm -f
ではなく、使用する必要がありrm
ますか?たとえば、P1はでロック解除を開始しmv
、次にP2がロックし、次にP2がロック解除(mv
およびの両方rm
)し、最後にP1が試行rm
して失敗します。
$$
、${f}.deleteme
ファイル名に含めることで簡単に軽減できます。
別のオプションは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スクリプトです。
sem
、関連する質問unix.stackexchange.com/a/322200/199525を参照してください。
シェルスクリプトの場合、ロックをより移植しやすくするため、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までの数値を想定または想定しています。それらを別の値に設定すると、スクリプトとバッチストリームを前のバッチジョブまたはスクリプトに応じて反応させることができます。
rm -r $LOCK_DIR
か、必要に応じて強制的に実行する必要があります(相対スクラッチファイルを保持するなどの特殊なケースでも同様です)。乾杯。
exit 1002
か?
本当に迅速かつ本当に汚いですか?スクリプトの上部にあるこのワンライナーは動作します:
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
もちろん、スクリプト名が一意であることを確認してください。:)
-gt 2
ですか?grepは、psの結果で常にそれを見つけるとは限りません!
pgrep
POSIXにはありません。これを移植可能に動作させたい場合は、POSIX ps
とその出力を処理する必要があります。
-c
が存在しない場合は、を使用する必要があります| wc -l
。数の比較について:-gt 1
最初のインスタンスがそれ自体を見ているのでチェックされます。
これは、アトミックディレクトリロックと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
この例はman flockで説明されていますが、バグと終了コードを管理する必要があるため、いくつかの改善が必要です。
#!/bin/bash
#set -e this is useful only for very stupid scripts because script fails when anything command exits with status more than 0 !! without possibility for capture exit codes. not all commands exits >0 are failed.
( #start subprocess
# Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
flock -x -w 10 200
if [ "$?" != "0" ]; then echo Cannot lock!; exit 1; fi
echo $$>>/var/lock/.myscript.exclusivelock #for backward lockdir compatibility, notice this command is executed AFTER command bottom ) 200>/var/lock/.myscript.exclusivelock.
# Do stuff
# you can properly manage exit codes with multiple command and process algorithm.
# I suggest throw this all to external procedure than can properly handle exit X commands
) 200>/var/lock/.myscript.exclusivelock #exit subprocess
FLOCKEXIT=$? #save exitcode status
#do some finish commands
exit $FLOCKEXIT #return properly exitcode, may be usefull inside external scripts
別の方法を使用して、過去に使用したプロセスをリストすることもできます。しかし、これは上記の方法よりも複雑です。プロセスをpsでリストし、その名前でフィルタリングし、追加のフィルターgrep -v grepで寄生虫を削除し、最終的にgrep -cでカウントします。と比較します。その複雑で不確実な
投稿された既存の回答は、CLIユーティリティに依存しているかflock
、ロックファイルを適切に保護していません。flockユーティリティは、Linux以外のすべてのシステム(FreeBSDなど)では使用できず、NFSでは正しく機能しません。
システム管理とシステム開発の初期の頃、ロックファイルを作成する安全で比較的移植性の高い方法は、mkemp(3)
またはを使用して一時ファイルを作成し、一時ファイルにmkemp(1)
識別情報(つまりPID)を書き込んでからハードリンクすることでした一時ファイルからロックファイルへ。リンクが成功した場合、ロックは正常に取得されています。
シェルスクリプトでロックを使用するときは、通常、obtain_lock()
関数を共有プロファイルに配置して、スクリプトからソースを取得します。以下は私のロック機能の例です:
obtain_lock()
{
LOCK="${1}"
LOCKDIR="$(dirname "${LOCK}")"
LOCKFILE="$(basename "${LOCK}")"
# create temp lock file
TMPLOCK=$(mktemp -p "${LOCKDIR}" "${LOCKFILE}XXXXXX" 2> /dev/null)
if test "x${TMPLOCK}" == "x";then
echo "unable to create temporary file with mktemp" 1>&2
return 1
fi
echo "$$" > "${TMPLOCK}"
# attempt to obtain lock file
ln "${TMPLOCK}" "${LOCK}" 2> /dev/null
if test $? -ne 0;then
rm -f "${TMPLOCK}"
echo "unable to obtain lockfile" 1>&2
if test -f "${LOCK}";then
echo "current lock information held by: $(cat "${LOCK}")" 1>&2
fi
return 2
fi
rm -f "${TMPLOCK}"
return 0;
};
以下は、ロック機能の使用例です。
#!/bin/sh
. /path/to/locking/profile.sh
PROG_LOCKFILE="/tmp/myprog.lock"
clean_up()
{
rm -f "${PROG_LOCKFILE}"
}
obtain_lock "${PROG_LOCKFILE}"
if test $? -ne 0;then
exit 1
fi
trap clean_up SIGHUP SIGINT SIGTERM
# bulk of script
clean_up
exit 0
# end of script
clean_up
スクリプトのすべての終了ポイントで呼び出すことを忘れないでください。
LinuxとFreeBSDの両方の環境で上記を使用しました。
Debianマシンを対象とする場合、lockfile-progs
パッケージは良い解決策であると思います。ツールprocmail
も付属していlockfile
ます。しかし時々私はこれらのどちらにも行き詰まっています。
これがmkdir
、アトミック性とPIDファイルを使用して古いロックを検出する私のソリューションです。このコードは現在Cygwinセットアップで運用されており、正常に動作します。
それを使用するにはexclusive_lock_require
、何かへの排他的なアクセスを取得する必要があるときに呼び出します。オプションのロック名パラメーターを使用すると、異なるスクリプト間でロックを共有できます。さらに複雑なものが必要な場合はexclusive_lock_try
、2つの下位レベル関数(および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
-n
なりexit 1
、すぐにそれがロックを取得できない場合
いくつかのunixはlockfile
すでに述べたものと非常によく似ていflock
ます。
マンページから:
lockfileを使用して、1つ以上のセマフォファイルを作成できます。lock- fileが指定されたすべてのファイルを(指定された順序で)作成できない場合、sleeptime(デフォルトは8)秒待機し、成功しなかった最後のファイルを再試行します。失敗が返されるまでの再試行回数を指定できます。再試行回数が-1(デフォルト、つまり-r-1)の場合、lockfileは永久に再試行します。
lockfile
ユーティリティを入手できますか?
lockfile
とともに配布されprocmail
ます。またdotlockfile
、liblockfile
パッケージに対応する代替手段もあります。どちらもNFSで確実に機能すると主張しています。
実際、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私はMikelがすでに質問に正しく回答したことを確認しませんでしたが、たとえばCtrl-Cでスクリプトを停止した後にロックファイルが残る可能性を減らすためのトラップコマンドは含まれていませんでした。これが完全なソリューションです
私はロックファイル、ロックディレクトリ、特別なロックプログラムを廃止したいと思っていpidof
ました。また、可能な限り最も単純なコード(または、少なくとも可能な限り少ない行)が必要でした。if
1行で最も簡単なステートメント:
if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi
/bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
古いロックファイルを処理するシンプルなアプローチを使用しています。
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
スクリプトの最初にこの行を追加します
[ "${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変数をチェックして初めて実行されたかどうかを検出し、スクリプト名が設定されていない場合、FLOCKER変数を初期化して、FLOCKER変数が初期化された状態でスクリプトを再帰的に再起動します。成功し、続行しても問題ありません。ロックがビジーの場合、構成可能な終了コードで失敗します。
Debian 7では動作しないようですが、試験的なutil-linux 2.25パッケージで再び動作するようです。「flock:... Text file busy」と書き込みます。スクリプトの書き込み権限を無効にすることで上書きできます。
PIDとロックファイルは間違いなく最も信頼できます。プログラムを実行しようとすると、ロックファイルをチェックできます。ロックファイルが存在するps
場合は、プロセスがまだ実行されているかどうかを確認するために使用できます。そうでない場合、スクリプトを開始して、ロックファイルのPIDを独自のものに更新できます。
少なくとも私のユースケースでは、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}
もちろん、ロックテストと設定操作は単一のアトミックアクションではないため、このスクリプトには、同時に開始する可能性のあるプロセスが競合する危険があるという欠点があります。しかし、これをlhunathがmkdirを使用するために提案した解決策には、強制終了したスクリプトがディレクトリに残ってしまい、他のインスタンスが実行できないという欠点があります。
semaphoricユーティリティ用途flock
(presto8によって例えば、上述したように)実装する計数セマフォを。特定の数の同時プロセスを必要に応じて有効にします。これを使用して、さまざまなキューワーカープロセスの同時実行レベルを制限します。
semに似ていますが、はるかに軽量です。(完全な開示:semが私たちのニーズに対してあまりにも重く、単純なカウントセマフォユーティリティが利用可能ではなかったので、私はそれを書いた。)
flock(1)の例ですが、サブシェルはありません。flock()されたファイル/ tmp / fooは決して削除されませんが、flock()され、un-flock()されるので、それは重要ではありません。
#!/bin/bash
exec 9<> /tmp/foo
flock -n 9
RET=$?
if [[ $RET -ne 0 ]] ; then
echo "lock failed, exiting"
exit
fi
#Now we are inside the "critical section"
echo "inside lock"
sleep 5
exec 9>&- #close fd 9, and release lock
#The part below is outside the critical section (the lock)
echo "lock released"
sleep 5
すでに100万回回答されていますが、別の方法で、外部の依存関係は必要ありません。
LOCK_FILE="/var/lock/$(basename "$0").pid"
trap "rm -f ${LOCK_FILE}; exit" INT TERM EXIT
if [[ -f $LOCK_FILE && -d /proc/`cat $LOCK_FILE` ]]; then
// Process already exists
exit 1
fi
echo $$ > $LOCK_FILE
現在のPID($$)をロックファイルに書き込むたびに、スクリプトの起動時に、最新の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
群れの道は行くべき道です。スクリプトが突然死んだときに何が起こるか考えてください。群れの場合、群れを失うだけですが、それは問題ではありません。また、邪悪なトリックはスクリプト自体に群れをとることであることに注意してください。もちろん、これにより、許可の問題に完全に先行することができます。
早くて汚い?
#!/bin/sh
if [ -f sometempfile ]
echo "Already running... will now terminate."
exit
else
touch sometempfile
fi
..do what you want here..
rm sometempfile