Bagaimana cara memperkenalkan batas waktu untuk skrip shell?

35

Saya ingin menjalankan skrip shell yang memiliki loop di dalamnya dan itu bisa berjalan selamanya yang saya tidak ingin terjadi. Jadi saya perlu memperkenalkan batas waktu untuk keseluruhan skrip.

Bagaimana saya bisa memperkenalkan batas waktu untuk seluruh skrip shell di bawah SuSE?

Radek
sumber
4
Ini pertanyaan yang sangat samar. Bisakah Anda jelaskan batas waktu seperti apa? Apakah Anda ingin batas waktu pada seluruh skrip, atau batas waktu pada satu fungsi atau perintah? Apa yang sedang Anda coba lakukan?
Tim Kennedy
@TimKennedy: semoga lebih baik sekarang.
Radek
1
Lihat juga Timing out in a shell script (Saya tidak menganggapnya sebagai duplikat karena persyaratan portabilitas saya yang tidak termasuk alat-alat seperti timeoutatau expect)
Gilles 'SO-stop being evil'

Jawaban:

30

Jika GNU timeouttidak tersedia, Anda dapat menggunakan expect(Mac OS X, BSD, ... biasanya tidak memiliki alat dan utilitas GNU secara default).

################################################################################
# Executes command with a timeout
# Params:
#   $1 timeout in seconds
#   $2 command
# Returns 1 if timed out 0 otherwise
timeout() {

    time=$1

    # start the command in a subshell to avoid problem with pipes
    # (spawn accepts one command)
    command="/bin/sh -c \"$2\""

    expect -c "set echo \"-noecho\"; set timeout $time; spawn -noecho $command; expect timeout { exit 1 } eof { exit 0 }"    

    if [ $? = 1 ] ; then
        echo "Timeout after ${time} seconds"
    fi

}

Edit Contoh:

timeout 10 "ls ${HOME}"
Matteo
sumber
Kelihatan bagus. Bagaimana saya bisa melihat output dari perintah yang dieksekusi di layar?
Radek
Hai, output harus terlihat karena stdout tidak dialihkan ke mana pun. Saya menambahkan contoh untuk penggunaan. Dalam kasus saya, ia mencetak isi direktori home saya seperti yang diharapkan. Perintah mana yang tidak mencetak output apa pun?
Matteo
Saya tidak menyebutnya dengan benar. Ini bekerja sangat bagus! Terima kasih. Hanya itu tidak menghasilkan jumlah detik setelah batas waktu.
Radek
@ Radek Maaf, sudah diperbaiki
Matteo
1
Saya ingin menggarisbawahi bahwa jika menggunakan batas waktu GNU, status pengembalian timeoutitu sendiri adalah 124 dalam kasus batas waktu, jika tidak maka status pengembalian perintah (ini disebutkan dalam jawaban Tim Kennedy).
QuasarDonkey
15

Terimakasih atas klarifikasinya.

Cara termudah untuk mencapai apa yang Anda cari adalah menjalankan skrip Anda dengan loop di dalam wrapper seperti timeoutperintah dari paket GNU Coreutils.

root@coraid-sp:~# timeout --help            
Usage: timeout [OPTION] DURATION COMMAND [ARG]...
   or: timeout [OPTION]
Start COMMAND, and kill it if still running after DURATION.

Mandatory arguments to long options are mandatory for short options too.
  -k, --kill-after=DURATION
                   also send a KILL signal if COMMAND is still running
                   this long after the initial signal was sent.
  -s, --signal=SIGNAL
                   specify the signal to be sent on timeout.
                   SIGNAL may be a name like 'HUP' or a number.
                   See `kill -l` for a list of signals
      --help     display this help and exit
      --version  output version information and exit

DURATION is an integer with an optional suffix:
`s' for seconds(the default), `m' for minutes, `h' for hours or `d' for days.

If the command times out, then exit with status 124.  Otherwise, exit
with the status of COMMAND.  If no signal is specified, send the TERM
signal upon timeout.  The TERM signal kills any process that does not
block or catch that signal.  For other processes, it may be necessary to
use the KILL (9) signal, since this signal cannot be caught.

Report timeout bugs to [email protected]
GNU coreutils home page: <http://www.gnu.org/software/coreutils/>
General help using GNU software: <http://www.gnu.org/gethelp/>
For complete documentation, run: info coreutils 'timeout invocation'

Pada akhirnya, itu akan jauh lebih mudah daripada menulis fungsi timeout Anda sendiri, yang cangkang cenderung tidak memiliki builtin.

Tim Kennedy
sumber
Saya tidak segera menyadari bahwa saya telah menginstal ini, karena itu disebut gtimeoutpada pengaturan saya (OSX).
Joshua Goldberg
7

Luncurkan proses pengawas dari dalam skrip Anda untuk mematikan induknya jika itu berjalan terlalu lama. Contoh:

# watchdog process
mainpid=$$
(sleep 5; kill $mainpid) &
watchdogpid=$!

# rest of script
while :
do
   ...stuff...
done
kill $watchdogpid

Skrip ini akan dihentikan oleh pengawas setelah lima detik.

Kyle Jones
sumber
6
Tetapi Anda harus menunggu waktu habis setiap waktu. Dalam contoh Anda, skrip Anda akan selalu berjalan 5 detik meskipun skripnya jauh lebih pendek. Anda juga harus menyimpan PID dari watchdog dan membunuhnya di akhir.
Matteo
@ Matteo Cukup adil. Ditambahkan.
Kyle Jones
1
Perhatikan bahwa ini mungkin lebih rumit dari itu, tergantung pada apa ... hal-hal ... lakukan. Lihat Waktu dalam skrip shell
Gilles 'SO- stop being evil'
baca juga: berapa lama PID dapat dianggap unik stackoverflow.com/questions/11323410/linux-pid-recycling
Florian Castellane
3

Ada juga cratimeoutoleh Martin Cracauer.

# cf. http://www.cons.org/cracauer/software.html
# usage: cratimeout timeout_in_msec cmd args
cratimeout 5000 sleep 600
cratimeout 5000 tail -f /dev/null
cratimeout 5000 sh -c 'while sleep 1; do date; done'
jackz
sumber
2
#!/bin/sh

# Execute a command with a timeout

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
"$@"& 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
Vivek Ragupathy
sumber