bash - programacion - scripts linux ejercicios resueltos
Tiempo de espera de un comando en bash sin demora innecesaria (22)
Aquí hay una versión que no se basa en generar un proceso secundario: necesitaba un script independiente que incorporara esta funcionalidad. También realiza un intervalo de sondeo fraccional, por lo que puede hacer una encuesta más rápida. Se habría preferido el tiempo de espera, pero estoy atascado en un servidor antiguo
# wait_on_command <timeout> <poll interval> command
wait_on_command()
{
local timeout=$1; shift
local interval=$1; shift
$* &
local child=$!
loops=$(bc <<< "($timeout * (1 / $interval)) + 0.5" | sed ''s//..*//g'')
((t = loops))
while ((t > 0)); do
sleep $interval
kill -0 $child &>/dev/null || return
((t -= 1))
done
kill $child &>/dev/null || kill -0 $child &>/dev/null || return
sleep $interval
kill -9 $child &>/dev/null
echo Timed out
}
slow_command()
{
sleep 2
echo Completed normally
}
# wait 1 sec in 0.1 sec increments
wait_on_command 1 0.1 slow_command
# or call an external command
wait_on_command 1 0.1 sleep 10
Esta respuesta al comando de la línea de comandos para eliminar automáticamente un comando después de un cierto tiempo
propone un método de 1 línea para vencer un comando de larga duración desde la línea de comandos de bash:
( /path/to/slow command with options ) & sleep 5 ; kill $!
Pero es posible que un comando "de larga duración" finalice antes del tiempo de espera. (Llamémoslo un comando "típicamente de larga ejecución pero a veces rápido", o tlrbsf por diversión).
Así que este ingenioso enfoque de 1 línea tiene un par de problemas. Primero, el sleep
no es condicional, por lo que establece un límite inferior indeseable en el tiempo necesario para que la secuencia finalice. Considere 30s o 2m o incluso 5m para el reposo, cuando el comando tlrbsf finalice en 2 segundos, lo cual es altamente indeseable. Segundo, la kill
es incondicional, por lo que esta secuencia intentará matar un proceso que no se está ejecutando y se quejará al respecto.
Asi que...
¿Hay alguna forma de que se agote un comando que normalmente dura mucho tiempo pero a veces es rápido ( "tlrbsf" ) que
- tiene una implementación de bash (la otra pregunta ya tiene respuestas de Perl y C)
- terminará al principio de los dos: terminación del programa tlrbsf , o tiempo de espera transcurrido
- no eliminará los procesos que no existen ni los que se están ejecutando (o, opcionalmente, no se quejará de una mala muerte)
- no tiene que ser un 1-liner
- Puede ejecutarse bajo Cygwin o Linux
... y, para obtener puntos de bonificación, ejecuta el comando tlrbsf en primer plano y cualquier proceso de ''reposo'' o proceso adicional en segundo plano, de manera que el stdin / stdout / stderr del comando tlrbsf pueda redirigirse, igual que si hubiera correr directamente?
Si es así, por favor comparte tu código. Si no, por favor explica por qué.
He pasado un tiempo tratando de hackear el ejemplo antes mencionado pero estoy llegando al límite de mis habilidades de bash.
Consulte también el script http://www.pixelbeat.org/scripts/timeout cuya funcionalidad se ha integrado en los nuevos Coreutils.
Creo que esto es precisamente lo que estás pidiendo:
http://www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3
#!/bin/bash
#
# The Bash shell script executes a command with a time-out.
# Upon time-out expiration SIGTERM (15) is sent to the process. If the signal
# is blocked, then the subsequent SIGKILL (9) terminates it.
#
# Based on the Bash documentation example.
# Hello Chet,
# please find attached a "little easier" :-) to comprehend
# time-out example. If you find it suitable, feel free to include
# anywhere: the very same logic as in the original examples/scripts, a
# little more transparent implementation to my taste.
#
# Dmitry V Golovashkin <[email protected]>
scriptName="${0##*/}"
declare -i DEFAULT_TIMEOUT=9
declare -i DEFAULT_INTERVAL=1
declare -i DEFAULT_DELAY=1
# Timeout.
declare -i timeout=DEFAULT_TIMEOUT
# Interval between checks if the process is still alive.
declare -i interval=DEFAULT_INTERVAL
# Delay between posting the SIGTERM signal and destroying the process by SIGKILL.
declare -i delay=DEFAULT_DELAY
function printUsage() {
cat <<EOF
Synopsis
$scriptName [-t timeout] [-i interval] [-d delay] command
Execute a command with a time-out.
Upon time-out expiration SIGTERM (15) is sent to the process. If SIGTERM
signal is blocked, then the subsequent SIGKILL (9) terminates it.
-t timeout
Number of seconds to wait for command completion.
Default value: $DEFAULT_TIMEOUT seconds.
-i interval
Interval between checks if the process is still alive.
Positive integer, default value: $DEFAULT_INTERVAL seconds.
-d delay
Delay between posting the SIGTERM signal and destroying the
process by SIGKILL. Default value: $DEFAULT_DELAY seconds.
As of today, Bash does not support floating point arithmetic (sleep does),
therefore all delay/time values must be integers.
EOF
}
# Options.
while getopts ":t:i:d:" option; do
case "$option" in
t) timeout=$OPTARG ;;
i) interval=$OPTARG ;;
d) delay=$OPTARG ;;
*) printUsage; exit 1 ;;
esac
done
shift $((OPTIND - 1))
# $# should be at least 1 (the command to execute), however it may be strictly
# greater than 1 if the command itself has options.
if (($# == 0 || interval <= 0)); then
printUsage
exit 1
fi
# kill -0 pid Exit code indicates if a signal may be sent to $pid process.
(
((t = timeout))
while ((t > 0)); do
sleep $interval
kill -0 $$ || exit 0
((t -= interval))
done
# Be nice, post SIGTERM first.
# The ''exit 0'' below will be executed if any preceeding command fails.
kill -s SIGTERM $$ && kill -0 $$ || exit 0
sleep $delay
kill -s SIGKILL $$
) 2> /dev/null &
exec "$@"
En el 99% de los casos, la respuesta NO es implementar ninguna lógica de tiempo de espera. La lógica del tiempo de espera es, en casi cualquier situación, una señal de advertencia roja de que otra cosa está mal y debería corregirse.
¿Su proceso se cuelga o se rompe después de n segundos a veces? Luego descubre por qué y arregla eso en su lugar.
Además, para resolver correctamente la solución de Strager, debe usar wait "$ SPID" en lugar de fg 1, ya que en los scripts no tiene control de trabajo (y tratar de activarlo es estúpido). Además, fg 1 se basa en el hecho de que no inició ningún otro trabajo previamente en el script, lo cual es una suposición errónea.
Esta solución funciona independientemente del modo de monitor de bash. Puedes usar la señal adecuada para terminar tu_comando
#!/bin/sh
( your_command ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher
El observador mata a tu_comando después de un tiempo de espera dado; la secuencia de comandos espera la tarea lenta y termina el observador. Tenga en cuenta que la wait
no funciona con procesos que son hijos de un shell diferente.
Ejemplos:
- your_command ejecuta más de 2 segundos y se terminó
tu_comando interrumpido
( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
echo "your_command finished"
pkill -HUP -P $watcher
wait $watcher
else
echo "your_command interrupted"
fi
- your_command terminó antes del tiempo límite (20 segundos)
tu_comando termino
( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
echo "your_command finished"
pkill -HUP -P $watcher
wait $watcher
else
echo "your_command interrupted"
fi
Hay que ir
timeout --signal=SIGINT 10 /path/to/slow command with options
puedes cambiar el SIGINT
y el 10
como desees;)
Mi problema fue quizás un poco diferente: comienzo un comando a través de ssh en una máquina remota y quiero matar al shell y a los childs si el comando se bloquea.
Ahora uso lo siguiente:
ssh server ''( sleep 60 && kill -9 0 ) 2>/dev/null & my_command; RC=$? ; sleep 1 ; pkill -P $! ; exit $RC''
De esta manera, el comando devuelve 255 cuando hubo un tiempo de espera o el código de retorno del comando en caso de éxito
Tenga en cuenta que matar procesos de una sesión ssh se maneja diferente de un shell interactivo. Pero también puede usar la opción -t para ssh para asignar un pseudo terminal, por lo que actúa como un shell interactivo
OS X no usa bash 4 todavía, ni tiene / usr / bin / timeout, así que aquí hay una función que funciona en OS X sin home-brew o macports que es similar a / usr / bin / timeout (basado en Tino''s responder). La validación de parámetros, la ayuda, el uso y el soporte para otras señales son un ejercicio para el lector.
# implement /usr/bin/timeout only if it doesn''t exist
[ -n "$(type -p timeout 2>&1)" ] || function timeout { (
set -m +b
sleep "$1" &
SPID=${!}
("${@:2}"; RETVAL=$?; kill ${SPID}; exit $RETVAL) &
CPID=${!}
wait %1
SLEEPRETVAL=$?
if [ $SLEEPRETVAL -eq 0 ] && kill ${CPID} >/dev/null 2>&1 ; then
RETVAL=124
# When you need to make sure it dies
#(sleep 1; kill -9 ${CPID} >/dev/null 2>&1)&
wait %2
else
wait %2
RETVAL=$?
fi
return $RETVAL
) }
Para slowcommand
del slowcommand
después de 1 segundo:
timeout 1 slowcommand || echo "I failed, perhaps due to time out"
Prefiero "timelimit", que tiene un paquete al menos en debian.
http://devel.ringlet.net/sysutils/timelimit/
Es un poco más agradable que el "tiempo de espera" de coreutils porque imprime algo al finalizar el proceso, y también envía SIGKILL después de algún tiempo de forma predeterminada.
Probablemente estés buscando el comando timeout
en coreutils. Dado que forma parte de coreutils, técnicamente es una solución C, pero sigue siendo coreutils. info timeout
para más detalles. Aquí hay un ejemplo:
timeout 5 /path/to/slow/command with options
Puedes hacer esto completamente con bash 4.3
y superior:
_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
- Ejemplo:
_timeout 5 longrunning_command args
- Ejemplo:
{ _timeout 5 producer || echo KABOOM $?; } | consumer
{ _timeout 5 producer || echo KABOOM $?; } | consumer
- Ejemplo:
producer | { _timeout 5 consumer1; consumer2; }
producer | { _timeout 5 consumer1; consumer2; }
Ejemplo:
{ while date; do sleep .3; done; } | _timeout 5 cat | less
{ while date; do sleep .3; done; } | _timeout 5 cat | less
Necesita Bash 4.3 para
wait -n
- Da 137 si el comando fue eliminado, de lo contrario, el valor de retorno del comando.
- Trabajos para tuberías. (¡No necesitas ir al primer plano aquí!)
- Funciona con comandos o funciones internas de shell, también.
- Se ejecuta en una subshell, por lo que no hay exportación de variables en el shell actual, lo siento.
Si no necesita el código de retorno, puede hacerlo aún más simple:
_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; kill -9 `jobs -p`; ) }
Notas:
Estrictamente hablando no necesitas el
;
en; )
, sin embargo, hace que la cosa sea más consistente con el; }
; }
-caso Y elset +b
probablemente también se puede dejar, pero es mejor prevenir que lamentar.A excepción de
--forground
(probablemente) puede implementar todas las variantes de soporte detimeout
.--preserve-status
es un poco difícil. Esto se deja como un ejercicio para el lector;)
Esta receta se puede usar "naturalmente" en la cáscara (tan natural como para flock fd
):
(
set +b
sleep 20 &
{
YOUR SHELL CODE HERE
} &
wait -n
kill `jobs -p`
)
Sin embargo, como se explicó anteriormente, no puede volver a exportar las variables de entorno al shell adjunto de esta manera, naturalmente.
Editar:
Ejemplo del mundo real: Tiempo de espera __git_ps1
en caso de que __git_ps1
demasiado (para cosas como enlaces SSHFS lentos):
eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "$@"; }; _timeout 0.3 __orig__git_ps1 "$@"; ) }
Edit2: Bugfix. Noté que la exit 137
no es necesaria y hace que _timeout
no _timeout
confiable al mismo tiempo.
Edit3: git
es un trabajo duro, por lo que necesita un doble truco para funcionar satisfactoriamente.
Edit4: Olvidé un _
en el primer _timeout
para el ejemplo GIT del mundo real.
Se me presentó un problema para preservar el contexto de la shell y permitir tiempos de espera, el único problema es que detendrá la ejecución del script en el tiempo de espera, pero está bien con las necesidades que me presentaron:
#!/usr/bin/env bash
safe_kill()
{
ps aux | grep -v grep | grep $1 >/dev/null && kill ${2:-} $1
}
my_timeout()
{
typeset _my_timeout _waiter_pid _return
_my_timeout=$1
echo "Timeout($_my_timeout) running: $*"
shift
(
trap "return 0" USR1
sleep $_my_timeout
echo "Timeout($_my_timeout) reached for: $*"
safe_kill $$
) &
_waiter_pid=$!
"$@" || _return=$?
safe_kill $_waiter_pid -USR1
echo "Timeout($_my_timeout) ran: $*"
return ${_return:-0}
}
my_timeout 3 cd scripts
my_timeout 3 pwd
my_timeout 3 true && echo true || echo false
my_timeout 3 false && echo true || echo false
my_timeout 3 sleep 10
my_timeout 3 pwd
con las salidas:
Timeout(3) running: 3 cd scripts
Timeout(3) ran: cd scripts
Timeout(3) running: 3 pwd
/home/mpapis/projects/rvm/rvm/scripts
Timeout(3) ran: pwd
Timeout(3) running: 3 true
Timeout(3) ran: true
true
Timeout(3) running: 3 false
Timeout(3) ran: false
false
Timeout(3) running: 3 sleep 10
Timeout(3) reached for: sleep 10
Terminated
Por supuesto, asumo que había un directorio llamado scripts
Sencillo script con claridad de código. Guardar en /usr/local/bin/run
:
#!/bin/bash
# run
# Run command with timeout $1 seconds.
# Timeout seconds
timeout_seconds="$1"
shift
# PID
pid=$$
# Start timeout
(
sleep "$timeout_seconds"
echo "Timed out after $timeout_seconds seconds"
kill -- -$pid &>/dev/null
) &
timeout_pid=$!
# Run
"$@"
# Stop timeout
kill $timeout_pid &>/dev/null
El tiempo de espera de un comando que se ejecuta demasiado tiempo:
$ run 2 sleep 10
Timed out after 2 seconds
Terminated
$
Termina inmediatamente para un comando que completa:
$ run 10 sleep 2
$
Si ya conoce el nombre del programa (supongamos que el program
) termina después del tiempo de espera (como un ejemplo de 3
segundos), puedo aportar una solución alternativa simple y algo sucia:
(sleep 3 && killall program) & ./program
Esto funciona perfectamente si llamo a los procesos de referencia con llamadas al sistema.
Sobre la base de la respuesta de @Loup ...
Si desea agotar el tiempo de espera de un proceso y silenciar la salida de trabajo / pid de eliminación, ejecute:
( (sleep 1 && killall program 2>/dev/null) &) && program --version
Esto coloca el proceso en segundo plano en una subshell para que no vea la salida del trabajo.
También hay cratimeout
por Martin Cracauer (escrito en C para sistemas Unix y Linux).
# cf. http://www.cons.org/cracauer/software.html
# usage: cratimeout timeout_in_msec cmd args
cratimeout 5000 sleep 1
cratimeout 5000 sleep 600
cratimeout 5000 tail -f /dev/null
cratimeout 5000 sh -c ''while sleep 1; do date; done''
Tengo un trabajo cron que llama a un script php y, algunas veces, se atasca en el script php. Esta solución fue perfecta para mí.
Yo suelo:
scripttimeout -t 60 /script.php
Un poco hacky, pero funciona. No funciona si tiene otros procesos en primer plano (¡ayúdenme a solucionar esto!)
sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}
En realidad, creo que puedes revertirlo, cumpliendo con tus criterios de "bonificación":
(YOUR COMMAND HERE & SPID=${!}; (sleep TIMEOUT; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}) < asdf > fdsa
Una forma muy simplista:
# command & sleep 5; pkill -9 -x -f "command"
con pkill (opción -f ) puede matar su comando específico con argumentos o especificar -n para evitar matar el proceso anterior.
tiempo de espera es probablemente el primer enfoque para tratar. Es posible que necesite una notificación u otro comando para ejecutar si se agota el tiempo. Después de un poco de búsqueda y experimentación, se me ocurrió este script de bash :
if
timeout 20s COMMAND_YOU_WANT_TO_EXECUTE;
timeout 20s AS_MANY_COMMANDS_AS_YOU_WANT;
then
echo ''OK''; #if you want a positive response
else
echo ''Not OK'';
AND_ALTERNATIVE_COMMANDS
fi
#! /bin/bash
timeout=10
interval=1
delay=3
(
((t = timeout)) || :
while ((t > 0)); do
echo "$t"
sleep $interval
# Check if the process still exists.
kill -0 $$ 2> /dev/null || exit 0
((t -= interval)) || :
done
# Be nice, post SIGTERM first.
{ echo SIGTERM to $$ ; kill -s TERM $$ ; sleep $delay ; kill -0 $$ 2> /dev/null && { echo SIGKILL to $$ ; kill -s KILL $$ ; } ; }
) &
exec "$@"