Pergunta

Este para linha de comando resposta comando para auto-kill em um comando depois de um determinado período de tempo

propõe um método de 1 linha de tempo limite de um comando de longa duração a partir da linha de comando bash:

( /path/to/slow command with options ) & sleep 5 ; kill $!

Mas é possível que um determinado comando "de longa duração" pode terminar mais cedo do que o tempo limite. (Vamos chamá-lo de "-vezes-rápido tipicamente longo correndo-mas" de comando, ou tlrbsf para se divertir.)

Assim, esta abordagem 1-liner bacana tem um par de problemas. Em primeiro lugar, o sleep não é condicional, de modo que os conjuntos ligados um indesejável inferior sobre o tempo necessário para que a sequência de acabamento. Considere 30s ou 2m ou até 5m para o sono, quando o tlrbsf acabamentos de comando em 2 segundos - altamente indesejável. Em segundo lugar, o kill é incondicional, então essa sequência irá tentar matar um processo não-execução e lamentar sobre isso.

Então ...

Existe uma maneira de tempo limite do comando a tipicamente-de longa duração, mas, às vezes rápido ( "tlrbsf" ) que

  • tem uma implementação bash (a outra pergunta já tem Perl e C respostas)
  • terminará no início dos dois: tlrbsf término do programa, ou tempo limite decorrido
  • não vai matar não-existente / não-execução de processos (ou, opcionalmente: não reclamar sobre uma má kill)
  • não tem de ser um 1-liner
  • pode ser executado sob Cygwin ou Linux

... e, para pontos de bônus, corre o tlrbsf comando em primeiro plano e qualquer 'dormir' ou processo extra no fundo, de modo que a stdin / stdout / stderr do tlrbsf comando pode ser redirecionado, mesmo que se tivesse sido executado diretamente?

Se assim for, por favor, compartilhe seu código. Se não, por favor explique porquê.

Eu passei algum tempo tentando cortar o exemplo acima mencionado, mas eu estou batendo o limite das minhas habilidades festança.

Foi útil?

Solução

Eu acho que isso é exatamente o que você está pedindo:

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 <Dmitry.Golovashkin@sas.com>

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 "$@"

Outras dicas

Você está procurando provavelmente o comando timeout em coreutils. Uma vez que é uma parte da coreutils, é tecnicamente uma solução C, mas ainda é coreutils. info timeout para mais detalhes. Aqui está um exemplo:

timeout 5 /path/to/slow/command with options

Esta solução funciona independentemente do modo monitor bash. Você pode usar o sinal adequado para terminar your_command

#!/bin/sh
( your_command ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher

O observador mata your_command após dado tempo limite; o script aguarda a tarefa lenta e termina o observador. Note-se que wait não funciona com processos que são filhos de um shell diferente.

Exemplos:

  • your_command executa mais de 2 segundos e foi encerrado

your_command interrompido

( 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 acabado antes do tempo limite (20 segundos)

your_command acabado

( 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

Lá vai você:

timeout --signal=SIGINT 10 /path/to/slow command with options

Você pode alterar a SIGINT e 10 como você deseja;)

Eu prefiro "timelimit", que tem um pacote de, pelo menos, no Debian.

http://devel.ringlet.net/sysutils/timelimit/

É um agradável pouco do que os coreutils "timeout" porque ele imprime alguma coisa quando matar o processo, e também envia SIGKILL depois de algum tempo, por padrão.

Você pode fazer isso inteiramente com bash 4.3 e acima:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
  • Exemplo: _timeout 5 longrunning_command args
  • Exemplo: { _timeout 5 producer || echo KABOOM $?; } | consumer
  • Exemplo: producer | { _timeout 5 consumer1; consumer2; }
  • Exemplo: { while date; do sleep .3; done; } | _timeout 5 cat | less

  • Precisa Bash 4.3 para wait -n

  • Dá 137 se o comando foi morto, então o valor de retorno do comando.
  • Trabalhos para tubos. (Você não precisa ir primeiro plano aqui!)
  • Funciona com comandos shell internos ou funções também.
  • é executado em um subshell, exportação assim nenhuma variável no shell atual, desculpe.

Se você não precisa do código de retorno, isso pode ser feito ainda mais simples:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; kill -9 `jobs -p`; ) }

Notas:

  • Estritamente falando, você não precisa o ; em ; ), no entanto, faz coisa mais consistente ao ; } caso. E o set +b provavelmente pode ser deixado de distância, também, mas é melhor prevenir do que remediar.

  • exceto para --forground (provavelmente) você pode implementar todas as variantes suportes timeout. --preserve-status é um pouco difícil, no entanto. Este é deixado como um exercício para o leitor;)

Esta receita pode ser usado "naturalmente" no shell (tão natural quanto para flock fd):

(
set +b
sleep 20 &
{
YOUR SHELL CODE HERE
} &
wait -n
kill `jobs -p`
)

No entanto, como explicado acima, você não pode re-exportação variáveis ??de ambiente para o delimitador desembolsar desta forma naturalmente.

Editar:

exemplo do mundo real: Tempo para fora __git_ps1 em caso que leva muito tempo (para coisas como SSHFS ligações lentas):

eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "$@"; }; _timeout 0.3 __orig__git_ps1 "$@"; ) }

Edit2: Bugfix. Notei que exit 137 não é necessário e marcas _timeout não confiável, ao mesmo tempo.

Edit3:. git é um die-hard, por isso precisa de um duplo-trick ao trabalho satisfatoriamente

edit4:. Esqueceu a _ no primeiro _timeout para o exemplo GIT mundo real

Veja também o script http://www.pixelbeat.org/scripts/timeout o funcionalidade dos quais foi integrado coreutils mais recentes

kinda hacky, mas funciona. Não funciona se você tiver outros processos em primeiro plano (por favor me ajudar a corrigir isso!)

sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}

Na verdade, eu acho que você pode revertê-la, atendendo seus critérios de 'bônus':

(YOUR COMMAND HERE & SPID=${!}; (sleep TIMEOUT; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}) < asdf > fdsa

tempo limite é provavelmente a primeira abordagem para tentar. Você pode precisar de notificação ou outro comando para executar se ele expira. Depois de um pouco de pesquisa e experimentação, eu vim com esta o bash script:

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

roteiro simples com clareza código. Salvar para /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

vezes em um comando que corre muito longo:

$ run 2 sleep 10
Timed out after 2 seconds
Terminated
$

termina imediatamente para um comando que completa:

$ run 10 sleep 2
$

Para tempo limite da slowcommand após 1 segundo:

timeout 1 slowcommand || echo "I failed, perhaps due to time out"

Se você já sabe o nome do programa (vamos supor program) para terminar após o tempo limite (como um exemplo 3 segundos), eu posso contribuir com uma solução simples e alternativa um pouco sujo:

(sleep 3 && killall program) & ./program

Isso funciona perfeitamente se eu chamar processos de benchmark com as chamadas do sistema.

Há também cratimeout por Martin Cracauer (escrito em C para sistemas Unix e 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'

OS X não usa o bash 4 ainda, nem tem / usr / bin / timeout, então aqui está uma função que funciona em OS X sem casa-bebida ou MacPorts que é semelhante a / usr / bin / timeout ( com base na resposta do Tino). validação de parâmetros, ajuda, uso e suporte para outros sinais são um exercício para o leitor.

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

Foi-me apresentado um problema para preservar o contexto shell e permitir que o tempo limite, o único problema com ele é que vai parar a execução de scripts no tempo limite -, mas é bom com as necessidades I foi apresentado:

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

com as saídas:

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

é claro que eu assumir que havia um dir chamado scripts

#! /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 "$@"

O meu problema foi talvez um pouco diferente: eu começo um comando via ssh em uma máquina remota e querem matar a casca e da criança se os trava de comando

.

Agora use o seguinte:

ssh server '( sleep 60 && kill -9 0 ) 2>/dev/null & my_command; RC=$? ; sleep 1 ; pkill -P $! ; exit $RC'

Desta forma, o comando retorna 255, quando houve um tempo limite ou o returncode do comando em caso de sucesso

Por favor, note que matar processos de uma sessão de ssh é tratado diferente de um shell interativo. Mas você também pode usar a opção -t para ssh para alocar um pseudo terminal, por isso age como um shell interativo

Aqui está uma versão que não depende de desova um processo filho - Eu precisava de um script autônomo que incorporado essa funcionalidade. Ele também faz um intervalo de consulta fracionário, assim você pode pesquisar mais rápido. tempo de espera teria sido a preferida - mas eu estou preso em um servidor antigo

# 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

Com base na resposta @ de loup ...

Se você quiser tempo limite de um processo e silenciar o trabalho da matança / saída pid, execute:

( (sleep 1 && killall program 2>/dev/null) &) && program --version 

Isso coloca o processo em segundo plano em um subnível para que você não ver a saída do trabalho.

Eu tenho um trabalho cron que chama um script php e, algumas vezes, ficar preso em script php. Esta solução foi perfeita para mim.

Eu uso:

scripttimeout -t 60 /script.php

Em 99% dos casos a resposta é não implementar qualquer lógica de tempo limite. lógica de tempo limite é em quase qualquer situação um sinal de aviso vermelho que algo else está errado e deve ser corrigido em vez .

É o seu processo de suspensão ou quebrar após às vezes n segundos? Em seguida, descobrir o porquê e correção que em vez.

Como um aparte, para fazer solução certa de strager, você precisa usar espera "$ SPID" em vez de fg 1, uma vez que em scripts que você não tem controle sobre o trabalho (e tentando ligá-lo é estúpido). Além disso, fg 1 se baseia no fato de que você não começar a qualquer outro emprego anteriormente no script que é uma má hipótese de fazer.

Uma maneira muito simplista:

# command & sleep 5; pkill -9 -x -f "command"

com pkill (opção -f ) você pode matar o seu comando específico com argumentos ou especificar -n para processo antigo evitar matar.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top