Вопрос

Этот ответ к Команда командной строки для автоматического завершения команды через определенное время

предлагает однострочный метод для тайм-аута длительной команды из командной строки bash:

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

Но возможно, что данная «длительная» команда завершится раньше истечения времени ожидания.(Давайте назовем это командой «обычно долго выполняющейся, но иногда быстрой» или тлрбсф ради забавы.)

Итак, у этого изящного однострочного подхода есть пара проблем.Во-первых, sleep не является условным, поэтому устанавливает нежелательную нижнюю границу времени, необходимого для завершения последовательности.Рассмотрим 30 секунд, 2 минуты или даже 5 минут для сна, когда тлрбсф команда завершается через 2 секунды — крайне нежелательно.Во-вторых, kill является безусловным, поэтому эта последовательность попытается завершить неработающий процесс и жаловаться на это.

Так...

Есть ли способ для тайм-аута обычно долго работающего, но иногда быстрого ("тлрбсф") командуйте этим

  • имеет реализацию bash (на другой вопрос уже есть ответы на Perl и C)
  • завершится в более раннем из двух: тлрбсф завершение программы или истек тайм-аут
  • не будет убивать несуществующие/незапущенные процессы (или, необязательно:не будет жаловаться о плохом убийстве)
  • не обязательно должен быть однострочным
  • может работать под Cygwin или Linux

...и, чтобы получить бонусные баллы, запускает тлрбсф команда на переднем плане и любой «спящий» или дополнительный процесс в фоновом режиме, например, stdin/stdout/stderr тлрбсф команду можно перенаправить, как если бы она была запущена напрямую?

Если да, поделитесь своим кодом.Если нет, объясните, почему.

Я потратил некоторое время, пытаясь взломать вышеупомянутый пример, но я достиг предела своих навыков Bash.

Это было полезно?

Решение

Я думаю, что это именно то, о чем вы просите:

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

Другие советы

Вы, вероятно, ищете timeout команда в coreutils.Поскольку это часть coreutils, технически это решение C, но это все равно coreutils. info timeout Больше подробностей.Вот пример:

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

Это решение работает независимо от режима монитора bash.Вы можете использовать правильный сигнал для завершения 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

Наблюдатель убивает your_command по истечении заданного времени ожидания;сценарий ожидает медленной задачи и завершает работу наблюдателя.Обратите внимание, что wait не работает с процессами, которые являются дочерними элементами другой оболочки.

Примеры:

  • your_command выполняется более 2 секунд и был прекращен

ваша_команда прервана

( 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 завершился до истечения времени ожидания (20 секунд)

ваша_команда завершена

( 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

Вот и все:

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

вы можете изменить SIGINT и 10 как пожелаете ;)

Я предпочитаю «timelimit», пакет которого есть как минимум в Debian.

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

Это немного лучше, чем «тайм-аут» coreutils, потому что он печатает что-то при завершении процесса, а также отправляет SIGKILL через некоторое время по умолчанию.

Вы можете сделать это полностью с помощью bash 4.3 и выше:

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

  • Требуется Bash 4.3 для wait -n

  • Возвращает 137, если команда была уничтожена, иначе — возвращаемое значение команды.
  • Работает на трубах.(Здесь не обязательно выходить на передний план!)
  • Также работает с внутренними командами или функциями оболочки.
  • Запускается в подоболочке, поэтому экспорт переменных в текущую оболочку невозможен, извините.

Если вам не нужен код возврата, это можно сделать еще проще:

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

Примечания:

  • Строго говоря, вам не нужен ; в ; ), однако это делает вещь более согласованной с ; }-случай.И set +b вероятно, тоже можно оставить, но лучше перестраховаться, чем потом сожалеть.

  • За исключением --forground (вероятно) вы можете реализовать все варианты timeout поддерживает. --preserve-status хотя это немного сложно.Это оставлено в качестве упражнения для читателя ;)

Этот рецепт можно использовать «натурально» в скорлупе (так же естественно, как и для flock fd):

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

Однако, как объяснялось выше, вы не можете естественным образом повторно экспортировать переменные среды во включающую оболочку.

Редактировать:

Пример из реального мира:Тайм-аут __git_ps1 на случай, если это займет слишком много времени (например, медленные SSHFS-ссылки):

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

Редактировать2:Исправлена ​​ошибка.Я заметил, что exit 137 не нужен и делает _timeout в то же время ненадежен.

Редактировать3: git является стойким, поэтому для удовлетворительной работы ему нужен двойной трюк.

Редактировать4:Забыл _ во-первых _timeout для реального примера GIT.

См. также http://www.pixelbeat.org/scripts/timeout скрипт, функциональность которого интегрирована в новые coreutils

Немного хакерски, но это работает.Не работает, если у вас есть другие процессы на переднем плане (пожалуйста, помогите мне это исправить!)

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

На самом деле, я думаю, вы можете изменить это, отвечая вашим критериям «бонуса»:

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

тайм-аут вероятно, это первый подход, который стоит попробовать.Вам может потребоваться уведомление или другая команда для выполнения, если время ожидания истекло.После долгих поисков и экспериментов я пришел к такому выводу. бить сценарий:

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

Простой скрипт с понятным кодом.Сохранить /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

Тайм-аут команды, которая выполняется слишком долго:

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

Завершается немедленно для команды, которая завершается:

$ run 10 sleep 2
$

Чтобы отключить slowcommand через 1 секунду:

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

Если вы уже знаете название программы (допустим program) для завершения после таймаута (как пример 3 секунд), я могу предложить простое и несколько грязное альтернативное решение:

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

Это прекрасно работает, если я вызываю тестовые процессы с помощью системных вызовов.

Есть также cratimeout Мартин Кракауэр (написан на C для систем Unix и 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 пока не использует bash 4 и не имеет /usr/bin/timeout, поэтому вот функция, которая работает в OS X без самодельных решений или macports и аналогична /usr/bin/timeout (на основе Tino). отвечать).Проверка параметров, помощь, использование и поддержка других сигналов — это упражнение для читателя.

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

У меня возникла проблема с сохранением контекста оболочки и разрешением тайм-аутов. Единственная проблема заключается в том, что выполнение сценария будет остановлено по тайм-ауту, но это нормально для тех потребностей, которые мне были представлены:

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

с выходами:

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

конечно, я предполагаю, что там был каталог под названием 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 "$@"

Моя проблема была, возможно, немного в другом:Я запускаю команду через ssh на удаленном компьютере и хочу уничтожить оболочку и дочерние элементы, если команда зависает.

Сейчас я использую следующее:

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

Таким образом, команда возвращает 255, если произошел тайм-аут, или код возврата команды в случае успеха.

Обратите внимание, что процессы завершения сеанса ssh обрабатываются иначе, чем в интерактивной оболочке.Но вы также можете использовать опцию -t для ssh, чтобы выделить псевдотерминал, чтобы он действовал как интерактивная оболочка.

Вот версия, которая не требует создания дочернего процесса — мне нужен был автономный скрипт, включающий эту функциональность.Он также выполняет дробный интервал опроса, поэтому вы можете производить опрос быстрее.тайм-аут был бы предпочтительнее, но я застрял на старом сервере

# 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

Опираясь на Ответ @loup...

Если вы хотите остановить процесс и отключить вывод задания уничтожения/pid, запустите:

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

Это помещает фоновый процесс в подоболочку, поэтому вы не видите результат задания.

У меня есть задание cron, которое вызывает скрипт php, и иногда оно зависает на скрипте php.Это решение было идеальным для меня.

Я использую:

scripttimeout -t 60 /script.php

В 99% случаев ответ заключается в том, чтобы НЕ реализовывать какую-либо логику тайм-аута.Логика тайм-аута практически в любой ситуации является красным предупреждающим знаком о том, что что-то еще это неправильно и должно быть исправлено вместо.

Иногда ваш процесс зависает или ломается через n секунд?Тогда выясните причину и исправьте это.

Кроме того, чтобы правильно выполнить решение Страгера, вам нужно использовать ожидание «$SPID» вместо fg 1, поскольку в сценариях у вас нет управления заданиями (и пытаться включить его глупо).Более того, fg 1 опирается на тот факт, что вы ранее не запускали никаких других заданий в сценарии, что является плохим предположением.

Очень упрощенный способ:

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

с pkill (вариант ) вы можете завершить конкретную команду с аргументами или указать -n, чтобы избежать уничтожения старого процесса.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top