बैश में दिए गए टाइमआउट के बाद बच्चे की प्रक्रिया को कैसे मारें?


178

मेरे पास एक बैश स्क्रिप्ट है जो एक बच्चे की प्रक्रिया को लॉन्च करती है जो समय-समय पर दुर्घटनाग्रस्त हो जाती है (वास्तव में, लटकती है) और बिना किसी स्पष्ट कारण के (बंद स्रोत, इसलिए बहुत कुछ नहीं है जो मैं इसके बारे में कर सकता हूं)। नतीजतन, मैं इस प्रक्रिया को दिए गए समय के लिए लॉन्च करने में सक्षम होना चाहूंगा, और इसे मार सकता हूं अगर यह किसी दिए गए समय के बाद सफलतापूर्वक वापस नहीं आया।

क्या बैश का उपयोग करने का एक सरल और मजबूत तरीका है?

PS: मुझे बताएं कि क्या यह प्रश्न सर्वरफॉल्ट या सुपरयुसर के लिए बेहतर है।



बहुत पूर्ण प्रतिक्रिया यहाँ: stackoverflow.com/a/58873049/2635443
ओरसिरिस डे जोंग

जवाबों:


260

(जैसा कि इसमें देखा गया है: BASH FAQ प्रविष्टि # 68: "मैं एक कमांड कैसे चला सकता हूं, और क्या यह एनॉर्ट है (एन सेकंड के बाद टाइमआउट?" )

यदि आपको कुछ डाउनलोड करने में कोई आपत्ति नहीं है, तो उपयोग करें timeout( sudo apt-get install timeout) और इसका उपयोग करें जैसे: (अधिकांश सिस्टम ने इसे पहले ही इंस्टॉल कर लिया है अन्यथा उपयोग करें sudo apt-get install coreutils)

timeout 10 ping www.goooooogle.com

यदि आप कुछ डाउनलोड नहीं करना चाहते हैं, तो आंतरिक रूप से टाइमआउट क्या करें:

( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec ping www.goooooogle.com )

यदि आप लंबे समय तक बैश कोड के लिए टाइमआउट करना चाहते हैं, तो दूसरे विकल्प का उपयोग करें:

( cmdpid=$BASHPID; 
    (sleep 10; kill $cmdpid) \
   & while ! ping -w 1 www.goooooogle.com 
     do 
         echo crap; 
     done )

8
किसी और के आश्चर्य के मामले में रे इग्नासियो का जवाब: मैंने क्या किया है: कॉलिंग शेल cmdpid=$BASHPIDके पीड को नहीं लेगा लेकिन (पहला) सबस्क्रिप्शन जो इसके द्वारा शुरू किया गया है । ... बात पृष्ठभूमि में 10 सेकेंड तक प्रतीक्षा करें और पहले subshell जो, हत्यारा subshell प्रक्रिया शुरू की करने के बाद, अपने काम का बोझ निष्पादित करने के लिए आगे बढ़ता है को मारने के लिए पहले subshell के भीतर दूसरी subshell कॉल ...()(sleep
जमदग्नि

17
timeoutGNU कोरुटिल का हिस्सा है, इसलिए पहले से ही सभी GNU सिस्टम में स्थापित होना चाहिए।
समीर

1
@Sameer: ​​केवल संस्करण 8 के रूप में
इग्नासियो

3
मुझे उस पर 100% यकीन नहीं है, लेकिन जहां तक ​​मुझे पता है (और मुझे पता है कि मेरे मैनपेज ने मुझे क्या बताया) timeoutअब कोर्यूटिल्स का हिस्सा है।
बेनिरॉर्ग

5
यह कमांड 'जल्दी खत्म' नहीं करता है। यह हमेशा टाइमआउट पर प्रक्रिया को मार देगा - लेकिन उस स्थिति को नहीं संभालेगा जहां यह समय समाप्त नहीं हुआ था।
बाज़ी

28
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) &

या बाहर निकलने के कोड प्राप्त करने के लिए:

# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) & waiter=$!
# wait on our worker process and return the exitcode
exitcode=$(wait $pid && echo $?)
# kill the waiter subshell, if it still runs
kill -9 $waiter 2>/dev/null
# 0 if we killed the waiter, cause that means the process finished before the waiter
finished_gracefully=$?

8
kill -9सिग्नल का प्रयास करने से पहले आपको इसका उपयोग नहीं करना चाहिए जो एक प्रक्रिया पहले प्रक्रिया कर सकती है।
अगली सूचना तक रोक दिया गया।

यह सच है, मैं एक तेजी से ठीक करने के लिए जा रहा था, लेकिन सिर्फ यह मान लिया कि वह प्रक्रिया को तुरंत मरना चाहता है क्योंकि उसने कहा कि यह दुर्घटनाग्रस्त हो जाता है
Dan

8
यह वास्तव में एक बहुत बुरा समाधान है। क्या होगा यदि dosmth2 सेकंड में समाप्त हो जाता है, एक और प्रक्रिया पुरानी पीड लेती है, और आप नए को मारते हैं?
Teleporting बकरी

PID पुनर्चक्रण सीमा तक पहुंचकर और चारों ओर लपेटकर काम करता है। जब तक कि सिस्टम पूरी तरह से हयवायर नहीं हो जाता है, तब तक शेष 8 सेकंड के भीतर पीआईडी ​​का पुन: उपयोग करने के लिए एक अन्य प्रक्रिया के लिए यह बहुत संभावना नहीं है।
किट्टयूर

13
sleep 999&
t=$!
sleep 10
kill $t

यह अत्यधिक प्रतीक्षा कर रहा है। क्या होगा यदि एक वास्तविक कमांड ( sleep 999यहां) अक्सर लगाए गए नींद ( sleep 10) से अधिक तेजी से खत्म हो जाता है ? क्या होगा अगर मैं इसे 1 मिनट, 5 मिनट तक का मौका देना चाहता हूं? क्या होगा अगर मेरी स्क्रिप्ट में इस तरह के मामलों का एक समूह है :)
it3xl

3

मेरे पास भी यह सवाल था और दो और चीजों को बहुत उपयोगी पाया:

  1. संघर्ष में SECONDS चर।
  2. कमांड "pgrep"।

इसलिए मैं कमांड लाइन (OSX 10.9) पर कुछ इस तरह का उपयोग करता हूं:

ping www.goooooogle.com & PING_PID=$(pgrep 'ping'); SECONDS=0; while pgrep -q 'ping'; do sleep 0.2; if [ $SECONDS = 10 ]; then kill $PING_PID; fi; done

जैसा कि यह एक लूप है मैंने सीपीयू को ठंडा रखने के लिए "स्लीप 0.2" को शामिल किया। ;-)

(BTW: पिंग वैसे भी एक बुरा उदाहरण है, आप सिर्फ बिल्ट-इन "-t" (टाइमआउट) विकल्प का उपयोग करेंगे।)


1

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

यदि आपको अधिक विवरण की आवश्यकता है तो मुझे बताएं। अगर वह आवाज नहीं करता है तो यह आपकी आवश्यकताओं के अनुरूप होगा, ऊपर की ओर क्या ?


1

एक तरीका यह है कि प्रोग्राम को एक सबशेल में चलाएं, और readकमांड के साथ नामित पाइप के माध्यम से सबमेल के साथ संवाद करें । इस तरह से आप चल रही प्रक्रिया की निकास स्थिति की जांच कर सकते हैं और पाइप के माध्यम से इस वापस संचार कर सकते हैं।

यहां yes3 सेकंड के बाद कमांड को टाइम करने का एक उदाहरण दिया गया है । इसका उपयोग करके प्रक्रिया का पीआईडी ​​मिलता है pgrep(संभवतः केवल लिनक्स पर काम करता है)। पाइप का उपयोग करने के साथ कुछ समस्या भी है कि पढ़ने के लिए एक पाइप खोलने की प्रक्रिया तब तक लटकाएगी जब तक कि इसे लिखने के लिए भी नहीं खोला जाता है, और इसके विपरीत। इसलिए readकमांड को लटकने से रोकने के लिए , मैंने "वेज" किया है, जो कि पृष्ठभूमि के साथ पढ़ने के लिए पाइप को खोलें। (पाइप रीड-राइट खोलने के लिए फ्रीज को रोकने का एक और तरीका है, अर्थात read -t 5 <>finished.pipe- हालांकि, यह भी लिनक्स के अलावा काम नहीं कर सकता है।)

rm -f finished.pipe
mkfifo finished.pipe

{ yes >/dev/null; echo finished >finished.pipe ; } &
SUBSHELL=$!

# Get command PID
while : ; do
    PID=$( pgrep -P $SUBSHELL yes )
    test "$PID" = "" || break
    sleep 1
done

# Open pipe for writing
{ exec 4>finished.pipe ; while : ; do sleep 1000; done } &  

read -t 3 FINISHED <finished.pipe

if [ "$FINISHED" = finished ] ; then
  echo 'Subprocess finished'
else
  echo 'Subprocess timed out'
  kill $PID
fi

rm finished.pipe

0

यहां एक प्रयास है जो एक प्रक्रिया को मारने से बचने की कोशिश करता है क्योंकि यह पहले से ही बाहर निकल चुका है, जो एक ही प्रक्रिया आईडी के साथ एक और प्रक्रिया को मारने की संभावना को कम करता है (हालांकि इस तरह की त्रुटि से पूरी तरह से बचना असंभव है)।

run_with_timeout ()
{
  t=$1
  shift

  echo "running \"$*\" with timeout $t"

  (
  # first, run process in background
  (exec sh -c "$*") &
  pid=$!
  echo $pid

  # the timeout shell
  (sleep $t ; echo timeout) &
  waiter=$!
  echo $waiter

  # finally, allow process to end naturally
  wait $pid
  echo $?
  ) \
  | (read pid
     read waiter

     if test $waiter != timeout ; then
       read status
     else
       status=timeout
     fi

     # if we timed out, kill the process
     if test $status = timeout ; then
       kill $pid
       exit 99
     else
       # if the program exited normally, kill the waiting shell
       kill $waiter
       exit $status
     fi
  )
}

जैसे उपयोग करें run_with_timeout 3 sleep 10000, जो चलाता है sleep 10000लेकिन इसे 3 सेकंड के बाद समाप्त करता है।

यह अन्य उत्तरों की तरह है जो देरी के बाद बच्चे की प्रक्रिया को मारने के लिए एक पृष्ठभूमि टाइमआउट प्रक्रिया का उपयोग करते हैं। मुझे लगता है कि यह दान के विस्तारित उत्तर ( https://stackoverflow.com/a/5161274/1351983 ) के लगभग समान है , सिवाय टाइमआउट शेल के अगर इसे पहले ही समाप्त हो गया है तो नहीं मारा जाएगा।

इस कार्यक्रम के समाप्त होने के बाद, अभी भी कुछ सुस्त "नींद" प्रक्रियाएं चल रही हैं, लेकिन उन्हें हानिरहित होना चाहिए।

यह मेरे अन्य उत्तर की तुलना में बेहतर समाधान हो सकता है क्योंकि यह गैर-पोर्टेबल शेल सुविधा read -tका उपयोग नहीं करता है और इसका उपयोग नहीं करता है pgrep


बीच क्या अंतर है (exec sh -c "$*") &और sh -c "$*" &? विशेष रूप से, बाद के बजाय पूर्व का उपयोग क्यों करें?
जस्टिन सी

0

यहाँ प्रस्तुत तीसरा उत्तर है। यह एक सिग्नल इंटरप्ट को हैंडल करता है और SIGINTप्राप्त होने पर पृष्ठभूमि प्रक्रियाओं को साफ करता है। यह एक प्रक्रिया का पीआईडी ​​प्राप्त करने के लिए शीर्ष उत्तर में उपयोग की जाने वाली चाल $BASHPIDऔर execचाल का उपयोग करता है (इस मामले में एक आह्वान में)। यह एक FIFO का उपयोग एक उप-समूह के साथ संवाद करने के लिए करता है जो हत्या और सफाई के लिए जिम्मेदार है। (यह मेरे दूसरे उत्तर में पाइप की तरह है , लेकिन नामांकित पाइप होने का मतलब है कि सिग्नल हैंडलर इसमें लिख सकता है)।)$$sh

run_with_timeout ()
{
  t=$1 ; shift

  trap cleanup 2

  F=$$.fifo ; rm -f $F ; mkfifo $F

  # first, run main process in background
  "$@" & pid=$!

  # sleeper process to time out
  ( sh -c "echo \$\$ >$F ; exec sleep $t" ; echo timeout >$F ) &
  read sleeper <$F

  # control shell. read from fifo.
  # final input is "finished".  after that
  # we clean up.  we can get a timeout or a
  # signal first.
  ( exec 0<$F
    while : ; do
      read input
      case $input in
        finished)
          test $sleeper != 0 && kill $sleeper
          rm -f $F
          exit 0
          ;;
        timeout)
          test $pid != 0 && kill $pid
          sleeper=0
          ;;
        signal)
          test $pid != 0 && kill $pid
          ;;
      esac
    done
  ) &

  # wait for process to end
  wait $pid
  status=$?
  echo finished >$F
  return $status
}

cleanup ()
{
  echo signal >$$.fifo
}

मैंने जहां तक ​​हो सके दौड़ की स्थितियों से बचने की कोशिश की है। हालाँकि, त्रुटि का एक स्रोत जिसे मैं नहीं हटा सकता था, जब प्रक्रिया समयावधि के समीप समाप्त हो जाती है। उदाहरण के लिए, run_with_timeout 2 sleep 2या run_with_timeout 0 sleep 0। मेरे लिए, बाद वाला एक त्रुटि देता है:

timeout.sh: line 250: kill: (23248) - No such process

जैसा कि यह एक प्रक्रिया को मारने की कोशिश कर रहा है जो पहले से ही खुद से बाहर निकल चुका है।

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