Yanıtlar:
İşte bir lockfile kullanan ve içine bir PID yansıtan bir uygulama . Bu, pidfile kaldırılmadan önce işlem öldürülürse bir koruma görevi görür :
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}
Buradaki hile, kill -0
sinyal vermeyen, ancak verilen PID ile bir işlem olup olmadığını kontrol eder. Ayrıca çağrı çağrısı , işleminiz öldürüldüğünde bile kilit dosyasının kaldırılmasını trap
sağlayacaktır (hariç ).kill -9
flock(1)
Dosya tanımlayıcıda özel bir kapsam kilidi yapmak için kullanın . Bu şekilde komut dosyasının farklı bölümlerini de senkronize edebilirsiniz.
#!/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
Bu olmasını sağlar kod arasındaki o (
ve)
bir seferde bir işlemle sadece çalıştırılır ve süreç çok uzun bir kilit beklemek gelmez.
Uyarı: bu özel komut bir parçasıdır util-linux
. Linux dışında bir işletim sistemi çalıştırırsanız, kullanılabilir olabilir veya olmayabilir.
( command A ) command B
için bir alt kabuk çağırır command A
. Tldp.org/LDP/abs/html/subshells.html adresinde belgelenmiştir .
if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fi
zaman aşımı oluşursa (başka bir işlem dosya kilitli), bu komut dosyası devam ve dosyayı değiştirmek yok. Muhtemelen ... karşıt argüman '10 saniye sürdü ve kilit hala mevcut değilse, asla mevcut olmayacaktır', muhtemelen kilidi tutan işlem sona ermediğinden (belki de çalıştırılıyor olabilir) hata ayıklayıcı altında?).
exit
kısımdan (
)
. Alt işlem sona erdiğinde, kilit otomatik olarak serbest bırakılır, çünkü tutma işlemi yoktur.
"Kilit dosyaları" nın varlığını test eden tüm yaklaşımlar kusurludur.
Neden? Çünkü bir dosyanın var olup olmadığını kontrol etmenin ve tek bir atomik eylemde yaratmanın bir yolu yoktur. Bu nedenle; karşılıklı dışlama molasında teşebbüsler YAPACAK bir yarış koşulu vardır .
Bunun yerine, kullanmanız gerekir mkdir
. mkdir
henüz yoksa bir dizin oluşturur ve varsa bir çıkış kodu ayarlar. Daha da önemlisi, tüm bunları tek bir atomik eylemde yapar ve bu senaryo için mükemmel hale getirir.
if ! mkdir /tmp/myscript.lock 2>/dev/null; then
echo "Myscript is already running." >&2
exit 1
fi
Tüm ayrıntılar için mükemmel BashFAQ'ya bakın: http://mywiki.wooledge.org/BashFAQ/045
Eski kilitlere dikkat etmek istiyorsanız, ısıtıcı (1) işe yarar. Buradaki tek dezavantaj, operasyonun yaklaşık bir saniye sürmesi, bu yüzden anında değil.
Kaynaştırıcı kullanarak sorunu çözen bir kez yazdığım bir fonksiyon:
# 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
}
Şöyle bir komut dosyasında kullanabilirsiniz:
mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }
Taşınabilirliği umursamıyorsanız (bu çözümler hemen hemen her UNIX kutusunda çalışmalıdır), Linux'un kaynaştırıcısı (1) bazı ek seçenekler sunar ve ayrıca sürü (1) vardır .
if ! mkdir
Parçayı, lockdir içinde saklanan PID (başarılı başlatma sırasında) ile gerçekten çalışıp çalışmadığını ve stalenes koruması için komut dosyasıyla aynı olup olmadığını kontrol ederek birleştirebilirsiniz . Bu aynı zamanda yeniden başlatmadan sonra PID'nin tekrar kullanılmasına karşı koruma sağlar ve hatta gerektirmez fuser
.
mkdir
değildir tanımlanan bir atom operasyon olmaya ve bu şekilde "yan etki" dosya sisteminin bir uygulama ayrıntı olduğunu. NFS'nin atomik bir şekilde uygulamadığını söylüyorsa ona tamamen inanıyorum. Yine /tmp
de bir NFS payı olacağınızdan ve muhtemelen mkdir
atomik olarak uygulanan bir fs tarafından sağlanacağından şüphelenmiyorum .
ln
olarak oluşturmanın bir yolu vardır: başka bir dosyadan sabit bir bağlantı oluşturmak için kullanma . Bunu garanti etmeyen garip dosya sistemleriniz varsa, orijinal dosyanın aynı olup olmadığını görmek için daha sonra yeni dosyanın inode'unu kontrol edebilirsiniz.
open(... O_CREAT|O_EXCL)
. Bunun için lockfile-create
(in lockfile-progs
) veya dotlockfile
(in liblockfile-bin
) gibi uygun bir kullanıcı programına ihtiyacınız vardır . Ve düzgün temizlediğinizden (örn. trap EXIT
) Veya eskiyen kilitleri (örneğin ile --use-pid
) test ettiğinizden emin olun .
Flock (2) sistem çağrısının etrafında yaratıcı olmayan bir şekilde flock (1) adı verilen bir sarıcı var. Bu, temizleme vb. İçin endişelenmeden özel kilitleri güvenilir bir şekilde elde etmeyi nispeten kolaylaştırır. Man sayfasında bir kabuk betiğinde nasıl kullanılacağına dair örnekler vardır .
flock()
Sistem çağrısı POSIX değildir ve NFS ayaklar üzerine dosyalar için çalışmaz.
flock -x -n %lock file% -c "%command%"
Yalnızca bir örneğin yürütüldüğünden emin olmak için kullandığım bir Cron işinden kaçıyorum.
Flok gibi atomik bir operasyona ihtiyacınız var, aksi takdirde bu başarısız olur.
Ancak sürü yoksa ne yapmalı. Mkdir var. Bu da atomik bir işlem. Sadece bir işlem başarılı bir mkdir ile sonuçlanır, diğer tüm işlemler başarısız olur.
Yani kod:
if mkdir /var/lock/.myscript.exclusivelock
then
# do stuff
:
rmdir /var/lock/.myscript.exclusivelock
fi
Sen eskiyen kilitleri dikkat çekmek gerekir betiğiniz bir daha asla çalışmayacak bir çökme sonrası.
sleep 10
önce bir ekleyin rmdir
ve tekrar art arda deneyin - hiçbir şey "sızıntı".
Kilitlemeyi güvenilir kılmak için atomik bir işlem yapmanız gerekir. Yukarıdaki tekliflerin çoğu atomik değildir. Önerilen lockfile (1) yardımcı programı, "NFS-dayanıklı" olduğundan bahsettiğimiz man sayfası olarak umut vericidir. İşletim sisteminiz kilit dosyasını (1) desteklemiyorsa ve çözümünüzün NFS üzerinde çalışması gerekiyorsa, çok fazla seçeneğiniz yoktur ....
NFSv2'nin iki atomik işlemi vardır:
NFSv3 ile oluşturma çağrısı da atomiktir.
Dizin işlemleri NFSv2 ve NFSv3 altında atomik DEĞİLDİR (lütfen Brent Callaghan'ın 'NFS Illustrated' kitabına bakın, ISBN 0-201-32570-5; Brent Sun'ta NFS emektarıdır).
Bunu bilerek, dosyalar ve dizinler için spin kilitleri uygulayabilirsiniz (PHP'de değil kabukta):
akım yönünü kilitle:
while ! ln -s . lock; do :; done
bir dosyayı kilitle:
while ! ln -s ${f} ${f}.lock; do :; done
geçerli dir kilidini (varsayım, çalışan süreç gerçekten kilidi aldı):
mv lock deleteme && rm deleteme
bir dosyanın kilidini açın (varsayım, çalışan işlem gerçekten kilidi aldı):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Kaldır da atomik değildir, bu nedenle önce yeniden adlandır (atomiktir) ve sonra kaldırır.
Symlink ve yeniden adlandırma çağrıları için, her iki dosya adının da aynı dosya sisteminde bulunması gerekir. Teklifim: sadece basit dosya adlarını kullanın (yol yok) ve aynı dizine dosya ve kilidi koyun.
lockfile
Varsa kullanır veya symlink
yoksa bu yönteme geri döner .
mv
, rm
), gerektiği rm -f
ziyade, kullanılacak rm
iki süreç P1, P2 yarışıyor durumda? Örneğin, P1 kilidi açma ile başlar mv
, sonra P2 kilitlenir, sonra P2 kilidi açılır (hem mv
ve hem de rm
), sonunda P1 dener rm
ve başarısız olur.
$$
içinde ${f}.deleteme
dosya.
Başka bir seçenek, shell'in noclobber
seçeneğini koşarak kullanmaktır set -C
. Sonra >
dosya zaten varsa başarısız olur.
Kısaca:
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
Bu, kabuğun çağrılmasına neden olur:
open(pathname, O_CREAT|O_EXCL)
Bu, atomik olarak dosyayı oluşturur veya dosya zaten varsa başarısız olur.
BashFAQ 045 hakkındaki bir yoruma göre , bu başarısız olabilir ksh88
, ancak tüm kabuklarımda çalışır:
$ 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
Bayrağı pdksh
ekleyen ilginç O_TRUNC
, ama açıkçası gereksiz:
boş bir dosya oluşturuyorsunuz veya hiçbir şey yapmıyorsunuz.
Nasıl yapacağınız, rm
kirli çıkışların nasıl işlenmesini istediğinize bağlıdır.
Temiz çıkışta sil
Temiz olmayan çıkışa neden olan sorun çözülene ve kilit dosyası el ile kaldırılıncaya kadar yeni işlemler başarısız olur.
# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"
Herhangi bir çıkışta sil
Komut dosyası zaten çalışmıyorsa yeni çalışmalar başarılı olur.
trap 'rm "$lockfile"' EXIT
Bunun GNU Parallel
için olarak adlandırıldığında bir muteks olarak çalıştığı için kullanabilirsiniz sem
. Yani, somut terimlerle, şunları kullanabilirsiniz:
sem --id SCRIPTSINGLETON yourScript
Siz de bir zaman aşımı istiyorsanız, şunu kullanın:
sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript
<0 zaman aşımı, semafor zaman aşımı içinde serbest bırakılmazsa komut dosyası çalıştırmadan çıkış anlamına gelir,> 0 zaman aşımı komut dosyasını yine de çalıştırmak anlamına gelir.
--id
Ona, kontrol terminaline varsayılan olarak başka bir ad vermeniz gerektiğini unutmayın .
GNU Parallel
Linux / OSX / Unix platformlarının çoğunda çok basit bir kurulumdur - sadece bir Perl betiğidir.
sem
İlgili soru hakkında daha fazla bilgi unix.stackexchange.com/a/322200/199525 .
Kabuk betikleri için, kilitleri daha taşınabilir hale getirdiği için mkdir
üstünden geçme eğilimindeyim flock
.
Her iki durumda da kullanmak set -e
yeterli değildir. Bu komut dosyasından yalnızca bir komut başarısız olursa çıkar. Kilitleriniz hala geride kalacak.
Düzgün kilit temizliği için, tuzaklarınızı gerçekten bu psuedo kodu (kaldırılmış, basitleştirilmiş ve test edilmemiş ancak aktif olarak kullanılan komut dosyalarından) gibi bir şeye ayarlamanız gerekir:
#=======================================================================
# 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
İşte olacak. Tüm tuzaklar bir çıkış üretecek, böylece fonksiyon __sig_exit
her zaman gerçekleşecek (bir SIGKILL'i engelliyor), bu da kilitlerinizi temizler.
Not: Çıkış değerlerim düşük değerler değil. Neden? Çeşitli toplu işlem sistemleri 0 ile 31 arasındaki sayıları yapar veya beklentileri vardır. Bunları başka bir şeye ayarlayarak, komut dosyalarımın ve toplu akışlarımın önceki toplu iş veya komut dosyasına uygun şekilde tepki vermesini sağlayabilirim.
rm -r $LOCK_DIR
veya gerektiğinde zorlamalıdırlar (göreceli çizik dosyalarını tutma gibi özel durumlarda da yaptığım gibi). Şerefe.
exit 1002
?
Gerçekten hızlı ve gerçekten kirli mi? Betiğinizin üst kısmındaki bu tek satırlı çalışma:
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
Elbette, komut dosyası adınızın benzersiz olduğundan emin olun. :)
-gt 2
? grep her zaman ps sonucunda kendini bulamaz!
pgrep
POSIX'te değil. Bunu taşınabilir olarak çalıştırmak istiyorsanız, POSIX'e ihtiyacınız var ps
ve çıktısını işliyorsunuz .
-c
mevcut değil, kullanmanız gerekecek | wc -l
. Sayı karşılaştırması hakkında: -gt 1
ilk örnek kendini gördüğünden beri kontrol edilir.
İşte atom dizini kilitlemesini PID üzerinden eski kilit kontrolü ile birleştiren ve eskiyse yeniden başlatan bir yaklaşım. Ayrıca, bu herhangi bir bashisms'e dayanmaz.
#!/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
Bu örnek adam sürüsünde açıklanmıştır, ancak bazı iyileştirmelere ihtiyaç vardır, çünkü hataları ve çıkış kodlarını yönetmeliyiz:
#!/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
Başka bir yöntem kullanabilir, geçmişte kullandığım işlemleri listeleyebilirsiniz. Ancak bu, yukarıdaki yöntemden daha karmaşıktır. Ps ile süreçleri listelemek, adına göre filtre, parazit kaldırmak için ek filtre grep -v grep nihayet grep -c ile saymak. ve sayı ile karşılaştırın. Karmaşık ve belirsiz
Gönderilen mevcut yanıtlar ya CLI yardımcı programına dayanır flock
ya da kilit dosyasını düzgün şekilde korumaz. Flock yardımcı programı tüm Linux dışı sistemlerde (yani FreeBSD) kullanılamaz ve NFS'de düzgün çalışmaz.
Sistem yönetiminin ve sistem geliştirmenin ilk günlerinde, kilit dosyası oluşturmak için güvenli ve nispeten taşınabilir bir yöntemin, mkemp(3)
veya kullanarak geçici bir dosya oluşturmak olduğu söylendi.mkemp(1)
, geçici dosya (yani PID) için tanımlayıcı bilgileri yazma, daha sonra sabit bağlantı geçici dosyayı kilit dosyasına. Bağlantı başarılı olursa, kilidi başarıyla elde ettiniz.
Kabuk komut dosyalarında kilitleri kullanırken, genellikle obtain_lock()
paylaşılan bir profile bir işlev yerleştirir ve komut dosyalarından kaynaklanırım. Aşağıda kilit işlevime bir örnek verilmiştir:
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;
};
Kilit işlevinin nasıl kullanılacağına ilişkin bir örnek aşağıdadır:
#!/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
Sesli aramayı unutmayın clean_up
Senaryonuzdaki herhangi bir çıkış noktasını .
Yukarıdakileri hem Linux hem de FreeBSD ortamlarında kullandım.
Bir Debian makinesini hedeflerken lockfile-progs
paketi iyi bir çözüm olarak görüyorum . procmail
Ayrıca bir lockfile
araç ile geliyor . Ancak bazen bunlardan hiçbirine takılı kalmam.
İşte mkdir
eski kilitleri tespit etmek için atomiklik ve bir PID dosyası için kullanılan çözümüm . Bu kod şu anda bir Cygwin kurulumunda üretilmektedir ve iyi çalışır.
Kullanmak exclusive_lock_require
için bir şeye özel erişim almanız gerektiğinde arayın . İsteğe bağlı bir kilit adı parametresi, kilitleri farklı komut dosyaları arasında paylaşmanıza olanak tanır. Daha karmaşık bir şeye ihtiyacınız varsa, iki alt seviye işlevi de vardır ( exclusive_lock_try
ve 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
}
Eğer bu iş parçacığının başka bir yerinde tarif edilmiş olan flok sınırlamaları sizin için bir sorun değilse, bu işe yarayacaktır:
#!/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
geçecekexit 1
Bazı unixler, lockfile
daha önce bahsedilenlere çok benzer flock
.
Manpage'den:
lockfile bir veya daha fazla semafor dosyası oluşturmak için kullanılabilir. Kilit dosyası belirtilen tüm dosyaları (belirtilen sırada) oluşturamazsa, uyku süresi (varsayılan değer 8'dir) saniye bekler ve başarısız olan son dosyayı yeniden dener. Hata döndürülünceye kadar yapılacak yeniden deneme sayısını belirleyebilirsiniz. Yeniden deneme sayısı -1 ise (varsayılan, yani -r-1) lockfile sonsuza kadar yeniden dener.
lockfile
yardımcı oluruz ??
lockfile
ile dağıtılır procmail
. Ayrıca paketle dotlockfile
birlikte gelen bir alternatif var liblockfile
. İkisi de NFS üzerinde güvenilir bir şekilde çalıştıklarını iddia ediyorlar.
Aslında bmdhacks'ın cevabı neredeyse iyi olsa da, ikinci betiğin önce kilit dosyasını kontrol ettikten ve yazmadan önce çalıştırma şansı azdır. Yani her ikisi de kilit dosyasını yazacak ve her ikisi de çalışıyor olacak. Bunu nasıl çalıştıracağınız aşağıda açıklanmıştır:
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
Seçenek emin dosyası zaten varsa yönlendirme komutunun başarısız olacağını yapacaktır. Yönlendirme komutu aslında atomiktir - dosyayı bir komutla yazar ve kontrol edersiniz. Dosyanın sonundaki kilit dosyasını kaldırmanıza gerek yoktur - tuzak tarafından kaldırılır. Umarım bu daha sonra okuyacak insanlara yardımcı olur.
PS Mikel'in soruyu doğru cevapladığını görmedim, ancak komut dosyasının Ctrl-C ile durdurulduktan sonra kilit dosyasının bırakılma olasılığını azaltmak için tuzak komutunu içermedi. İşte bu tam çözüm
Lockfiles, lockdirs, özel kilitleme programları ve pidof
tüm Linux kurulumlarında bulunmadığı için ortadan kaldırmak istedim . Ayrıca mümkün olan en basit koda (veya en azından mümkün olduğunca az satıra) sahip olmak istedim. if
Tek satırda en basit ifade:
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; }'
Eski kilit dosyalarını işleyen basit bir yaklaşım kullanıyorum.
Pid'i saklayan yukarıdaki çözümlerin bazılarının, pid'in etrafına sarılabileceğini göz ardı ettiğini unutmayın. Bu nedenle, saklanan pid ile geçerli bir işlem olup olmadığını kontrol etmek, özellikle uzun süre çalışan komut dosyaları için yeterli değildir.
Bir kerede yalnızca bir komut dosyasının açılıp yazabileceğinden emin olmak için noclobber kullanıyorum. Ayrıca, kilit dosyasındaki bir işlemi benzersiz olarak tanımlamak için yeterli bilgi saklıyorum. Ben pid, ppid, lstart olmak üzere bir süreci benzersiz olarak tanımlamak için veri kümesini tanımlarım.
Yeni bir komut dosyası başlatıldığında, kilit dosyasını oluşturamazsa, kilit dosyasını oluşturan işlemin hala etrafta olduğunu doğrular. Değilse, orijinal sürecin nankör bir ölümle öldüğünü ve eski bir kilit dosyası bıraktığını varsayıyoruz. Yeni komut dosyası daha sonra kilit dosyasının sahipliğini alır ve her şey yolundadır.
Birden fazla platformda birden fazla kabukla çalışmalıdır. Hızlı, taşınabilir ve basit.
#!/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
Bu satırı komut dosyanızın başına ekleyin
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
Bu adam sürüsünden kaynak kodudur.
Daha fazla günlük kaydı istiyorsanız, bunu kullanın
[ "${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."
Bu, flock
yardımcı programı kullanarak kilitleri ayarlar ve denetler . Bu kod, FLOCKER değişkenini kontrol ederek ilk kez çalıştırılıp çalıştırılmadığını algılar, komut dosyası adına ayarlanmadıysa, flock kullanarak ve FLOCKER değişkeni başlatılırken komut dosyasını tekrar tekrar başlatmaya çalışır, ardından FLOCKER doğru ayarlanmışsa, önceki yinelemede akın başarılı oldu ve devam etmek sorun değil. Kilit meşgulse, yapılandırılabilir çıkış koduyla başarısız olur.
Debian 7 üzerinde çalışmıyor gibi görünüyor, ancak deneysel util-linux 2.25 paketiyle tekrar çalışıyor gibi görünüyor. "Flock: ... Metin dosyası meşgul" yazar. Komut dosyanızdaki yazma iznini devre dışı bırakarak geçersiz kılınabilir.
PID ve kilit dosyaları kesinlikle en güvenilir olanlardır. Programı çalıştırmayı denediğinizde, hangi kilit dosyasının bulunup bulunmadığını kontrol edebilir ve varsa ps
sürecin hala çalışıp çalışmadığını görmek için kullanabilir . Değilse, komut dosyası başlatılabilir ve kilit dosyasındaki PID'yi kendine günceller.
En azından kullanım durumum için bmdhack'ın çözümünün en pratik olduğunu düşünüyorum. Flock ve lockfile kullanmak, komut dosyası sona erdiğinde her zaman garanti edilemeyen rm kullanarak lockfile'ın kaldırılmasına dayanır (ör. -9 öldür).
Ben bmdhack'ın çözümü ile ilgili küçük bir şeyi değiştirirdim: Bu, bu semaforun güvenli çalışması için gereksiz olduğunu belirtmeden kilit dosyasını kaldırmanın bir noktasını oluşturuyor. Kill -0'ı kullanması, ölü bir işlem için eski bir kilit dosyasının yok sayılmasını / üzerine yazılmasını sağlar.
Bu nedenle basitleştirilmiş çözümüm, singletonunuzun üstüne aşağıdakileri eklemektir:
## 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}
Tabii ki, bu komut dosyası hala aynı anda başlaması muhtemel süreçlerin bir yarış tehlikesine sahip olduğu kusuruna sahiptir, çünkü kilit testi ve ayar işlemleri tek bir atomik eylem değildir. Ancak lhunath tarafından mkdir kullanmak için önerilen çözüm, öldürülen bir komut dosyasının dizinin arkasında bırakabileceği ve böylece diğer örneklerin çalışmasını engelleyebileceği kusuruna sahiptir.
Semaphoric yardımcı kullanımları flock
(şekilde presto8 göre, örneğin yukarıda ele alınan) bir uygulamaya sayma semaforu . İstediğiniz belirli sayıda eşzamanlı işlemi etkinleştirir. Bunu, çeşitli kuyruk işçi işlemlerinin eşzamanlılık düzeyini sınırlamak için kullanırız.
Sem gibi ama çok daha hafif. (Tam açıklama: Sem'in ihtiyaçlarımız için çok ağır olduğunu ve basit bir sayım semaforu bulunmadığını bulduktan sonra yazdım.)
Flok (1) içeren ancak alt kabuğu olmayan bir örnek. flock () ed dosyası / tmp / foo hiçbir zaman kaldırılmaz, ancak flock () ve un-flock () ed sürümlerinin alınması önemli değildir.
#!/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
Dış bağımlılıklara ihtiyaç duymadan milyonlarca kez cevap verdim, ancak başka bir yol:
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
Geçerli PID'yi ($$) kilit dosyasına her yazdığında ve komut dosyası başlatıldığında bir işlemin en son PID ile çalışıp çalışmadığını kontrol eder.
Sürecin kilidini kullanmak çok daha güçlüdür ve nankör çıkışları da halleder. süreç çalıştığı sürece lock_file açık tutulur. Süreç gerçekleştikten sonra (kabukla) kapatılacaktır (öldürülse bile). Bunu çok verimli buldum:
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
Ben oneliner @ betiğin en başında kullanıyorum:
#!/bin/bash
if [[ $(pgrep -afc "$(basename "$0")") -gt "1" ]]; then echo "Another instance of "$0" has already been started!" && exit; fi
.
the_beginning_of_actual_script
İşlemin bellekte varlığını görmek iyidir (işlemin durumu ne olursa olsun); ama bu işi benim için yapıyor.
Sürü yolu gitmek için bir yoldur. Senaryo aniden öldüğünde ne olacağını düşünün. Sürü durumunda sadece sürüyü kaybedersiniz, ancak bu bir sorun değildir. Ayrıca, kötü bir hile komut dosyasının kendisi bir sürü almak olduğunu unutmayın .. ama elbette tam buhar ileriye izin sorunları içine çalıştırmak sağlar.
Hızlı ve kirli?
#!/bin/sh
if [ -f sometempfile ]
echo "Already running... will now terminate."
exit
else
touch sometempfile
fi
..do what you want here..
rm sometempfile