2009-02-09 23 views
16

Tengo un script BASH de ejecución larga que estoy ejecutando bajo CYGWIN en Windows.¿Cómo puedo limitar el tiempo de ejecución de un script BASH?

Me gustaría limitar la secuencia de comandos para que se ejecute durante 30 segundos y terminar automáticamente si supera este límite. Idealmente, me gustaría poder hacer esto con cualquier comando.

Por ejemplo:

sh-3.2$ limittime -t 30 'myscript.sh' 

o

sh-3.2$ limittime -t 30 'grep func *.c' 

bajo Cygwin el comando ulimit no parece funcionar.

Estoy abierto a cualquier idea.

+0

@jm, vea mi respuesta actualizada para saber cómo dejar de esperar lo antes posible f timeout y child-exit -normalmente. – paxdiablo

+3

Pregunta similar, algunas respuestas diferentes: http://stackoverflow.com/questions/687948 –

Respuesta

14

Ver el guión http://www.pixelbeat.org/scripts/timeout la funcionalidad de los cuales se ha integrado en los nuevos coreutils:

#!/bin/sh 

# Execute a command with a timeout 

# License: LGPLv2 
# 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 <[email protected]> 
#      Make more portable by using the 'CHLD' 
#      signal spec rather than 17. 
# V1.3, Oct 29 2009, Ján Sáreník <[email protected]> 
#      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 <[email protected]> 
#      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 
"[email protected]"& 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 
+0

Esa es una buena secuencia de comandos. Funcionó bien bajo CYGWIN, también. –

+2

este script no funcionará si está utilizando la redirección de stdin, algo así como: ./time_limit.sh cat siemanko

2

Puede ejecutar el comando como un trabajo en segundo plano (es decir, con "&"), utilizar la variable bash para "pid del último comando ejecutado", dormir durante el tiempo requerido y ejecutar kill con ese pid.

5

Consulte this link. La idea es simplemente ejecutar myscript.sh como un subproceso de su secuencia de comandos y registrar su PID, luego matarlo si se ejecuta demasiado tiempo.

+0

No pude obtener esa muestra para ejecutar en cygwin. Obtuve: sh: línea 46: kill: SIGUSR1: especificación de señal no válida –

+0

¿Funciona si reemplaza SIGUSR1 con SIGTERM? –

+0

Esa solución en realidad parece extraña. Inicia las tareas programadas, luego una subcapa separada para dormir y envía USR1 al shell en ejecución. ¿Por qué no simplemente dormir en el caparazón en funcionamiento? – paxdiablo

12

La siguiente secuencia de comandos muestra cómo hacerlo utilizando tareas en segundo plano. La primera sección elimina un proceso de 60 segundos después del límite de 10 segundos. El segundo intenta matar un proceso que ya ha salido. Tenga en cuenta que, si configura el tiempo de espera realmente alto, las ID del proceso pueden volcarse y usted matará el proceso incorrecto, pero esto es más un problema teórico: el tiempo de espera debería ser muy grande y tendría que para comenzar un lote de procesos.

#!/usr/bin/bash 

sleep 60 & 
pid=$! 
sleep 10 
kill -9 $pid 

sleep 3 & 
pid=$! 
sleep 10 
kill -9 $pid 

Aquí está la salida en mi caja de Cygwin:

$ ./limit10 
./limit10: line 9: 4492 Killed sleep 60 
./limit10: line 11: kill: (4560) - No such process 

Si sólo desea que esperar hasta que el proceso ha terminado, es necesario entrar en un bucle y comprobar. Esto es ligeramente menos preciso desde sleep 1 y los otros comandos realmente tomarán más de un segundo (pero no mucho más). Utilice esta secuencia de comandos para reemplazar la segunda sección anterior (los comandos "echo $proc" y "date" son para la depuración, no esperaría tenerlos en la solución final).

#!/usr/bin/bash 

date 
sleep 3 & 
pid=$! 
((lim = 10)) 
while [[ $lim -gt 0 ]] ; do 
    sleep 1 
    proc=$(ps -ef | awk -v pid=$pid '$2==pid{print}{}') 
    echo $proc 
    ((lim = lim - 1)) 
    if [[ -z "$proc" ]] ; then 
      ((lim = -9)) 
    fi 
done 
date 
if [[ $lim -gt -9 ]] ; then 
    kill -9 $pid 
fi 
date 

Básicamente, comprueba si el proceso todavía se está ejecutando cada segundo. Si no, sale del ciclo con un valor especial para no intentar matar al niño. De lo contrario, se agota y mata al niño.

Aquí está la salida para un sleep 3:

Mon Feb 9 11:10:37 WADT 2009 
pax 4268 2476 con 11:10:37 /usr/bin/sleep 
pax 4268 2476 con 11:10:37 /usr/bin/sleep 
Mon Feb 9 11:10:41 WADT 2009 
Mon Feb 9 11:10:41 WADT 2009 

y una sleep 60:

Mon Feb 9 11:11:51 WADT 2009 
pax 4176 2600 con 11:11:51 /usr/bin/sleep 
pax 4176 2600 con 11:11:51 /usr/bin/sleep 
pax 4176 2600 con 11:11:51 /usr/bin/sleep 
pax 4176 2600 con 11:11:51 /usr/bin/sleep 
pax 4176 2600 con 11:11:51 /usr/bin/sleep 
pax 4176 2600 con 11:11:51 /usr/bin/sleep 
pax 4176 2600 con 11:11:51 /usr/bin/sleep 
pax 4176 2600 con 11:11:51 /usr/bin/sleep 
pax 4176 2600 con 11:11:51 /usr/bin/sleep 
pax 4176 2600 con 11:11:51 /usr/bin/sleep 
Mon Feb 9 11:12:03 WADT 2009 
Mon Feb 9 11:12:03 WADT 2009 
./limit10: line 20: 4176 Killed sleep 60 
+0

Eso es bueno, pero la duración mínima que puede ejecutar el proceso ahora es el tiempo de espera. Eso es correcto por la forma en que formulé la pregunta, sin embargo. –

+0

@jm, ver mi respuesta actualizada. – paxdiablo

+1

¡Ack, no uses kill -9 a menos que sea absolutamente necesario! SIGKILL no se puede atrapar, por lo que el programa eliminado no puede ejecutar ninguna rutina de apagado, p. borrar archivos temporales. Primero pruebe HUP (1), luego INT (2), luego QUIT (3). – andrewdotn

Cuestiones relacionadas