Como introduzir o tempo limite para scripts de shell?

35

Eu quero executar um script de shell que tenha um loop nele e ele pode durar para sempre, o que eu não quero que aconteça. Então, preciso introduzir um tempo limite para todo o script.

Como posso introduzir um tempo limite para todo o shell script no SuSE?

Radek
fonte
4
Esta é uma pergunta muito vaga. Você pode, por favor, especificar que tipo de tempo limite? Deseja um tempo limite em todo o script ou tempo limite em uma única função ou comando? O que você está tentando fazer?
Tim Kennedy
@ TimKennedy: espero que esteja melhor agora.
Radek
11
Veja também o tempo limite em um shell script (eu não considerá-lo um duplicado por causa da minha exigência portabilidade que exclui ferramentas como timeoutou expect)
Gilles 'SO parada sendo mal'

Respostas:

30

Se o GNU timeoutnão estiver disponível, você poderá usá-lo expect(Mac OS X, BSD, ... normalmente não possui ferramentas e utilitários GNU por padrão).

################################################################################
# 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

}

Editar exemplo:

timeout 10 "ls ${HOME}"
Matteo
fonte
Parece bom. Como posso ver a saída do comando executado na tela?
Radek
Oi, a saída deve estar visível, pois o stdout não é redirecionado para lugar nenhum. Eu adicionei um exemplo para o uso. No meu caso, ele imprime o conteúdo do meu diretório doméstico conforme o esperado. Qual comando não está imprimindo nenhuma saída?
Matteo
Eu não liguei direito. Funciona muito bem! Obrigado. Somente ele não gera o número de segundos após o tempo limite.
Radek
@Radek Desculpe, corrigido
Matteo
11
Eu gostaria de destacar que, se estiver usando o tempo limite do GNU, o status de retorno em timeoutsi é 124 no caso de um tempo limite, caso contrário, é o status de retorno do comando (isso é mencionado na resposta de Tim Kennedy).
QuasarDonkey
15

Obrigado pelo esclarecimento.

A maneira mais fácil de realizar o que você procura é executar seu script com o loop dentro de um wrapper como o timeoutcomando do pacote 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'

No final, será muito mais fácil do que escrever sua própria função de tempo limite, que os shells tendem a não ter sido incorporados.

Tim Kennedy
fonte
Não percebi imediatamente que o tinha instalado, porque é chamado gtimeoutna minha instalação (OSX).
18720 Joshua Goldberg #
7

Inicie um processo de watchdog de dentro do seu script para eliminar o pai, se ele demorar muito. Exemplo:

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

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

Este script será encerrado pelo cão de guarda após cinco segundos.

Kyle Jones
fonte
6
Mas você terá que esperar o tempo limite o tempo todo. No seu exemplo, seu script sempre executará 5 segundos, mesmo que o script seja muito mais curto. Você também deve armazenar o PID do cão de guarda e matá-lo no final.
Matteo
@ Matteo Fair bastante. Adicionado.
Kyle Jones
11
Note que pode ser mais complicado que isso, dependendo do que as ... coisas ... estejam fazendo. Veja Tempo limite em um script de shell
Gilles 'SO- stop be evil'
leia também: por quanto tempo os PIDs podem ser considerados únicos stackoverflow.com/questions/11323410/linux-pid-recycling
Florian Castellane
3

Há também cratimeoutde 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
fonte
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
fonte