जवाबों:
यहां एक कार्यान्वयन है जो लॉकफ़ाइल का उपयोग करता है और इसमें एक पीआईडी डालता है। यह एक सुरक्षा के रूप में कार्य करता है अगर प्रक्रिया को हटाने से पहले मार दिया जाता है :
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
जो किसी भी सिग्नल को डिलीवर नहीं करता है, लेकिन यदि पीआईडी के साथ कोई प्रक्रिया मौजूद है तो बस चेक करता है। इसके अलावा 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
यह उस कोड को बीच में सुनिश्चित करता है (
और )
एक समय में केवल एक प्रक्रिया द्वारा चलाया जाता है और यह प्रक्रिया एक लॉक के लिए बहुत लंबा इंतजार नहीं करती है।
कैविएट: यह विशेष कमांड का एक हिस्सा है util-linux
। यदि आप लिनक्स के अलावा कोई ऑपरेटिंग सिस्टम चलाते हैं, तो यह उपलब्ध हो सकता है या नहीं भी हो सकता है।
( command A ) command B
लिए एक उपखंड को आमंत्रित करता है command A
। Tldp.org/LDP/abs/html/subshells.html पर प्रलेखित । मैं अभी भी
if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fi
ताकि यदि टाइमआउट होता है (किसी अन्य प्रक्रिया में फ़ाइल लॉक है), तो यह स्क्रिप्ट आगे नहीं जाती है और फ़ाइल को संशोधित करती है। संभवतः ... प्रतिवाद 'है, लेकिन अगर इसमें 10 सेकंड लगे हैं और ताला अभी भी उपलब्ध नहीं है, तो यह कभी भी उपलब्ध नहीं होगा', संभवत: क्योंकि ताला धारण करने वाली प्रक्रिया समाप्त नहीं हो रही है (शायद इसे चलाया जा रहा है) डिबगर के तहत?)
exit
अंदर के भाग से है (
)
। जब उपप्रकार समाप्त हो जाता है, तो लॉक स्वचालित रूप से जारी होता है, क्योंकि इसे धारण करने की कोई प्रक्रिया नहीं है।
"लॉक फाइल्स" के अस्तित्व का परीक्षण करने वाले सभी दृष्टिकोण त्रुटिपूर्ण हैं।
क्यों? क्योंकि यह जांचने का कोई तरीका नहीं है कि क्या कोई फ़ाइल मौजूद है और इसे एक एकल परमाणु क्रिया में बनाएँ। होने के कारण; वहाँ एक दौड़ की स्थिति है कि आपसी बहिष्करण विराम पर आपके प्रयास करेंगे ।
इसके बजाय, आपको उपयोग करने की आवश्यकता है 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
यदि आप बासी ताले की देखभाल करना चाहते हैं, तो फ्यूज़र (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; }
यदि आप पोर्टेबिलिटी के बारे में परवाह नहीं करते हैं (इन समाधानों को किसी भी यूनिक्स बॉक्स पर बहुत अधिक काम करना चाहिए), लिनक्स ' फ्यूज़र (1) कुछ अतिरिक्त विकल्प प्रदान करता है और झुंड (1) भी है ।
if ! mkdir
हिस्से की जांच के साथ जोड़ सकते हैं कि क्या लॉकड के अंदर संग्रहीत पीआईडी के साथ प्रक्रिया (सक्सेसर स्टार्टअप पर) वास्तव में चल रही है और स्टैलनस सुरक्षा के लिए स्क्रिप्ट के समान है। यह रिबूट के बाद पीआईडी का पुन: उपयोग करने से भी बचाएगा, और इसकी आवश्यकता भी नहीं है fuser
।
mkdir
नहीं किया गया है और जैसे कि "साइड-इफेक्ट" फाइल सिस्टम का कार्यान्वयन विवरण है। मुझे पूरा विश्वास है कि अगर वह कहते हैं कि एनएफएस इसे परमाणु फैशन में लागू नहीं करता है। हालांकि मुझे संदेह नहीं है कि आपका /tmp
एनएफएस हिस्सा होगा और संभवतः एफ़एस द्वारा लागू किए जाने वाले एफएस द्वारा प्रदान किया जाएगा mkdir
।
ln
किसी अन्य फ़ाइल से हार्ड लिंक बनाने के लिए उपयोग करना। यदि आपके पास अजीब फाइल सिस्टम हैं जो गारंटी नहीं देते हैं, तो आप नई फाइल के इनकोड को बाद में यह देखने के लिए देख सकते हैं कि यह मूल फाइल के समान है या नहीं।
open(... O_CREAT|O_EXCL)
। आपको ऐसा करने के लिए बस एक उपयुक्त उपयोगकर्ता कार्यक्रम की आवश्यकता है, जैसे lockfile-create
(में lockfile-progs
) या dotlockfile
(में liblockfile-bin
)। और सुनिश्चित करें कि आप ठीक से सफाई (जैसे trap EXIT
), या बासी ताले के लिए परीक्षण (जैसे के साथ --use-pid
)।
झुंड (2) सिस्टम कॉल के आसपास एक आवरण है, जिसे अकल्पनीय रूप से झुंड (1) कहा जाता है। यह अपेक्षाकृत आसानी से सफाई के बारे में चिंता किए बिना अनन्य ताले प्राप्त करना आसान बनाता है आदि । मैन पेज पर उदाहरण हैं कि इसे शेल स्क्रिप्ट में कैसे उपयोग किया जाए।
flock()
सिस्टम कॉल POSIX नहीं है और एनएफएस माउंट पर फ़ाइलों के लिए काम नहीं करता।
flock -x -n %lock file% -c "%command%"
यह सुनिश्चित करने के लिए उपयोग करता हूं कि केवल एक उदाहरण कभी निष्पादित हो रहा है।
आपको एक परमाणु संचालन की आवश्यकता है, जैसे झुंड, अन्यथा यह अंततः विफल हो जाएगा।
लेकिन अगर झुंड उपलब्ध नहीं है तो क्या करें। वैसे तो मुक्दिर है। वह एक परमाणु ऑपरेशन भी है। केवल एक प्रक्रिया के परिणामस्वरूप एक सफल mkdir होगा, अन्य सभी विफल हो जाएंगे।
तो कोड है:
if mkdir /var/lock/.myscript.exclusivelock
then
# do stuff
:
rmdir /var/lock/.myscript.exclusivelock
fi
आपको बासी तालों की देखभाल करने की आवश्यकता है, एक और दुर्घटना से आपकी स्क्रिप्ट फिर कभी नहीं चलेगी।
sleep 10
पहले जोड़ें rmdir
और फिर से कैस्केड करने का प्रयास करें - कुछ भी "लीक" नहीं होगा।
लॉकिंग को विश्वसनीय बनाने के लिए आपको एक परमाणु ऑपरेशन की आवश्यकता होती है। उपरोक्त कई प्रस्ताव परमाणु नहीं हैं। प्रस्तावित लॉकफाइल (1) उपयोगिता के रूप में आशाजनक दिख रहा है, जैसा कि उल्लेख किया गया है कि इसका "एनएफएस-प्रतिरोधी" है। यदि आपका OS लॉकफ़ाइल (1) का समर्थन नहीं करता है और आपके समाधान को NFS पर काम करना है, तो आपके पास कई विकल्प नहीं हैं ...।
NFSv2 में दो परमाणु ऑपरेशन हैं:
NFSv3 के साथ क्रिएट कॉल भी परमाणु है।
निर्देशिका संचालन NFSv2 और NFSv3 के तहत परमाणु नहीं हैं (ब्रेंट कॉलगान, आईएसबीएन 0-201-32570-5 द्वारा पुस्तक 'एनएफएस इलस्ट्रेटेड' के लिए कृपया देखें; ब्रेंट सन में एनएफएस-अनुभवी है)।
यह जानकर, आप फ़ाइलों और निर्देशिकाओं के लिए स्पिन-लॉक लागू कर सकते हैं (शेल में, PHP नहीं):
ताला वर्तमान dir:
while ! ln -s . lock; do :; done
फ़ाइल लॉक करें:
while ! ln -s ${f} ${f}.lock; do :; done
वर्तमान dir अनलॉक करें (धारणा, चल रही प्रक्रिया ने वास्तव में लॉक का अधिग्रहण किया):
mv lock deleteme && rm deleteme
एक फ़ाइल अनलॉक करें (धारणा, चल रही प्रक्रिया ने वास्तव में लॉक का अधिग्रहण किया):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
निकालें भी परमाणु नहीं है, इसलिए पहले नाम (जो परमाणु है) और फिर निकालें।
सिम्लिंक और रीनेम कॉल के लिए, दोनों फाइलनेम को एक ही फाइल सिस्टम पर रहना होता है। मेरा प्रस्ताव: केवल सरल फ़ाइल नाम (कोई पथ नहीं) का उपयोग करें और एक ही निर्देशिका में फ़ाइल और लॉक डालें।
lockfile
उपलब्ध है या symlink
नहीं तो इस पद्धति का उपयोग करता है यदि नहीं।
mv
, ( rm
), के rm -f
लिए इस्तेमाल किया जाना चाहिए, बजाय rm
दो प्रक्रियाओं P1, P2 रेसिंग कर रहे हैं? उदाहरण के लिए, पी 1 शुरू होता है mv
, फिर पी 2 लॉक, फिर पी 2 अनलॉक (दोनों mv
और rm
), आखिरकार पी 1 प्रयास 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
अधिकांश लिनक्स / ओएसएक्स / यूनिक्स प्लेटफार्मों पर एक बहुत ही सरल इंस्टॉल है - यह सिर्फ एक पर्ल स्क्रिप्ट है।
sem
संबंधित प्रश्न unix.stackexchange.com/a/322200/199525 पर अधिक ।
शेल स्क्रिप्ट के लिए, मैं mkdir
ओवर के साथ जाना पसंद करता हूं 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
हमेशा होगा (एक साइकल को छोड़कर) जो आपके ताले को साफ करता है।
नोट: मेरे निकास मान निम्न मान नहीं हैं। क्यों? विभिन्न बैच प्रोसेसिंग सिस्टम 31 के माध्यम से संख्या 0 की अपेक्षाएं रखते हैं या करते हैं। उन्हें कुछ और के लिए सेट करना, मैं अपनी स्क्रिप्ट और बैच स्ट्रीम पिछले बैच की नौकरी या स्क्रिप्ट के अनुसार प्रतिक्रिया कर सकता हूं।
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
पहला उदाहरण खुद को देखने के बाद से जाँच की जाती है।
यहां एक दृष्टिकोण है जो पीआईडी के माध्यम से बासी लॉक के लिए एक चेक के साथ परमाणु निर्देशिका लॉकिंग को जोड़ती है और यदि बासी हो तो पुनरारंभ करें। इसके अलावा, यह किसी भी bashism पर निर्भर नहीं करता है।
#!/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
इस उदाहरण को मानव झुंड में समझाया गया है, लेकिन इसके लिए कुछ आवेगों की आवश्यकता है, क्योंकि हमें बग और निकास कोड का प्रबंधन करना चाहिए:
#!/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
आप एक अन्य विधि, सूची प्रक्रियाओं का उपयोग कर सकते हैं जो मैंने अतीत में उपयोग किया था। लेकिन यह उस विधि से अधिक जटिल है। आपको पीएस द्वारा प्रक्रियाओं को सूचीबद्ध करना चाहिए, इसके नाम से फ़िल्टर करना चाहिए, परजीवी नाड को हटाने के लिए अतिरिक्त फ़िल्टर grep -v grep अंत में इसे grep -c द्वारा गिनना चाहिए। और संख्या के साथ तुलना करें। इसकी जटिल और अनिश्चित है
पोस्ट किए गए मौजूदा उत्तर या तो सीएलआई उपयोगिता पर भरोसा करते हैं flock
या लॉक फ़ाइल को ठीक से सुरक्षित नहीं करते हैं। झुंड की उपयोगिता सभी गैर-लिनक्स प्रणालियों (यानी फ्रीबीएसडी) पर उपलब्ध नहीं है, और एनएफएस पर ठीक से काम नहीं करता है।
सिस्टम एडमिनिस्ट्रेशन और सिस्टम डेवलपमेंट के अपने शुरुआती दिनों में, मुझे बताया गया था कि लॉक फाइल बनाने की एक सुरक्षित और अपेक्षाकृत पोर्टेबल विधि थी टेम्परेरी फाइल का उपयोग करना 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
अपनी स्क्रिप्ट में किसी भी निकास बिंदु पर कॉल करना याद रखें ।
मैंने उपरोक्त दोनों लिनक्स और फ्रीबीएसडी वातावरण में उपयोग किया है।
डेबियन मशीन को लक्षित करते समय मुझे lockfile-progs
पैकेज एक अच्छा समाधान लगता है। procmail
एक lockfile
टूल भी आता है । हालाँकि कभी-कभी मैं इनमें से किसी के साथ फंस जाता हूं।
यहाँ मेरा समाधान है जो mkdir
बासी तालों का पता लगाने के लिए परमाणु-नेस और एक पीआईडी फ़ाइल का उपयोग करता है । यह कोड वर्तमान में Cygwin सेटअप पर उत्पादन में है और अच्छी तरह से काम करता है।
इसका उपयोग करने के लिए बस exclusive_lock_require
उस समय कॉल करें जब आपको किसी चीज़ की अनन्य पहुँच की आवश्यकता हो। एक वैकल्पिक लॉक नाम पैरामीटर आपको विभिन्न स्क्रिप्ट के बीच ताले साझा करने देता है। दो निचले स्तर के कार्य भी हैं ( 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
}
यदि झुंड की सीमाएं, जो पहले से ही इस धागे पर कहीं और वर्णित की गई हैं, आपके लिए कोई समस्या नहीं हैं, तो यह काम करना चाहिए:
#!/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
तुरंत अगर यह ताला नहीं मिल सकता है
कुछ यूनिक्स lockfile
में पहले से ही उल्लेख के समान है flock
।
मैनपेज से:
लॉकफाइल का उपयोग एक या अधिक सेमाफोर फाइलें बनाने के लिए किया जा सकता है। यदि लॉक-फ़ाइल सभी निर्दिष्ट फ़ाइलों (निर्दिष्ट क्रम में) को नहीं बना सकती है, तो यह स्लीपटाइम (8 से डिफॉल्ट) सेकंड इंतजार करती है और अंतिम फ़ाइल को पुनर्प्राप्त करती है जो सफल नहीं हुई। जब तक विफलता वापस नहीं हो जाती तब तक आप रिट्रीट की संख्या निर्दिष्ट कर सकते हैं। यदि रिट्रीज़ की संख्या -1 है (डिफ़ॉल्ट, यानी -r-1) लॉकफाइल हमेशा के लिए पुनः प्रयास करेगा।
lockfile
उपयोगिता कैसे प्राप्त करते हैं ??
lockfile
के साथ वितरित किया जाता है procmail
। इसके अलावा एक विकल्प है dotlockfile
जो liblockfile
पैकेज के साथ जाता है। वे दोनों एनएफएस पर मज़बूती से काम करने का दावा करते हैं।
असल में यद्यपि बम्हैड्स का उत्तर लगभग अच्छा है, फिर भी पहली बार लॉकफ़ाइल की जाँच करने और इसे लिखने से पहले दूसरी स्क्रिप्ट को चलाने का थोड़ा मौका है। इसलिए वे दोनों लॉक फाइल को लिखेंगे और दोनों चलेंगे। यहाँ यह सुनिश्चित करने के लिए काम करना है:
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
विकल्प यकीन है कि अगर फ़ाइल पहले से मौजूद रीडायरेक्ट आदेश विफल हो जाएगा कर देगा। इसलिए रीडायरेक्ट कमांड वास्तव में परमाणु है - आप फ़ाइल को एक कमांड के साथ लिखते और चेक करते हैं। आपको फ़ाइल के अंत में लॉकफाइल को हटाने की आवश्यकता नहीं है - यह जाल द्वारा हटा दिया जाएगा। मुझे उम्मीद है कि इससे लोगों को मदद मिलेगी जो इसे बाद में पढ़ेंगे।
PS मैंने यह नहीं देखा कि मिकेल ने पहले ही प्रश्न का सही उत्तर दे दिया है, हालांकि उसने उदाहरण के लिए Ctrl-C के साथ स्क्रिप्ट को रोकने के बाद लॉक फ़ाइल को छोड़ दिया जाएगा मौका को कम करने के लिए ट्रैप कमांड को शामिल नहीं किया। तो यह पूरा समाधान है
मैं लॉकफाइल्स, लॉकडायर, विशेष लॉकिंग प्रोग्राम और यहां तक कि pidof
जब से यह सभी लिनक्स इंस्टॉलेशन पर नहीं मिला है , तब भी मैं इसे दूर करना चाहता था । इसके अलावा सरलतम संभव कोड (या संभव के रूप में कम से कम कुछ पंक्तियाँ) चाहते थे। सरलतम if
कथन, एक पंक्ति में:
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; }'
मैं एक सरल दृष्टिकोण का उपयोग करता हूं जो बासी लॉक फ़ाइलों को संभालता है।
ध्यान दें कि उपरोक्त कुछ समाधान जो पीआईडी को स्टोर करते हैं, इस तथ्य को अनदेखा करते हैं कि पीआईडी चारों ओर लपेट सकता है। इसलिए - सिर्फ जाँच करना कि क्या संग्रहित पीड के साथ एक वैध प्रक्रिया पर्याप्त नहीं है, विशेष रूप से लंबे समय तक चलने वाली स्क्रिप्ट के लिए।
मैं noclobber का उपयोग यह सुनिश्चित करने के लिए करता हूं कि केवल एक स्क्रिप्ट खोल सकता है और एक समय में लॉक फ़ाइल को लिख सकता है। इसके अलावा, मैं लॉकफ़ाइल में एक प्रक्रिया को विशिष्ट रूप से पहचानने के लिए पर्याप्त जानकारी संग्रहीत करता हूं। मैं डेटा के सेट को विशिष्ट रूप से 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" "$@" || :
यह आदमी झुंड से एक बॉयलरप्लेट कोड है।
यदि आप अधिक लॉगिंग चाहते हैं, तो इसका उपयोग करें
[ "${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 को सही तरीके से सेट किया जाता है, तो पिछले पुनरावृत्ति पर झुंड सफल हुआ और आगे बढ़ना ठीक है। यदि लॉक व्यस्त है, तो यह कॉन्फ़िगर करने योग्य निकास कोड के साथ विफल हो जाता है।
ऐसा लगता है कि डेबियन 7 पर काम नहीं किया जा रहा है, लेकिन प्रायोगिक उपयोग-लाइन 2.25 पैकेज के साथ फिर से काम करने लगता है। यह लिखता है "झुंड: ... पाठ फ़ाइल व्यस्त"। आपकी स्क्रिप्ट पर लिखने की अनुमति को अक्षम करने से इसे ओवरराइड किया जा सकता है।
पीआईडी और लॉकफाइल्स निश्चित रूप से सबसे विश्वसनीय हैं। जब आप प्रोग्राम को चलाने का प्रयास करते हैं, तो यह लॉकफ़ाइल के लिए जाँच कर सकता है जो मौजूद है और यदि मौजूद है, तो यह ps
देखने के लिए उपयोग कर सकता है कि क्या प्रक्रिया अभी भी चल रही है। यदि यह नहीं है, तो स्क्रिप्ट शुरू हो सकती है, पीआईडी को लॉकफाइल में अपडेट कर सकता है।
मुझे लगता है कि bmdhack का समाधान सबसे व्यावहारिक है, कम से कम मेरे उपयोग के मामले के लिए। झुंड और लॉकफाइल का उपयोग करते हुए स्क्रिप्ट समाप्त होने पर आरएम का उपयोग करके लॉकफाइल को हटाने पर भरोसा करते हैं, जिसे हमेशा गारंटी नहीं दी जा सकती है (जैसे, किल -9)।
मैं bmdhack के समाधान के बारे में एक छोटी सी बात को बदल दूंगा: यह लॉक फाइल को हटाने का एक बिंदु बनाता है, यह बताते हुए कि यह इस सेमाफोर के सुरक्षित काम के लिए अनावश्यक है। किल -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 द्वारा इसके लिए प्रस्तावित समाधान में दोष है कि एक मार दी गई स्क्रिप्ट निर्देशिका को पीछे छोड़ सकती है, इस प्रकार अन्य उदाहरणों को चलने से रोक सकती है।
Semaphoric उपयोगिता का उपयोग करता है flock
(जैसा कि presto8 से ऊपर चर्चा की, जैसे) एक लागू करने के लिए गिनती सेमाफोर । यह समवर्ती प्रक्रियाओं की किसी भी विशिष्ट संख्या को आप चाहते हैं। हम इसका उपयोग विभिन्न कतार कार्यकर्ता प्रक्रियाओं की समाप्ती के स्तर को सीमित करने के लिए करते हैं।
यह सेम की तरह है लेकिन बहुत हल्का है। (पूर्ण प्रकटीकरण: मैंने लिखा है कि खोजने के बाद सेम हमारी जरूरतों के लिए बहुत भारी था और वहाँ एक सरल गणना अर्ध-उपयोगिता उपलब्ध नहीं थी।)
झुंड के साथ एक उदाहरण (1) लेकिन बिना उपधारा के। झुंड () एड फ़ाइल / tmp / foo को कभी भी हटाया नहीं जाता है, लेकिन इससे कोई फर्क नहीं पड़ता क्योंकि यह झुंड () और संयुक्त राष्ट्र का झुंड () एड हो जाता है।
#!/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="/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
हर बार यह वर्तमान पीआईडी ($ $) को लॉकफाइल में और स्क्रिप्ट स्टार्टअप चेक पर लिखता है कि क्या कोई प्रक्रिया नवीनतम पीआईडी के साथ चल रही है।
प्रक्रिया के लॉक का उपयोग करना अधिक मजबूत होता है और असहमतिपूर्ण निकास का भी ध्यान रखता है। जब तक प्रक्रिया चल रही है तब तक 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
मैं स्क्रिप्ट की शुरुआत में oneliner @ का उपयोग करता हूं:
#!/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
मेमोरी में प्रक्रिया की उपस्थिति को देखना अच्छा है (कोई फर्क नहीं पड़ता कि प्रक्रिया की स्थिति क्या है); लेकिन यह मेरे लिए काम करता है।
झुंड का रास्ता जाने का रास्ता है। जब स्क्रिप्ट अचानक मर जाती है तो क्या होता है, इसके बारे में सोचें। झुंड-मामले में आप सिर्फ झुंड को ढीला करते हैं, लेकिन यह कोई समस्या नहीं है। इसके अलावा, ध्यान दें कि एक बुरी चाल स्क्रिप्ट पर ही एक झुंड लेना है .. लेकिन निश्चित रूप से आपको अनुमति की समस्याओं में पूर्ण-स्टीम-फॉरवर्ड चलाने की सुविधा मिलती है।
जल्दी और गन्दी?
#!/bin/sh
if [ -f sometempfile ]
echo "Already running... will now terminate."
exit
else
touch sometempfile
fi
..do what you want here..
rm sometempfile