कैसे सुनिश्चित करें कि केवल बैश स्क्रिप्ट का एक ही उदाहरण चलता है?


26

एक ऐसा उपाय जिसमें अतिरिक्त उपकरणों की आवश्यकता नहीं होती है, को प्राथमिकता दी जाएगी।


लॉक फ़ाइल के बारे में क्या?
मार्को १

@ मर्को मुझे इसका उपयोग करते हुए एसओ उत्तर मिला , लेकिन जैसा कि एक टिप्पणी में कहा गया है , यह एक दौड़ की स्थिति पैदा कर सकता है
टोबियास किंजलर


@ jw013 धन्यवाद! तो शायद कुछ ऐसा ln -s my.pid .lockहोगा जो लॉक का दावा करेगा (इसके बाद echo $$ > my.pid) और असफलता की जांच कर सकता है कि क्या पीआईडी ​​संग्रहित है या नहीं .lockयह वास्तव में स्क्रिप्ट का एक सक्रिय उदाहरण है
टोबैस किंजलर

जवाबों:


18

लगभग nsg के उत्तर की तरह: एक लॉक डायरेक्टरी का उपयोग करें । निर्देशिका निर्माण में लिनक्स और यूनिक्स और * बीएसडी और कई अन्य ओएस के तहत परमाणु है।

if mkdir $LOCKDIR
then
    # Do important, exclusive stuff
    if rmdir $LOCKDIR
    then
        echo "Victory is mine"
    else
        echo "Could not remove lock dir" >&2
    fi
else
    # Handle error condition
    ...
fi

आप लॉकिंग श के PID को डीबगिंग उद्देश्यों के लिए लॉक डायरेक्टरी में एक फ़ाइल में डाल सकते हैं, लेकिन यह सोचकर आप उसके चक्कर में न पड़ें कि क्या पीआईडी ​​देख सकता है कि लॉकिंग प्रक्रिया अभी भी निष्पादित होती है या नहीं। दौड़ की बहुत सारी स्थितियां उस रास्ते पर लेट जाती हैं।


1
मैं संग्रहीत पीआईडी ​​का उपयोग करने पर विचार करूंगा कि क्या लॉकिंग इंस्टेंस अभी भी जीवित है। हालांकि, यहाँ एक दावा है कि mkdirएनएफएस पर परमाणु नहीं है (जो मेरे लिए मामला नहीं है, लेकिन मुझे लगता है कि किसी को इसका उल्लेख करना चाहिए, अगर यह सच है)
टोबियास किंजलर

हां, हर तरह से संग्रहीत पीआईडी ​​का उपयोग करके देखें कि क्या लॉकिंग प्रक्रिया अभी भी निष्पादित होती है, लेकिन लॉग संदेश के अलावा कुछ भी करने का प्रयास न करें। संग्रहीत पीआईडी ​​की जांच करने, एक नई पीआईडी ​​फ़ाइल बनाने आदि का काम दौड़ के लिए एक बड़ी खिड़की छोड़ देता है।
ब्रूस एडिगर

ठीक है, जैसा कि इहुनाथ ने कहा था , लॉकडायर की संभावना सबसे अधिक होगी /tmpजिसमें आमतौर पर एनएफएस साझा नहीं किया जाता है, इसलिए यह ठीक होना चाहिए।
टोबियास किंजलर

मैं rm -rfलॉक डायरेक्टरी को हटाने के लिए उपयोग करूंगा । rmdirअसफल हो जाएगा अगर कोई (जरूरी नहीं कि आप) निर्देशिका में एक फ़ाइल जोड़ने में कामयाब रहे।
चॅपनर

18

ब्रूस एडगर के जवाब में जोड़ने के लिए , और इस जवाब से प्रेरित होकर , आपको स्क्रिप्टिंग के खिलाफ सफाई के लिए सफाई में अधिक स्मार्ट भी जोड़ना चाहिए:

#Remove the lock directory
function cleanup {
    if rmdir $LOCKDIR; then
        echo "Finished"
    else
        echo "Failed to remove lock directory '$LOCKDIR'"
        exit 1
    fi
}

if mkdir $LOCKDIR; then
    #Ensure that if we "grabbed a lock", we release it
    #Works for SIGTERM and SIGINT(Ctrl-C)
    trap "cleanup" EXIT

    echo "Acquired lock, running"

    # Processing starts here
else
    echo "Could not create lock directory '$LOCKDIR'"
    exit 1
fi

वैकल्पिक रूप से, if ! mkdir "$LOCKDIR"; then handle failure to lock and exit; fi trap and do processing after if-statement
Kusalananda

6

यह बहुत सरल हो सकता है, कृपया मुझे सही करें अगर मैं गलत हूं। एक सरल psपर्याप्त नहीं है?

#!/bin/bash 

me="$(basename "$0")";
running=$(ps h -C "$me" | grep -wv $$ | wc -l);
[[ $running > 1 ]] && exit;

# do stuff below this comment

1
अच्छा और / या शानदार। :)
स्पूकी

1
मैंने एक सप्ताह के लिए इस स्थिति का उपयोग किया है, और 2 अवसरों में यह नई प्रक्रिया को शुरू करने से नहीं रोकता है। मुझे लगा कि समस्या क्या है - नया पीआईडी ​​पुराने का एक विकल्प है और इससे छिप जाता है grep -v $$। वास्तविक उदाहरण: वर्ष - 14532, नई - 1453, वर्ष - 28,858, नई - 858.
Naktibalda

मैंने इसे बदलकर तय grep -v $$कियाgrep -v "^${$} "
नकटिबल्दा

@Naktibalda की अच्छी पकड़, धन्यवाद! आप इसे ठीक भी कर सकते हैं grep -wv "^$$"(देखें संपादित करें)।
terdon

उस अद्यतन के लिए धन्यवाद। मेरा पैटर्न कभी-कभार विफल हो गया क्योंकि छोटे ग्रिड रिक्त स्थान छोड़ दिए गए थे।
नकटिबल्दा

4

मैं एक लॉक फ़ाइल का उपयोग करूंगा, जैसा कि मार्को ने उल्लेख किया है

#!/bin/bash

# Exit if /tmp/lock.file exists
[ -f /tmp/lock.file ] && exit

# Create lock file, sleep 1 sec and verify lock
echo $$ > /tmp/lock.file
sleep 1
[ "x$(cat /tmp/lock.file)" == "x"$$ ] || exit

# Do stuff
sleep 60

# Remove lock file
rm /tmp/lock.file

1
(मुझे लगता है कि आप लॉक फ़ाइल बनाना भूल गए) दौड़ की स्थिति के बारे में क्या ?
टोबियास किंजलर

ops :) हां, दौड़ की स्थिति मेरे उदाहरण में एक समस्या है, मैं आमतौर पर प्रति घंटा या दैनिक क्रॉन जॉब्स लिखता हूं और दौड़ की स्थितियां दुर्लभ हैं।
nsg

उन्हें मेरे मामले में प्रासंगिक नहीं होना चाहिए, लेकिन यह कुछ ऐसा है जिसे ध्यान में रखना चाहिए। शायद का उपयोग lsof $0करना बुरा नहीं है, या तो?
टोबियास किंजलर

आप $$लॉक फ़ाइल में अपना लिखकर दौड़ की स्थिति को कम कर सकते हैं । फिर sleepथोड़े अंतराल के लिए और इसे वापस पढ़ें। यदि पीआईडी ​​अभी भी आपकी है, तो आपने सफलतापूर्वक लॉक का अधिग्रहण कर लिया है। बिल्कुल कोई अतिरिक्त उपकरण की जरूरत है।
मैनावर्क

1
मैंने इस उद्देश्य के लिए कभी भी lsof का उपयोग नहीं किया है, मुझे यह काम करना चाहिए। ध्यान दें कि मेरे सिस्टम में lsof वास्तव में धीमा है (1-2 सेकंड) और सबसे अधिक संभावना है कि दौड़ की स्थिति के लिए बहुत समय है।
एनएसजी १g

3

यदि आप यह सुनिश्चित करना चाहते हैं कि आपकी स्क्रिप्ट का केवल एक उदाहरण चल रहा है:

अपनी स्क्रिप्ट लॉक करें (समानांतर रन के खिलाफ)

अन्यथा आप जांच psया आह्वान कर सकते हैं lsof <full-path-of-your-script>, क्योंकि मैं उन्हें अतिरिक्त उपकरण नहीं कहूंगा।


अनुपूरक :

वास्तव में मैंने ऐसा करने के बारे में सोचा था:

for LINE in `lsof -c <your_script> -F p`; do 
    if [ $$ -gt ${LINE#?} ] ; then
        echo "'$0' is already running" 1>&2
        exit 1;
    fi
done

यह सुनिश्चित करता है कि केवल सबसे कम प्रक्रिया ही pidचलती रहे, भले ही आप <your_script>एक साथ कई उदाहरणों को कांट-छांट करते हों ।


1
लिंक के लिए धन्यवाद, लेकिन क्या आप अपने उत्तर में आवश्यक भागों को शामिल कर सकते हैं? लिंक सड़ांध को रोकने के लिए एसई पर यह सामान्य नीति है ... लेकिन [[(lsof $0 | wc -l) > 2]] && exitवास्तव में ऐसा कुछ हो सकता है, या क्या यह भी दौड़ की स्थिति से ग्रस्त है?
टोबियास किंजलर

आप सही हैं मेरे उत्तर का अनिवार्य हिस्सा गायब था और केवल लिंक पोस्ट करना बहुत ही लचर है। मैंने अपना सुझाव उत्तर में जोड़ दिया।
user1146332 12

3

बैश स्क्रिप्ट का एक ही उदाहरण सुनिश्चित करने के लिए एक और तरीका चलता है:

#!/bin/bash

# Check if another instance of script is running
pidof -o %PPID -x $0 >/dev/null && echo "ERROR: Script $0 already running" && exit 1

...

pidof -o %PPID -x $0 यदि पहले से चल रहा है या त्रुटि कोड 1 से बाहर निकलता है यदि कोई अन्य स्क्रिप्ट नहीं चल रही है, तो मौजूदा स्क्रिप्ट का पीआईडी ​​प्राप्त करता है


3

यद्यपि आपने अतिरिक्त टूल के बिना समाधान के लिए पूछा है, यह मेरा पसंदीदा तरीका है flock:

#!/bin/sh

[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :

echo "servus!"
sleep 10

यह उदाहरण अनुभाग से आता है man flock, जो आगे बताता है:

यह शेल स्क्रिप्ट के लिए उपयोगी बॉयलरप्लेट कोड है। इसे शेल स्क्रिप्ट के शीर्ष पर रखें जिसे आप लॉक करना चाहते हैं और यह स्वचालित रूप से पहले रन पर लॉक हो जाएगा। यदि env var $ FLOCKER शेल स्क्रिप्ट के लिए सेट नहीं है जिसे चलाया जा रहा है, तो झुंड को निष्पादित करें और सही तर्कों के साथ खुद को फिर से निष्पादित करने से पहले एक विशेष नॉन-ब्लॉकिंग लॉक (स्क्रिप्ट को लॉक फाइल के रूप में उपयोग करके) को पकड़ो। यह FLOCKER env var को सही मान पर सेट करता है ताकि यह फिर से न चले।

घ्यान देने योग्य बातें:

  • आवश्यकता है flock, उदाहरण स्क्रिप्ट एक त्रुटि के साथ समाप्त होता है अगर यह नहीं पाया जा सकता है
  • कोई अतिरिक्त लॉक फ़ाइल की आवश्यकता नहीं है
  • यदि स्क्रिप्ट NFS पर है तो काम न करें (देखें /server/66919/file-locks-on-an-nfs )

Https://stackoverflow.com/questions/185451/quick-and-dirty-way-to-ensure-only-one-instance-of-a-shell-script-is-running-at भी देखें


1

यह Anselmo के उत्तर का एक संशोधित संस्करण है । विचार केवल बैश स्क्रिप्ट का उपयोग करके एक रीड ओनली फाइल डिस्क्रिप्टर बनाने का है और flockलॉक को संभालने के लिए उपयोग करना है।

SCRIPT=`realpath $0`     # get absolute path to the script itself
exec 6< "$SCRIPT"        # open bash script using file descriptor 6
flock -n 6 || { echo "ERROR: script is already running" && exit 1; }   # lock file descriptor 6 OR show error message if script is already running

echo "Run your single instance code here"

अन्य सभी उत्तरों में मुख्य अंतर यह है कि यह कोड फ़ाइल सिस्टम को संशोधित नहीं करता है, बहुत कम पदचिह्न का उपयोग करता है और किसी भी क्लीनअप की आवश्यकता नहीं है क्योंकि फ़ाइल विवरणक बंद होने के बाद जैसे ही स्क्रिप्ट बाहर निकलने की स्थिति से स्वतंत्र हो जाती है। इस प्रकार यह कोई फर्क नहीं पड़ता कि स्क्रिप्ट विफल हो जाती है या सफल होती है।


जब तक आपके पास कोई अच्छा कारण नहीं है, आपको हमेशा सभी शेल चर संदर्भों को उद्धृत करना चाहिए, और आपको यकीन है कि आप जानते हैं कि आप क्या कर रहे हैं। तो आपको करना चाहिए exec 6< "$SCRIPT"
स्कॉट

@ सेट करें मैंने आपके सुझावों के अनुसार कोड बदल दिया है। बहुत धन्यवाद।
जॉन डोए

1

मैं अपनी स्क्रिप्ट की जांच करने के लिए cksum का उपयोग कर रहा हूं, वास्तव में एकल उदाहरण चल रहा है, यहां तक ​​कि मैं फ़ाइल नाम और फ़ाइल पथ भी बदलता हूं

मैं ट्रैप एंड लॉक फ़ाइल का उपयोग नहीं कर रहा हूं, क्योंकि यदि मेरा सर्वर अचानक डाउन हो जाता है, तो सर्वर के ऊपर जाने के बाद मुझे मैन्युअल रूप से लॉक फ़ाइल को हटाने की आवश्यकता है।

नोट: #! / बिन / बैश पहली पंक्ति में grep ps के लिए आवश्यक है

#!/bin/bash

checkinstance(){
   nprog=0
   mysum=$(cksum $0|awk '{print $1}')
   for i in `ps -ef |grep /bin/bash|awk '{print $2}'`;do 
        proc=$(ls -lha /proc/$i/exe 2> /dev/null|grep bash) 
        if [[ $? -eq 0 ]];then 
           cmd=$(strings /proc/$i/cmdline|grep -v bash)
                if [[ $? -eq 0 ]];then 
                   fsum=$(cksum /proc/$i/cwd/$cmd|awk '{print $1}')
                   if [[ $mysum -eq $fsum ]];then
                        nprog=$(($nprog+1))
                   fi
                fi
        fi
   done

   if [[ $nprog -gt 1 ]];then
        echo $0 is already running.
        exit
   fi
}

checkinstance 

#--- run your script bellow 

echo pass
while true;do sleep 1000;done

या आप अपनी स्क्रिप्ट के अंदर cksum हार्डकोड कर सकते हैं, इसलिए आपको कोई चिंता नहीं है अगर आप अपनी स्क्रिप्ट का फ़ाइल नाम, पथ या सामग्री बदलना चाहते हैं

#!/bin/bash

mysum=1174212411

checkinstance(){
   nprog=0
   for i in `ps -ef |grep /bin/bash|awk '{print $2}'`;do 
        proc=$(ls -lha /proc/$i/exe 2> /dev/null|grep bash) 
        if [[ $? -eq 0 ]];then 
           cmd=$(strings /proc/$i/cmdline|grep -v bash)
                if [[ $? -eq 0 ]];then 
                   fsum=$(grep mysum /proc/$i/cwd/$cmd|head -1|awk -F= '{print $2}')
                   if [[ $mysum -eq $fsum ]];then
                        nprog=$(($nprog+1))
                   fi
                fi
        fi
   done

   if [[ $nprog -gt 1 ]];then
        echo $0 is already running.
        exit
   fi
}

checkinstance

#--- run your script bellow

echo pass
while true;do sleep 1000;done

1
कृपया ठीक से समझाएं कि चेकसम को हार्डकोड करना कितना अच्छा विचार है।
स्कॉट

हार्डकोडिंग चेकसम नहीं, यह आपकी स्क्रिप्ट की एकमात्र पहचान कुंजी बनाता है, जब कोई अन्य उदाहरण चल रहा होगा, तो यह अन्य शेल स्क्रिप्ट प्रक्रिया की जांच करेगा और फ़ाइल को पहले बिल्ली करेगा, यदि आपकी पहचान कुंजी उस फ़ाइल पर है, तो इसका मतलब है कि आपका उदाहरण पहले से ही चल रहा है।
शाम

ठीक है; कृपया अपना उत्तर संपादित करने के लिए उसे समझाएं। और, भविष्य में, कृपया कोड के कई 30-रेखा वाले लंबे ब्लॉक पोस्ट न करें, जो यह कहते हैं कि वे (लगभग) समान हैं, बिना कहे और समझाए कि कैसे अलग हैं। और अपनी स्क्रिप्ट के अंदर "आप हार्डकोड [सिक] सीकसुम" जैसी चीजें नहीं कह सकते हैं, और चर नामों का उपयोग करना जारी न रखें mysumऔर fsum, जब आप किसी चेकसम के बारे में बात नहीं कर रहे हों।
स्कॉट

दिलचस्प लग रहा है, धन्यवाद! और unix.stackexchange पर आपका स्वागत है :)
टोबियास किन्ज़लर

0

आप इसका उपयोग कर सकते हैं: https://github.com/sayanarijit/pidlock

sudo pip install -U pidlock

pidlock -n sleepy_script -c 'sleep 10'

> एक ऐसा समाधान जिसके लिए अतिरिक्त उपकरणों की आवश्यकता नहीं है, को प्राथमिकता दी जाएगी।
धग

0

आपको मेरा कोड

#!/bin/bash

script_file="$(/bin/readlink -f $0)"
lock_file=${script_file////_}

function executing {
  echo "'${script_file}' already executing"
  exit 1
}

(
  flock -n 9 || executing

  sleep 10

) 9> /var/lock/${lock_file}

man flockकेवल सुधार के आधार पर :

  • स्क्रिप्ट के पूर्ण नाम के आधार पर लॉक फ़ाइल का नाम
  • संदेश executing

जहां मैंने यहां रखा है sleep 10, आप सभी मुख्य स्क्रिप्ट रख सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.