Jawaban:
Berikut ini adalah implementasi yang menggunakan lockfile dan gema PID ke dalamnya. Ini berfungsi sebagai perlindungan jika prosesnya dibunuh sebelum mengeluarkan 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}
Kuncinya di sini adalah kill -0
yang tidak memberikan sinyal apa pun tetapi hanya memeriksa apakah ada proses dengan PID yang diberikan. Juga panggilan ke trap
akan memastikan bahwa lockfile dihapus bahkan ketika proses Anda terbunuh (kecuali kill -9
).
Menggunakan flock(1)
untuk membuat kunci scoped eksklusif pada deskriptor file. Dengan cara ini Anda bahkan dapat menyinkronkan berbagai bagian skrip.
#!/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
Ini memastikan bahwa kode antara (
dan )
dijalankan hanya oleh satu proses pada satu waktu dan bahwa proses tidak menunggu terlalu lama untuk kunci.
Peringatan: perintah khusus ini adalah bagian dari util-linux
. Jika Anda menjalankan sistem operasi selain Linux, mungkin atau mungkin tidak tersedia.
( command A ) command B
memanggil subshell untuk command A
. Didokumentasikan di tldp.org/LDP/abs/html/subshells.html . Saya masih tidak yakin tentang waktu doa subkulit dan perintah B.
if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fi
sehingga jika batas waktu terjadi (beberapa proses lain file terkunci), skrip ini tidak melanjutkan dan memodifikasi file. Mungkin ... argumen tandingannya adalah 'tetapi jika perlu 10 detik dan kunci masih tidak tersedia, itu tidak akan pernah tersedia', mungkin karena proses memegang kunci tidak berakhir (mungkin sedang dijalankan di bawah debugger?).
exit
adalah dari bagian dalam (
)
. Ketika subproses berakhir, kunci dilepaskan secara otomatis, karena tidak ada proses menahannya.
Semua pendekatan yang menguji keberadaan "file kunci" cacat.
Mengapa? Karena tidak ada cara untuk memeriksa apakah suatu file ada dan membuatnya dalam satu aksi atom. Karena ini; ada kondisi perlombaan yang AKAN membuat upaya Anda untuk saling menyingkirkan pengecualian.
Sebagai gantinya, Anda perlu menggunakan mkdir
. mkdir
membuat direktori jika belum ada, dan jika itu ada, ia menetapkan kode keluar. Lebih penting lagi, ia melakukan semua ini dalam aksi atom tunggal sehingga sempurna untuk skenario ini.
if ! mkdir /tmp/myscript.lock 2>/dev/null; then
echo "Myscript is already running." >&2
exit 1
fi
Untuk semua detail, lihat BashFAQ yang luar biasa: http://mywiki.wooledge.org/BashFAQ/045
Jika Anda ingin merawat kunci basi, fuser (1) berguna. Satu-satunya downside di sini adalah bahwa operasi memakan waktu sekitar satu detik, jadi itu tidak instan.
Inilah fungsi yang saya tulis sekali yang menyelesaikan masalah menggunakan fuser:
# 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
}
Anda dapat menggunakannya dalam skrip seperti:
mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }
Jika Anda tidak peduli tentang portabilitas (solusi ini harus bekerja pada hampir semua kotak UNIX), Linux fuser (1) menawarkan beberapa opsi tambahan dan ada juga kawanan (1) .
if ! mkdir
bagian dengan memeriksa apakah proses dengan PID disimpan (pada startup yang sukses) di dalam lockdir benar-benar berjalan dan identik dengan skrip untuk perlindungan stalen. Ini juga akan melindungi terhadap penggunaan kembali PID setelah reboot, dan bahkan tidak memerlukan fuser
.
mkdir
tidak didefinisikan sebagai operasi atom dan karena itu "efek samping" adalah detail implementasi dari sistem file. Saya sepenuhnya percaya padanya jika dia mengatakan NFS tidak mengimplementasikannya dengan cara atom. Meskipun saya tidak curiga Anda /tmp
akan menjadi bagian NFS dan kemungkinan akan disediakan oleh fs yang mengimplementasikan mkdir
secara atom.
ln
untuk membuat tautan keras dari file lain. Jika Anda memiliki sistem file aneh yang tidak menjamin itu, Anda dapat memeriksa inode file baru setelahnya untuk melihat apakah itu sama dengan file asli.
open(... O_CREAT|O_EXCL)
. Anda hanya perlu program pengguna yang sesuai untuk melakukannya, seperti lockfile-create
(dalam lockfile-progs
) atau dotlockfile
(dalam liblockfile-bin
). Dan pastikan Anda membersihkan dengan benar (misalnya trap EXIT
), atau menguji untuk kunci basi (misalnya dengan --use-pid
).
Ada pembungkus di sekitar kawanan (2) panggilan sistem yang disebut, tidak terbayangkan, kawanan (1). Ini membuatnya relatif mudah untuk mendapatkan kunci eksklusif tanpa khawatir tentang pembersihan dll. Ada contoh di halaman manual tentang bagaimana menggunakannya dalam skrip shell.
flock()
system call tidak POSIX dan tidak bekerja untuk file di NFS.
flock -x -n %lock file% -c "%command%"
untuk memastikan hanya satu instance yang pernah dijalankan.
Anda memerlukan operasi atom, seperti kawanan domba, kalau tidak ini akan gagal.
Tetapi apa yang harus dilakukan jika kawanan tidak tersedia. Yah ada mkdir. Itu operasi atom juga. Hanya satu proses yang akan menghasilkan mkdir yang berhasil, semua yang lain akan gagal.
Jadi kodenya adalah:
if mkdir /var/lock/.myscript.exclusivelock
then
# do stuff
:
rmdir /var/lock/.myscript.exclusivelock
fi
Anda perlu menjaga kunci basi jika tidak crash skrip Anda tidak akan pernah berjalan lagi.
sleep 10
sebelum rmdir
dan coba cascade lagi - tidak ada yang akan "bocor".
Untuk membuat penguncian dapat diandalkan, Anda memerlukan operasi atom. Banyak dari proposal di atas tidak bersifat atom. Utilitas lockfile (1) yang diusulkan terlihat menjanjikan seperti halaman manual yang disebutkan, bahwa "NFS-resistant". Jika OS Anda tidak mendukung lockfile (1) dan solusi Anda harus bekerja pada NFS, Anda tidak memiliki banyak pilihan ....
NFSv2 memiliki dua operasi atom:
Dengan NFSv3, panggilan buat juga atom.
Operasi direktori BUKAN atom di bawah NFSv2 dan NFSv3 (lihat buku 'NFS Illustrated' oleh Brent Callaghan, ISBN 0-201-32570-5; Brent adalah veteran NFS di Sun).
Mengetahui hal ini, Anda dapat menerapkan spin-lock untuk file dan direktori (di shell, bukan PHP):
kunci dir saat ini:
while ! ln -s . lock; do :; done
mengunci file:
while ! ln -s ${f} ${f}.lock; do :; done
membuka kunci dir saat ini (asumsi, proses yang berjalan benar-benar mendapatkan kunci):
mv lock deleteme && rm deleteme
membuka kunci file (asumsi, proses yang berjalan benar-benar mendapatkan kunci):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Buang juga bukan atom, oleh karena itu pertama ganti nama (yang merupakan atom) dan kemudian hapus.
Untuk symlink dan rename panggilan, kedua nama file harus berada pada sistem file yang sama. Proposal saya: hanya menggunakan nama file sederhana (tidak ada jalur) dan meletakkan file dan mengunci ke direktori yang sama.
lockfile
jika tersedia, atau mundur ke symlink
metode ini jika tidak.
mv
, rm
), harus rm -f
digunakan, daripada rm
dalam kasus dua proses P1, P2 balap? Sebagai contoh, P1 mulai membuka dengan mv
, kemudian P2 mengunci, lalu P2 membuka kunci (keduanya mv
dan rm
), akhirnya P1 mencoba rm
dan gagal.
$$
dalam ${f}.deleteme
nama file.
Opsi lain adalah menggunakan noclobber
opsi shell dengan menjalankan set -C
. Kemudian>
akan gagal jika file tersebut sudah ada.
Secara singkat:
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
Ini menyebabkan shell memanggil:
open(pathname, O_CREAT|O_EXCL)
yang secara atom menciptakan file atau gagal jika file tersebut sudah ada.
Menurut komentar di BashFAQ 045 , ini mungkin gagal ksh88
, tetapi berfungsi di semua shell saya:
$ 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
Menarik yang pdksh
menambahkan O_TRUNC
bendera, tetapi jelas berlebihan:
Anda membuat file kosong, atau tidak melakukan apa-apa.
Cara Anda melakukannya rm
tergantung pada bagaimana Anda ingin pintu keluar yang tidak bersih ditangani.
Hapus saat keluar bersih
Menjalankan baru gagal sampai masalah yang menyebabkan keluarnya yang tidak bersih untuk diselesaikan dan lockfile dihapus secara manual.
# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"
Hapus kapan saja
Menjalankan baru berhasil asalkan skrip belum berjalan.
trap 'rm "$lockfile"' EXIT
Anda dapat menggunakan GNU Parallel
ini karena berfungsi sebagai mutex ketika dipanggil sebagai sem
. Jadi, secara konkret, Anda dapat menggunakan:
sem --id SCRIPTSINGLETON yourScript
Jika Anda ingin waktu habis juga, gunakan:
sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript
Batas waktu <0 berarti keluar tanpa menjalankan skrip jika semaphore tidak dirilis dalam batas waktu, batas waktu> 0 berarti tetap menjalankan skrip.
Perhatikan bahwa Anda harus memberi nama (dengan --id
) jika tidak default ke terminal pengendali.
GNU Parallel
adalah instalasi yang sangat sederhana di kebanyakan platform Linux / OSX / Unix - ini hanya skrip Perl.
sem
di pertanyaan terkait unix.stackexchange.com/a/322200/199525 .
Untuk skrip shell, saya cenderung menggunakan yang mkdir
lebih flock
karena membuat kunci lebih portabel.
Either way, menggunakan set -e
tidak cukup. Itu hanya keluar dari skrip jika ada perintah yang gagal. Kunci Anda masih akan tertinggal.
Untuk pembersihan kunci yang benar, Anda benar-benar harus mengatur jebakan Anda ke sesuatu seperti kode psuedo ini (diangkat, disederhanakan, dan belum teruji tetapi dari skrip yang digunakan secara aktif):
#=======================================================================
# 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
Inilah yang akan terjadi. Semua jebakan akan menghasilkan jalan keluar sehingga fungsi tersebut __sig_exit
akan selalu terjadi (kecuali SIGKILL) yang membersihkan kunci Anda.
Catatan: nilai keluar saya bukan nilai rendah. Mengapa? Berbagai sistem pemrosesan batch membuat atau memiliki harapan angka 0 hingga 31. Mengaturnya ke hal lain, saya dapat membuat skrip dan aliran batch saya bereaksi sesuai dengan pekerjaan atau skrip batch sebelumnya.
rm -r $LOCK_DIR
atau bahkan memaksanya seperlunya (seperti yang telah saya lakukan juga dalam kasus-kasus khusus seperti memegang file awal relatif). Bersulang.
exit 1002
?
Sangat cepat dan sangat kotor? Satu baris ini di bagian atas skrip Anda akan berfungsi:
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
Tentu saja, pastikan nama skrip Anda unik. :)
-gt 2
? grep tidak selalu menemukan dirinya di hasil ps!
pgrep
tidak dalam POSIX. Jika Anda ingin ini berfungsi dengan baik, Anda perlu POSIX ps
dan memproses hasilnya.
-c
tidak ada, Anda harus menggunakan | wc -l
. Tentang perbandingan angka: -gt 1
diperiksa sejak instance pertama melihat dirinya sendiri.
Berikut adalah pendekatan yang menggabungkan penguncian direktori atom dengan tanda centang untuk kunci basi melalui PID dan mulai ulang jika basi. Juga, ini tidak bergantung pada bashisme apa pun.
#!/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
Contoh ini dijelaskan dalam man flock, tetapi perlu beberapa perbaikan, karena kita harus mengelola bug dan kode keluar:
#!/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
Anda dapat menggunakan metode lain, daftar proses yang saya gunakan di masa lalu. Tetapi ini lebih rumit dari metode di atas. Anda harus mendaftar proses dengan ps, filter dengan namanya, filter grep tambahan -v grep untuk menghapus parasit dan akhirnya hitung dengan grep -c. dan bandingkan dengan angka. Rumit dan tidak pasti
Jawaban yang ada diposting baik mengandalkan utilitas CLI flock
atau tidak mengunci file kunci dengan benar. Utilitas kawanan tidak tersedia di semua sistem non-Linux (yaitu FreeBSD), dan tidak berfungsi dengan baik pada NFS.
Pada hari-hari awal saya administrasi sistem dan pengembangan sistem, saya diberitahu bahwa metode yang aman dan relatif portabel untuk membuat file kunci adalah membuat file temp menggunakan mkemp(3)
ataumkemp(1)
, menulis informasi pengidentifikasi ke file temp (yaitu PID), kemudian hard link file temp ke file kunci. Jika tautan berhasil, maka Anda telah berhasil mendapatkan kunci.
Saat menggunakan kunci dalam skrip shell, saya biasanya menempatkan suatu obtain_lock()
fungsi di profil bersama dan kemudian mengambilnya dari skrip. Di bawah ini adalah contoh dari fungsi kunci saya:
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;
};
Berikut ini adalah contoh cara menggunakan fungsi kunci:
#!/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
Ingatlah untuk menelepon clean_up
di titik keluar apa pun dalam skrip Anda.
Saya telah menggunakan hal di atas di lingkungan Linux dan FreeBSD.
Saat menargetkan mesin Debian saya menemukan lockfile-progs
paket menjadi solusi yang baik. procmail
juga dilengkapi dengan lockfile
alat. Namun kadang-kadang saya terjebak dengan keduanya.
Inilah solusi saya yang menggunakan mkdir
untuk atom-ness dan file PID untuk mendeteksi kunci basi. Kode ini saat ini sedang diproduksi pada pengaturan Cygwin dan berfungsi dengan baik.
Untuk menggunakannya cukup menelepon exclusive_lock_require
ketika Anda perlu mendapatkan akses eksklusif ke sesuatu. Parameter nama kunci opsional memungkinkan Anda berbagi kunci antara skrip yang berbeda. Ada juga dua fungsi level yang lebih rendah ( exclusive_lock_try
dan exclusive_lock_retry
) jika Anda membutuhkan sesuatu yang lebih kompleks.
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
}
Jika batasan kawanan, yang telah dijelaskan di tempat lain di utas ini, bukan masalah bagi Anda, maka ini harusnya berfungsi:
#!/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
akan exit 1
segera jika tidak bisa mendapatkan kunci
Beberapa unix memiliki lockfile
yang sangat mirip dengan yang telah disebutkan flock
.
Dari halaman manual:
lockfile dapat digunakan untuk membuat satu atau beberapa file semaphore. Jika file kunci tidak dapat membuat semua file yang ditentukan (dalam urutan yang ditentukan), itu menunggu sleeptime (default ke 8) detik dan mencoba lagi file terakhir yang tidak berhasil. Anda dapat menentukan jumlah percobaan yang harus dilakukan sampai kegagalan dikembalikan. Jika jumlah coba lagi adalah -1 (default, yaitu -r-1) lockfile akan coba lagi selamanya.
lockfile
utilitasnya ??
lockfile
didistribusikan bersama procmail
. Juga ada alternatif dotlockfile
yang sesuai dengan liblockfile
paket. Mereka berdua mengklaim dapat bekerja dengan andal pada NFS.
Sebenarnya walaupun jawaban bmdhacks hampir bagus, ada sedikit kemungkinan skrip kedua dijalankan setelah pertama kali memeriksa lockfile dan sebelum ia menulisnya. Jadi mereka berdua akan menulis file kunci dan mereka berdua akan berjalan. Berikut ini cara membuatnya pasti:
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
Itu noclobber
pilihan akan memastikan bahwa perintah redirect akan gagal jika file sudah ada. Jadi perintah pengalihan sebenarnya atom - Anda menulis dan memeriksa file dengan satu perintah. Anda tidak perlu menghapus file kunci di akhir file - itu akan dihapus oleh perangkap. Saya harap ini membantu orang-orang yang akan membacanya nanti.
PS Saya tidak melihat bahwa Mikel sudah menjawab pertanyaan dengan benar, meskipun ia tidak memasukkan perintah trap untuk mengurangi kemungkinan file kunci akan ditinggalkan setelah menghentikan skrip dengan Ctrl-C misalnya. Jadi ini solusi lengkapnya
Saya ingin menghapus lockfile, lockdirs, program penguncian khusus dan bahkan pidof
karena tidak ditemukan di semua instalasi Linux. Juga ingin memiliki kode sesederhana mungkin (atau setidaknya beberapa baris mungkin). if
Pernyataan paling sederhana , dalam satu baris:
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; }'
Saya menggunakan pendekatan sederhana yang menangani file kunci basi.
Perhatikan bahwa beberapa solusi di atas yang menyimpan pid, abaikan fakta bahwa pid dapat membungkus. Jadi - hanya memeriksa apakah ada proses yang valid dengan pid yang disimpan tidak cukup, terutama untuk skrip yang berjalan lama.
Saya menggunakan noclobber untuk memastikan hanya satu skrip yang dapat membuka dan menulis ke file kunci pada satu waktu. Selanjutnya, saya menyimpan informasi yang cukup untuk mengidentifikasi secara unik suatu proses di lockfile. Saya mendefinisikan set data untuk secara unik mengidentifikasi suatu proses menjadi pid, ppid, lstart.
Ketika skrip baru dimulai, jika gagal membuat file kunci, itu kemudian memverifikasi bahwa proses yang membuat file kunci masih ada. Jika tidak, kami menganggap proses asli meninggal karena kematian yang tidak dapat dilacak, dan meninggalkan file kunci basi. Script baru kemudian mengambil kepemilikan file kunci, dan semuanya baik-baik saja di dunia.
Harus bekerja dengan banyak shell di berbagai platform. Cepat, portabel, dan sederhana.
#!/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
Tambahkan baris ini di awal skrip Anda
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
Ini kode boilerplate dari kawanan manusia.
Jika Anda ingin lebih banyak login, gunakan yang ini
[ "${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."
Ini mengatur dan memeriksa kunci menggunakan flock
utilitas. Kode ini mendeteksi jika dijalankan pertama kali dengan memeriksa variabel FLOCKER, jika tidak disetel ke nama skrip, maka ia mencoba memulai skrip lagi menggunakan kawanan secara acak dan dengan variabel FLOCKER diinisialisasi, jika FLOCKER diatur dengan benar, kemudian kawanan pada iterasi sebelumnya berhasil dan tidak apa-apa untuk melanjutkan. Jika kunci sibuk, gagal dengan kode keluar yang dapat dikonfigurasi.
Tampaknya tidak berfungsi pada Debian 7, tetapi tampaknya bekerja kembali dengan paket util-linux 2.25 eksperimental. Itu menulis "kawanan: ... File teks sibuk". Ini bisa diganti dengan menonaktifkan izin menulis pada skrip Anda.
PID dan file kunci jelas yang paling dapat diandalkan. Ketika Anda mencoba menjalankan program, ia dapat memeriksa file kunci mana dan jika ada, itu dapat digunakan ps
untuk melihat apakah prosesnya masih berjalan. Jika tidak, skrip dapat mulai, memperbarui PID di lockfile sendiri.
Saya menemukan bahwa solusi bmdhack adalah yang paling praktis, setidaknya untuk kasus penggunaan saya. Menggunakan flock dan lockfile mengandalkan penghapusan lockfile menggunakan rm ketika skrip berakhir, yang tidak selalu dapat dijamin (misalnya, kill -9).
Saya akan mengubah satu hal kecil tentang solusi bmdhack: Ini membuat titik menghapus file kunci, tanpa menyatakan bahwa ini tidak perlu untuk kerja semaphore yang aman ini. Penggunaannya untuk kill -0 memastikan bahwa file lockfile lama untuk proses mati hanya akan diabaikan / ditulis berlebihan.
Oleh karena itu solusi saya yang disederhanakan adalah dengan menambahkan berikut ini di bagian atas singleton Anda:
## 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}
Tentu saja, skrip ini masih memiliki cacat yang prosesnya cenderung dimulai pada saat yang sama memiliki bahaya ras, karena uji penguncian dan operasi yang ditetapkan bukan aksi atom tunggal. Tetapi solusi yang diusulkan untuk ini oleh lhunath untuk menggunakan mkdir memiliki kelemahan bahwa skrip yang terbunuh dapat meninggalkan direktori, sehingga mencegah instance lain dari berjalan.
The semaphoric menggunakan utilitas flock
(seperti dibahas di atas, misalnya dengan presto8) untuk menerapkan penghitungan semaphore . Ini memungkinkan sejumlah proses bersamaan yang Anda inginkan. Kami menggunakannya untuk membatasi tingkat konkurensi dari berbagai proses pekerja antrian.
Ini seperti sem tapi jauh lebih ringan-berat. (Pengungkapan penuh: Saya menulisnya setelah menemukan sem terlalu berat untuk kebutuhan kita dan tidak ada utilitas semafor penghitungan yang tersedia.)
Contoh dengan kawanan (1) tetapi tanpa subkulit. flock () ed file / tmp / foo tidak pernah dihapus, tetapi itu tidak masalah karena mendapat flock () dan 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
Menjawab sejuta kali, tetapi dengan cara lain, tanpa perlu ketergantungan eksternal:
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
Setiap kali ia menulis PID saat ini ($$) ke dalam lockfile dan pada skrip startup memeriksa apakah suatu proses berjalan dengan PID terbaru.
Menggunakan kunci proses jauh lebih kuat dan merawat pintu keluar yang tidak berterima juga. lock_file tetap terbuka selama proses ini berjalan. Ini akan ditutup (oleh shell) setelah proses ada (bahkan jika terbunuh). Saya menemukan ini sangat efisien:
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
Saya menggunakan oneliner @ bagian paling awal dari skrip:
#!/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
Adalah baik untuk melihat keberadaan proses dalam memori (tidak peduli apa status prosesnya); tetapi itu berhasil bagi saya.
Jalur kawanan adalah cara untuk pergi. Pikirkan tentang apa yang terjadi ketika naskah tiba-tiba mati. Dalam kasus kawanan Anda hanya kehilangan kawanan, tetapi itu tidak masalah. Juga, perhatikan bahwa trik jahat adalah mengambil kawanan pada skrip itu sendiri .. tetapi itu tentu saja memungkinkan Anda menjalankan penuh-depan ke masalah izin.
Cepat dan kotor?
#!/bin/sh
if [ -f sometempfile ]
echo "Already running... will now terminate."
exit
else
touch sometempfile
fi
..do what you want here..
rm sometempfile