シェルスクリプトの1つのインスタンスのみが特定の時間に実行されていることを確認するための迅速で汚い方法は何ですか?
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)もあります 。
Flock(2)システムコールには、想像を絶するほどflock(1)と呼ばれるラッパーがあります。これにより、クリーンアップなどを心配せずに確実に排他ロックを比較的簡単に取得できます。シェルスクリプトでの排他ロックの使用方法に関する manページ の例があります。
Flockのようなアトミック操作が必要です。そうしないと、最終的に失敗します。
しかし、群れが利用できない場合の対処方法。まあmkdirがあります。それも原子操作です。 mkdirが成功するプロセスは1つだけで、他のプロセスはすべて失敗します。
したがって、コードは次のとおりです。
if mkdir /var/lock/.myscript.exclusivelock
then
# do stuff
:
rmdir /var/lock/.myscript.exclusivelock
fi
古いロックの世話をする必要があります。そうしないと、スクリプトが二度と実行されないクラッシュが発生します。
ロックの信頼性を高めるには、アトミック操作が必要です。上記の提案の多くはアトミックではありません。提案された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
削除もアトミックではないため、最初に名前変更(アトミック)を実行してから削除を実行します。
シンボリックリンクと名前変更の呼び出しでは、両方のファイル名が同じファイルシステムに存在する必要があります。私の提案:単純なファイル名(パスなし)のみを使用し、ファイルを入れて同じディレクトリにロックします。
別のオプションは、set -C
を実行してシェルのnoclobber
オプションを使用することです。ファイルが既に存在する場合、>
は失敗します。
簡単に言うと:
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
sem
として呼び出されたときにミューテックスとして機能するため、GNU Parallel
を使用できます。したがって、具体的には、次を使用できます。
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までの数字を期待します。これらを他の値に設定すると、スクリプトとバッチストリームを前のバッチジョブまたはスクリプトに応じて反応させることができます。
本当に 迅速かつ 本当に 汚れた?スクリプトの一番上にあるこの1行は機能します。
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
もちろん、スクリプト名が一意であることを確認してください。 :)
この例は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でカウントします。数値と比較します。その複雑で不確実な
既知の場所にロックファイルを作成し、スクリプトの開始時に存在を確認しますか?誰かがスクリプトの実行を妨げている誤ったインスタンスを追跡しようとする場合、ファイルにPIDを入れると役立つ場合があります。
アトミックディレクトリロックと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
このスレッドの他の場所ですでに説明されている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
Debianマシンをターゲットにする場合、lockfile-progs
パッケージが適切なソリューションであることがわかります。 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
}
すべてのLinuxインストールで見つかるわけではないので、ロックファイル、ロックディレクトリ、特別なロックプログラム、さらにはpidof
を廃止したかったのです。また、可能な限りシンプルなコード(または、少なくとも可能な限り少ない行)が必要でした。最も単純なif
ステートメント、1行で:
if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi
投稿された既存の回答は、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の両方の環境で上記を使用しました。
一部のUNIXにはlockfile
があります。これは、前述のflock
と非常に似ています。
マンページから:
lockfileを使用して、1つ以上のセマフォファイルを作成できます。 lock-fileがすべての指定されたファイルを(指定された順序で)作成できない場合、sleeptime(デフォルトは8)秒待機し、成功しなかった最後のファイルを再試行します。失敗が返されるまでの再試行回数を指定できます。再試行回数が-1(デフォルト、つまり-r-1)の場合、lockfileは永久に再試行します。
スクリプトの先頭にこの行を追加します
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
これは、男の群れからの定型コードです。
より多くのロギングが必要な場合は、これを使用してください
[ "${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変数が初期化された状態で再帰的にスクリプトを再起動しようとします。成功し、続行しても問題ありません。ロックがビジーの場合、構成可能な終了コードで失敗します。
Debian 7では動作しないようですが、実験的なutil-linux 2.25パッケージで再び動作するようです。 「flock:...テキストファイルがビジー」と書き込みます。スクリプトの書き込み許可を無効にすることで上書きできます。
実際、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つのコマンドでファイルを記述および確認します。ファイルの最後でロックファイルを削除する必要はありません-トラップによって削除されます。これが後で読む人々に役立つことを願っています。
追伸Mikelが既に質問に正しく答えていることはわかりませんでしたが、たとえばCtrl-Cでスクリプトを停止した後にロックファイルが残される可能性を減らすためのtrapコマンドは含まれていませんでした。これが完全なソリューションです
古いロックファイルを処理する単純なアプローチを使用します。
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
Flock(1)を使用するがサブシェルを使用しない例。 flock()edファイル/ tmp/fooが削除されることはありませんが、flock()およびun-flock()edを取得するので問題ありません。
#!/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
プロセスのロックを使用すると、より強力になり、不正な終了も処理します。プロセスが実行されている限り、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
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}
もちろん、このスクリプトには、ロックテストとセット操作が単一のアトミックアクションではないため、同時に開始される可能性が高いプロセスに競合の危険があるという欠陥がまだあります。しかし、mkdirを使用するためにlhunathが提案したこのソリューションには、削除されたスクリプトがディレクトリに残る可能性があるため、他のインスタンスが実行されないという欠陥があります。
セマフォリック ユーティリティは、flock
(上記で説明したように、たとえばpresto8によって)を使用して counting semaphore を実装します。必要な特定の数の同時プロセスを可能にします。さまざまなキューワーカープロセスの同時実行レベルを制限するために使用します。
sem のようなものですが、much軽量です。 (完全な開示:semが私たちのニーズに対して非常に重すぎて、利用可能な単純なカウントセマフォユーティリティがなかったことがわかった後に書きました)
すでに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で実行されているかどうかをチェックします。
これはどこにも言及されていませんが、読み取りを使用します。読み取りが実際にアトミックであるかどうかは正確にはわかりませんが、これまで私に役立ってきました...実装、ロッカーコプロセスを開始し、そのI/Oを使用してロックを管理します。ターゲットI/Oをロッカーファイル記述子からファイルシステムファイル記述子(exec 3<>/file && exec 4</file
)にスワップするだけで、プロセス間で同じことができます。
## gives locks
locker() {
locked=false
while read l; do
case "$l" in
lock)
if $locked; then
echo false
else
locked=true
echo true
fi
;;
unlock)
if $locked; then
locked=false
echo true
else
echo false
fi
;;
*)
echo false
;;
esac
done
}
## locks
lock() {
local response
echo lock >&${locker[1]}
read -ru ${locker[0]} response
$response && return 0 || return 1
}
## unlocks
unlock() {
local response
echo unlock >&${locker[1]}
read -ru ${locker[0]} response
$response && return 0 || return 1
}
群れの道は行く道です。スクリプトが突然死んだときに何が起こるか考えてください。群れの場合、群れを放すだけですが、それは問題ではありません。また、悪意のあるトリックは、スクリプト自体に群れをとることであることに注意してください。
なぜ私たちは次のようなものを使用しないのですか
pgrep -f $cmd || $cmd
早くて汚い?
#!/bin/sh
if [ -f sometempfile ]
echo "Already running... will now terminate."
exit
else
touch sometempfile
fi
..do what you want here..
rm sometempfile
FLOM(Free LOck Manager)をご覧ください http://sourceforge.net/projects/flom/ :コマンドやスクリプトを同期させることができますファイルシステム内のロックファイルを必要としない抽象的なリソース。 NFS(ネットワークファイルシステム)サーバーのようなNAS(ネットワーク接続ストレージ)を使用せずに、異なるシステムで実行されているコマンドを同期できます。
最も単純なユースケースを使用すると、「command1」と「command2」のシリアル化は実行と同じくらい簡単です。
flom -- command1
そして
flom -- command2
2つの異なるシェルスクリプトから。
if [ 1 -ne $(/bin/fuser "$0" 2>/dev/null | wc -w) ]; then
exit 1
fi
この1行の答えは、関連する誰かから来ています buntu 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
MY_FILENAME=`basename "$BASH_SOURCE"`
MY_PROCESS_COUNT=$(ps a -o pid,cmd | grep $MY_FILENAME | grep -v grep | grep -v $$ | wc -
l)
if [ $MY_PROCESS_COUNT -ne 0 ]; then
echo found another process
exit 0
if
# Follows the code to get the job done.
これは、よりエレガントで、フェイルセーフ、迅速です & 汚れた 上記の回答を組み合わせた方法。
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
これも使用できます: https://github.com/sayanarijit/pidlock
Sudo pip install -U pidlock
pidlock -n sleepy_script -c 'sleep 10'
パーティーに遅れて、@ Majalのアイデアを使用して、これはemacsclient GUIの1つのインスタンスのみを開始する私のスクリプトです。これを使用して、同じemacsclientを開くか、ジャンプするようにショートカットキーを設定できます。必要なときに端末でemacsclientを呼び出す別のスクリプトがあります。ここでemacsclientを使用するのは、実際の例を示すためだけであり、他のものを選択できます。このアプローチは、私の小さなスクリプトには十分迅速かつ適切です。汚れている場所を教えてください:)
#!/bin/bash
# if [ $(pgrep -c $(basename $0)) -lt 2 ]; then # this works but requires script name to be unique
if [ $(pidof -x "$0"|wc -w ) -lt 3 ]; then
echo -e "Starting $(basename $0)"
emacsclient --alternate-editor="" -c "$@"
else
echo -e "$0 is running already"
fi