วิธีฆ่ากระบวนการเด็กหลังจากหมดเวลาที่กำหนดใน Bash?


178

ฉันมีสคริปต์ทุบตีที่เปิดใช้กระบวนการเด็กที่ล่ม (อันที่จริงแฮงค์) เป็นครั้งคราวและไม่มีเหตุผลที่ชัดเจน (แหล่งปิดดังนั้นจึงไม่มีอะไรที่ฉันสามารถทำได้) เป็นผลให้ฉันต้องการที่จะเปิดตัวกระบวนการนี้ตามระยะเวลาที่กำหนดและฆ่ามันถ้ามันไม่ได้กลับมาสำเร็จหลังจากระยะเวลาที่กำหนด

มีวิธีที่ง่ายและมีประสิทธิภาพเพื่อให้บรรลุโดยใช้ทุบตี?

PS: บอกฉันว่าคำถามนี้เหมาะกับ serverfault หรือ superuser หรือไม่


ที่เกี่ยวข้อง: stackoverflow.com/q/601543/132382
pilcrow

การตอบสนองที่สมบูรณ์มากที่นี่: stackoverflow.com/a/58873049/2635443
Orsiris de Jong

คำตอบ:


260

(ดังที่เห็นใน: รายการคำถามที่พบบ่อย BASH # 68: "ฉันจะรันคำสั่งได้อย่างไรและให้ยกเลิก (หมดเวลา) หลังจาก N วินาที" )

หากคุณไม่สนใจดาวน์โหลดสิ่งใดให้ใช้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 )

ในกรณีที่คุณต้องการหมดเวลาสำหรับรหัส bash ที่ยาวขึ้นให้ใช้ตัวเลือกที่สองดังนี้:

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

8
ตอบเรื่องอิกนาซิโอในกรณีที่คนอื่นสิ่งมหัศจรรย์สิ่งที่ฉันได้คือcmdpid=$BASHPIDจะไม่ใช้ pid ของการเรียกเปลือก แต่ (ตอนแรก) subshell ()ที่ตั้งขึ้นโดย (sleep... สิ่งที่เรียก subshell สองภายใน subshell แรกที่จะรอ 10 วินาทีในพื้นหลังและฆ่า subshell แรกซึ่งหลังจากมีการเปิดตัวกระบวนการ subshell ฆาตกรรายได้ในการดำเนินการภาระงานของ ...
Jamadagni

17
timeoutเป็นส่วนหนึ่งของ coreutils ของ GNU ดังนั้นควรติดตั้งในระบบ GNU ทั้งหมดแล้ว
Sameer

1
@Sameer: ​​เฉพาะกับรุ่น 8
Ignacio Vazquez-Abrams

3
ฉันไม่แน่ใจ 100% แต่เท่าที่ฉันรู้ (และฉันรู้ว่า manpage ของฉันบอกฉัน) timeoutตอนนี้เป็นส่วนหนึ่งของ coreutils
benaryorg

5
คำสั่งนี้ไม่ 'เสร็จสิ้นเร็ว' มันจะฆ่ากระบวนการเมื่อหมดเวลาเสมอ - แต่จะไม่จัดการกับสถานการณ์ที่ไม่ได้หมดเวลา
Hawkeye

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
นั่นเป็นทางออกที่เลวร้ายจริงๆ จะเกิดอะไรขึ้นถ้าdosmthยุติลงใน 2 วินาทีกระบวนการอื่นจะใช้ pid เก่าและคุณฆ่าอันใหม่
ไกลแพะ

การรีไซเคิล PID ทำงานได้ถึงขีด จำกัด และล้อมรอบ มันไม่น่าเป็นไปได้ที่กระบวนการอื่นจะนำ PID กลับมาใช้ใหม่ภายใน 8 วินาทีที่เหลือเว้นแต่ว่าระบบกำลังยุ่งเหยิงอย่างสมบูรณ์
kittydoor

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

เนื่องจากนี่เป็นลูปฉันจึงรวม "sleep 0.2" เพื่อให้ CPU เย็นอยู่ ;-)

(BTW: ping เป็นตัวอย่างที่ไม่ดีอยู่แล้วคุณจะใช้ตัวเลือก "-t" (หมดเวลา) ในตัว)


1

สมมติว่าคุณมี (หรือสามารถสร้าง) ไฟล์ pid สำหรับติดตามเด็กของ pid ได้จากนั้นคุณสามารถสร้างสคริปต์ที่ตรวจสอบ modtime ของไฟล์ pid และฆ่ากระบวนการเกิดใหม่ได้ตามต้องการ จากนั้นให้วางสคริปต์ไว้ใน crontab เพื่อให้ทำงานตามระยะเวลาที่คุณต้องการ

แจ้งให้เราทราบหากคุณต้องการรายละเอียดเพิ่มเติม ถ้ามันฟังดูไม่เหมาะกับความต้องการของคุณล่ะ


1

วิธีหนึ่งคือการเรียกใช้โปรแกรมใน subshell และสื่อสารกับ subshell ผ่านไปป์ที่มีชื่อด้วยreadคำสั่ง วิธีนี้คุณสามารถตรวจสอบสถานะการออกของกระบวนการที่กำลังทำงานอยู่และสื่อสารกลับผ่านไพพ์

นี่คือตัวอย่างของการจับเวลาyesคำสั่งหลังจาก 3 วินาที มันได้รับ PID ของกระบวนการที่ใช้pgrep(อาจใช้ได้กับ Linux เท่านั้น) นอกจากนี้ยังมีปัญหาบางอย่างกับการใช้ไพพ์ในกระบวนการที่เปิดไพพ์สำหรับการอ่านจะหยุดทำงานจนกว่ามันจะถูกเปิดเพื่อการเขียนและในทางกลับกัน ดังนั้นเพื่อป้องกันreadคำสั่งหยุดทำงานฉันได้ "เปิดใช้งาน" เพื่อเปิดไปป์สำหรับอ่านด้วย subshell พื้นหลัง (อีกวิธีหนึ่งในการป้องกันการค้างเพื่อเปิดการอ่าน - เขียนไพพ์เช่นread -t 5 <>finished.pipe- อย่างไรก็ตามอาจไม่ทำงานยกเว้นกับ Linux)

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
  )
}

ใช้ like run_with_timeout 3 sleep 10000ซึ่งทำงานsleep 10000แต่จบลงหลังจาก 3 วินาที

นี่เป็นเหมือนคำตอบอื่น ๆ ที่ใช้กระบวนการหมดเวลาแบ็กกราวน์เพื่อฆ่ากระบวนการลูกหลังจากเกิดความล่าช้า ฉันคิดว่านี่เกือบจะเหมือนกับคำตอบเพิ่มเติมของ Dan ( https://stackoverflow.com/a/5161274/1351983 ) ยกเว้นว่าการหมดเวลาของเชลล์จะไม่ถูกฆ่าถ้ามันสิ้นสุดลงแล้ว

หลังจากโปรแกรมนี้สิ้นสุดลงแล้วจะยังมีกระบวนการ "sleep" ที่ยังทำงานอยู่ แต่จะไม่เป็นอันตราย

นี้อาจจะเป็นทางออกที่ดีกว่าคำตอบอื่น ๆ ของฉันเพราะมันไม่ได้ใช้คุณลักษณะเปลือกไม่ใช่แบบพกพาและไม่ได้ใช้read -tpgrep


ความแตกต่างระหว่าง(exec sh -c "$*") &และsh -c "$*" &คืออะไร ทำไมใช้อดีตแทนแบบหลัง?
Justin C

0

นี่คือคำตอบที่สามที่ฉันส่งมา อันนี้จัดการสัญญาณขัดจังหวะและทำความสะอาดกระบวนการพื้นหลังเมื่อSIGINTได้รับ มันใช้$BASHPIDและexecเคล็ดลับที่ใช้ในคำตอบด้านบนเพื่อรับ PID ของกระบวนการ (ในกรณีนี้$$ในการshอุทธรณ์) มันใช้ FIFO เพื่อสื่อสารกับ subshell ที่รับผิดชอบในการฆ่าและล้างข้อมูล (นี่เป็นเหมือนไพพ์ในคำตอบที่สองของฉันแต่การมีไพพ์ที่มีชื่อหมายความว่าตัวจัดการสัญญาณสามารถเขียนลงไปได้ด้วย

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

เนื่องจากมันพยายามที่จะฆ่ากระบวนการที่ได้ออกไปแล้วโดยตัวมันเอง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.