หมดเวลาในเชลล์สคริปต์


53

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

สคริปต์นี้ต้องพกพาได้มากรวมถึงระบบยูนิกซ์ในศตวรรษที่ 20 ที่ไม่มีคอมไพเลอร์ C และอุปกรณ์ฝังตัวที่ใช้งานอยู่ในขณะนี้ดังนั้น Perl, ทุบตี, ภาษาใด ๆ ที่คอมไพล์และแม้แต่ POSIX.2 แบบเต็มไม่สามารถพึ่งพาได้ โดยเฉพาะอย่างยิ่ง$PPID, read -tและกับดักที่ดีเลิศ POSIX สอดคล้องไม่สามารถใช้ได้ การเขียนไปยังไฟล์ชั่วคราวจะถูกแยกออกเช่นกัน สคริปต์อาจทำงานแม้ว่าระบบไฟล์ทั้งหมดจะถูกเมาท์แบบอ่านอย่างเดียว

เพียงเพื่อทำให้สิ่งต่าง ๆ ยากขึ้นฉันยังต้องการให้สคริปต์มีความรวดเร็วพอสมควรเมื่อไม่ได้หมดเวลา โดยเฉพาะอย่างยิ่งฉันยังใช้สคริปต์ใน Windows (ส่วนใหญ่ใน Cygwin) โดยที่ fork และ exec มีระดับต่ำโดยเฉพาะดังนั้นฉันจึงต้องการใช้ให้น้อยที่สุด

โดยสรุปฉันมี

trap cleanup 1 2 3 15
foo=`cat`

และฉันต้องการเพิ่มการหมดเวลา ฉันไม่สามารถแทนที่catด้วยในreadตัว ในกรณีที่หมดเวลาฉันต้องการใช้งานcleanupฟังก์ชั่น


พื้นหลัง: สคริปต์นี้คาดเดาการเข้ารหัสของเครื่องโดยพิมพ์อักขระ 8 บิตและเปรียบเทียบตำแหน่งเคอร์เซอร์ก่อนและหลัง จุดเริ่มต้นของการทดสอบสคริปต์ที่ stdout เชื่อมต่อกับเทอร์มินัลที่ได้รับการสนับสนุน แต่บางครั้งสภาพแวดล้อมก็กำลังโกหก (เช่นการplinkตั้งค่าTERM=xtermแม้ว่าจะถูกเรียกด้วยTERM=dumb ) ส่วนที่เกี่ยวข้องของสคริปต์มีลักษณะดังนี้:

text='Éé'  # UTF-8; shows up as Ãé on a latin1 terminal
csi='␛['; dsr_cpr="${csi}6n"; dsr_ok="${csi}5n"  # ␛ is an escape character
stty_save=`stty -g`
cleanup () { stty "$stty_save"; }
trap 'cleanup; exit 120' 0 1 2 3 15     # cleanup code
stty eol 0 eof n -echo                # Input will end with `0n`
# echo-n is a function that outputs its argument without a newline
echo-n "$dsr_cpr$dsr_ok"              # Ask the terminal to report the cursor position
initial_report=`tr -dc \;0123456789`  # Expect ␛[42;10R␛[0n for y=42,x=10
echo-n "$text$dsr_cpr$dsr_ok"
final_report=`tr -dc \;0123456789`
cleanup
# Compute and return initial_x - final_x

ฉันจะแก้ไขสคริปต์อย่างไรหากtrไม่ได้อ่านอินพุตหลังจาก 2 วินาทีสคริปต์จะถูกฆ่าและสคริปต์จะเรียกใช้cleanupฟังก์ชัน


2
ดูเพิ่มเติมวิธีการแนะนำการหมดเวลาใช้งานของเชลล์สคริปต์ สำหรับโซลูชันแบบพกพาที่น้อยกว่า
Gilles 'หยุดความชั่วร้าย'

คำตอบ:


33

เกี่ยวกับสิ่งนี้:

foo=`{ { cat 1>&3; kill 0; } | { sleep 2; kill 0; } } 3>&1`

นั่นคือ: เรียกใช้คำสั่งที่เอาต์พุตและsleepในกลุ่มกระบวนการเดียวกันกลุ่มกระบวนการสำหรับพวกเขา คำสั่งใดก็ตามที่ส่งคืนอันดับแรกจะฆ่ากลุ่มกระบวนการทั้งหมด

มีใครสงสัยบ้างไหม: ใช่ไม่ได้ใช้ไพพ์ มันข้ามโดยใช้การเปลี่ยนเส้นทาง วัตถุประสงค์เพียงอย่างเดียวของมันคือการให้เชลล์รันสองกระบวนการในกลุ่มกระบวนการเดียวกัน


ตามที่ Gilles ชี้ให้เห็นในความคิดเห็นของเขาสิ่งนี้จะไม่ทำงานในเชลล์สคริปต์เนื่องจากกระบวนการสคริปต์จะถูกฆ่าพร้อมกับกระบวนการย่อยสองรายการ

วิธีหนึ่งในการบังคับให้คำสั่งทำงานในกลุ่มกระบวนการแยกต่างหากคือเริ่มเชลล์แบบโต้ตอบใหม่:

#!/bin/sh
foo=`sh -ic '{ cat 1>&3; kill 0; } | { sleep 2; kill 0; }' 3>&1 2>/dev/null`
[ -n "$foo" ] && echo got: "$foo" || echo timeouted

แต่อาจมีข้อควรระวังในเรื่องนี้ (เช่นเมื่อ stdin ไม่ใช่ tty?) การเปลี่ยนเส้นทาง stderr จะมีการกำจัดข้อความ "ยุติ" เมื่อเปลือกโต้ตอบถูกฆ่าตาย

ทดสอบกับzsh, และbash dashแต่สิ่งที่เกี่ยวกับเนียร์

B98แนะนำการเปลี่ยนแปลงต่อไปนี้ทำงานบน Mac OS X ด้วย GNU bash 3.2.57 หรือ Linux พร้อมเส้นประ:

foo=`sh -ic 'exec 3>&1 2>/dev/null; { cat 1>&3; kill 0; } | { sleep 2; kill 0; }'`

-
1. นอกเหนือจากsetsidที่ดูเหมือนจะไม่เป็นมาตรฐาน


1
ฉันชอบสิ่งนี้มากฉันไม่เคยคิดที่จะใช้กลุ่มกระบวนการเลย มันค่อนข้างง่ายและไม่มีสภาพการแข่งขัน ฉันต้องทดสอบเพื่อความสะดวกในการพกพา แต่ดูเหมือนว่าจะเป็นผู้ชนะ
Gilles 'หยุดชั่วร้าย'

โชคไม่ดีสิ่งนี้ล้มเหลวอย่างน่าทึ่งทันทีที่ฉันใส่มันลงในสคริปต์: ไพพ์ไลน์ในการทดแทนคำสั่งไม่ได้ทำงานในกลุ่มกระบวนการของตัวเองและkill 0จบลงด้วยการฆ่าผู้เรียกของสคริปต์เช่นกัน มีวิธีพกพาเพื่อบังคับให้ไปป์ไลน์ในกลุ่มกระบวนการของตัวเองหรือไม่?
Gilles 'ดังนั้น - หยุดความชั่วร้าย'

@Gilles: Eek! ไม่สามารถหาวิธีที่จะทำได้setprgp()โดยไม่ได้setsid:-(
Stéphane Gimenez

1
ฉันชอบกลอุบายดังนั้นฉันจึงตัดสินความโปรดปราน ดูเหมือนว่าจะทำงานบน Linux ฉันยังไม่มีเวลาทดสอบบนระบบอื่น ๆ
Gilles 'หยุดความชั่วร้าย'

2
@Zac: มันเป็นไปไม่ได้ที่จะกลับคำสั่งซื้อในกรณีเดิมเพราะเพียงกระบวนการแรกเท่านั้นที่เข้าถึง stdin
Stéphane Gimenez

6
me=$$
(sleep 2; kill $me >/dev/null 2>&1) & nuker=$!
# do whatever
kill $nuker >/dev/null 2>&1

คุณกำลังดัก 15 อยู่แล้ว (เวอร์ชันที่เป็นตัวเลขSIGTERMซึ่งเป็นสิ่งที่killจะส่งเว้นแต่จะมีการแจ้งเป็นอย่างอื่น) ดังนั้นคุณควรจะไปได้ดี ที่กล่าวว่าหากคุณกำลังดู POSIX ล่วงหน้าโปรดทราบว่าฟังก์ชั่นเชลล์อาจไม่มีอยู่ (ซึ่งมาจากเชลล์ของ System V)


มันไม่ง่ายอย่างนั้น หากคุณดักสัญญาณเปลือกหอยจำนวนมาก (เส้นประ, ทุบตี, pdksh, zsh ... น่าจะเป็นทั้งหมดยกเว้น ATT ksh) ไม่สนใจมันในขณะที่พวกเขากำลังรอcatออก ฉันได้ทดลองเล็กน้อยแล้ว แต่ไม่พบสิ่งใดที่ฉันพอใจ
Gilles 'หยุดความชั่วร้าย'

เกี่ยวกับการพกพา: ขอบคุณที่ฉันมีฟังก์ชั่นอยู่ทุกที่ ผมไม่แน่ใจว่าเกี่ยวกับ$!ผมคิดว่าบางส่วนของเครื่องที่ผมใช้ไม่ค่อยได้มีการควบคุมงานคือ$!สามารถใช้ได้ในระดับสากล?
Gilles 'หยุดความชั่วร้าย'

@Gilles: อืมใช่แล้ว ผมจำบางอย่างแท้จริงน่าเกลียดและbashสิ่งที่ฉันไม่ -specific -o monitorครั้งเดียวที่เกี่ยวข้องกับการละเมิดของ ฉันคิดในแง่ของกระสุนโบราณอย่างแท้จริงเมื่อฉันเขียนมัน (มันใช้งานได้ใน v7) ที่กล่าวว่าฉันคิดว่าคุณสามารถทำอย่างใดอย่างหนึ่งในสองสิ่งอื่น ๆ : (1) พื้นหลัง "อะไรก็ตาม" และwait $!หรือ (2) ยังส่งSIGCLD/ SIGCHLD... แต่ในเครื่องเก่าพอพอหลังเป็นทั้งไม่มีอยู่หรือไม่พกพา (อดีตคือระบบ III / V, BSD หลัง, และ V7 ไม่มี)
geekosaur

@Gilles: $!กลับไปที่ V7 เป็นอย่างน้อยและแน่นอนก่อนหน้านี้shเหมือนที่รู้อะไรเกี่ยวกับการควบคุมงาน (ในความเป็นจริงเป็นเวลานาน/bin/shใน BSD ไม่ได้ควบคุมงานคุณต้องวิ่งcshเพื่อให้ได้ - แต่$!มี )
geekosaur

ฉันไม่สามารถให้พื้นหลัง "อะไรก็ได้" ฉันต้องการผลลัพธ์ของมัน $!ขอบคุณสำหรับข้อมูลเกี่ยวกับ
Gilles 'หยุดความชั่วร้าย'

4

แม้ว่า coretuils ในเวอร์ชั่น 7.0 จะมีคำสั่งไทม์เอาต์ที่คุณได้กล่าวถึงสภาพแวดล้อมบางอย่างที่ไม่มี โชคดีที่pixelbeat.orgshได้หมดเวลาของสคริปต์ที่เขียน

ฉันเคยใช้มาหลายครั้งแล้วและใช้งานได้ดีมาก

http://www.pixelbeat.org/scripts/timeout ( หมายเหตุ:สคริปต์ด้านล่างนี้ได้รับการแก้ไขเล็กน้อยจากสคริปต์บน pixelbeat.org โปรดดูความคิดเห็นด้านล่างคำตอบนี้)

#!/bin/sh

# Execute a command with a timeout

# Author:
#    http://www.pixelbeat.org/
# Notes:
#    Note there is a timeout command packaged with coreutils since v7.0
#    If the timeout occurs the exit status is 124.
#    There is an asynchronous (and buggy) equivalent of this
#    script packaged with bash (under /usr/share/doc/ in my distro),
#    which I only noticed after writing this.
#    I noticed later again that there is a C equivalent of this packaged
#    with satan by Wietse Venema, and copied to forensics by Dan Farmer.
# Changes:
#    V1.0, Nov  3 2006, Initial release
#    V1.1, Nov 20 2007, Brad Greenlee <brad@footle.org>
#                       Make more portable by using the 'CHLD'
#                       signal spec rather than 17.
#    V1.3, Oct 29 2009, Ján Sáreník <jasan@x31.com>
#                       Even though this runs under dash,ksh etc.
#                       it doesn't actually timeout. So enforce bash for now.
#                       Also change exit on timeout from 128 to 124
#                       to match coreutils.
#    V2.0, Oct 30 2009, Ján Sáreník <jasan@x31.com>
#                       Rewritten to cover compatibility with other
#                       Bourne shell implementations (pdksh, dash)

if [ "$#" -lt "2" ]; then
    echo "Usage:   `basename $0` timeout_in_seconds command" >&2
    echo "Example: `basename $0` 2 sleep 3 || echo timeout" >&2
    exit 1
fi

cleanup()
{
    trap - ALRM               #reset handler to default
    kill -ALRM $a 2>/dev/null #stop timer subshell if running
    kill $! 2>/dev/null &&    #kill last job
      exit 124                #exit with 124 if it was running
}

watchit()
{
    trap "cleanup" ALRM
    sleep $1& wait
    kill -ALRM $$
}

watchit $1& a=$!         #start the timeout
shift                    #first param was timeout for sleep
trap "cleanup" ALRM INT  #cleanup after timeout
"$@" < /dev/tty & wait $!; RET=$?    #start the job wait for it and save its return value
kill -ALRM $a            #send ALRM signal to watchit
wait $a                  #wait for watchit to finish cleanup
exit $RET                #return the value

ดูเหมือนว่าสิ่งนี้ไม่อนุญาตให้ดึงเอาท์พุทของคำสั่ง ดูที่ "ฉันไม่สามารถแสดงความเห็น" อะไรก็ตาม "" โดย Gilles ในคำตอบอื่น ๆ
Stéphane Gimenez

อ่าน่าสนใจ ฉันได้แก้ไขสคริปต์ (ดังนั้นจะไม่จับคู่กับ pixelbeat อีกต่อไป) เพื่อเปลี่ยนเส้นทาง / dev / stdin เป็นคำสั่ง ดูเหมือนว่าจะทำงานในการทดสอบของฉัน
บาฮามาต

สิ่งนี้ใช้ไม่ได้กับการอ่านคำสั่งจากอินพุตมาตรฐานยกเว้น (ผิดปกติ) เป็น bash </dev/stdinเป็น no-op </dev/ttyจะช่วยให้สามารถอ่านจากเทอร์มินัลซึ่งดีพอสำหรับกรณีการใช้งานของฉัน
Gilles 'หยุดความชั่วร้าย' ใน

@Giles: เยี่ยมมากฉันจะทำการอัปเดตนั้น
บาฮามาต

สิ่งนี้ใช้ไม่ได้หากปราศจากความพยายามมากขึ้น: ฉันต้องดึงเอาท์พุทของคำสั่งออกมาและไม่สามารถทำได้หากคำสั่งนั้นอยู่ในพื้นหลัง
Gilles 'หยุดความชั่วร้าย' ใน

3

เกี่ยวกับ (ab) ใช้ NC สำหรับสิ่งนี้

ชอบ;

   $ nc -l 0 2345 | cat &  # output come from here
   $ nc -w 5 0 2345   # input come from here and times out after 5 sec

หรือรีดเป็นบรรทัดคำสั่งเดียว

   $ foo=`nc -l 0 2222 | nc -w 5 0 2222`

เวอร์ชันล่าสุดแม้ว่าการค้นหา stange จะใช้งานได้จริงเมื่อฉันทดสอบบนระบบ linux แต่สิ่งที่ดีที่สุดก็คือควรใช้กับระบบใดก็ได้และหากไม่มีการเปลี่ยนเส้นทางของเอาต์พุตอาจช่วยแก้ปัญหาการพกพาได้ ประโยชน์ที่นี่คือไม่มีกระบวนการพื้นหลังที่เกี่ยวข้อง


พกพาไม่พอ ฉันไม่มีncใน Unix boxen หรือ Linux ที่ฝังอยู่หลายตัว
Gilles 'ดังนั้น - หยุดความชั่วร้าย'

0

อีกวิธีในการรันไปป์ไลน์ในกลุ่มกระบวนการของตัวเองคือการรันsh -c '....'ในสถานีหลอกโดยใช้scriptคำสั่ง (ซึ่งใช้setsidฟังก์ชันโดยปริยาย)

#!/bin/sh
stty -echo -onlcr
# GNU script
foo=`script -q -c 'sh -c "{ cat 1>&3; kill 0; } | { sleep 5; kill 0; }" 3>&1 2>/dev/null' /dev/null`
# FreeBSD script
#foo=`script -q /dev/null sh -c '{ cat 1>&3; kill 0; } | { sleep 5; kill 0; }' 3>&1 2>/dev/null`
stty echo onlcr
echo "foo: $foo"


# alternative without: stty -echo -onlcr
# cr=`printf '\r'`
# foo=`script -q -c 'sh -c "{ { cat 1>&3; kill 0; } | { sleep 5; kill 0; } } 3>&1 2>/dev/null"' /dev/null | sed -e "s/${cr}$//" -ne 'p;N'`  # GNU
# foo=`script -q /dev/null sh -c '{ { cat 1>&3; kill 0; } | { sleep 5; kill 0; } } 3>&1 2>/dev/null' | sed -e "s/${cr}$//" -ne 'p;N'`  # FreeBSD
# echo "foo: $foo"

พกพาไม่พอ ฉันไม่มีscriptใน Unix boxen หรือ Linux ที่ฝังอยู่หลายตัว
Gilles 'หยุดชั่วร้าย'

0

คำตอบในhttps://unix.stackexchange.com/a/18711นั้นดีมาก

ฉันต้องการบรรลุผลลัพธ์ที่คล้ายกัน แต่ไม่ต้องเรียกใช้เชลล์อีกครั้งอย่างชัดเจนเพราะฉันต้องการเรียกใช้ฟังก์ชันเชลล์ที่มีอยู่

เมื่อใช้ bash คุณสามารถทำสิ่งต่อไปนี้ได้:

eval 'set -m ; ( ... ) ; '"$(set +o)"

สมมติว่าฉันมีฟังก์ชั่นเชลล์อยู่แล้วf:

f() { date ; kill 0 ; }

echo Before
eval 'set -m ; ( f ) ; '"$(set +o)"
echo After

ดำเนินการนี้ฉันเห็น:

$ sh /tmp/foo.sh
Before
Mon 14 Mar 2016 17:22:41 PDT
/tmp/foo.sh: line 4: 17763 Terminated: 15          ( f )
After

-2
timeout_handler () { echo Timeout. goodbye.;  exit 1; }
trap timeout_handler ALRM
( sleep 60; kill -ALRM $$ ) &

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