Тайм-аут команды в bash без лишней задержки

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

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

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

Но возможно, что заданная команда «длительная» может закончиться раньше таймаута. (Назовем это командой «обычно долгое время, но иногда-быстро», или tlrbsf для удовольствия.)

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

Так…

Есть ли способ тайм-аута команды типично долгое время, но иногда-быстрая ( «tlrbsf» ), которая

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

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

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

Я потратил некоторое время, пытаясь взломать вышеупомянутый пример, но я нажимаю предел своих навыков 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  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 < 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 "[email protected]" 

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

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

    Это решение работает независимо от режима мониторинга bash. Вы можете использовать правильный сигнал для завершения вашей команды

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

    Наблюдатель убивает вашу команду после заданного таймаута; скрипт ждет медленной задачи и завершает наблюдателя. Обратите внимание, что 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 

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

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

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

    Там вы идете:

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

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

    Вы можете сделать это полностью с помощью 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 { _timeout 5 producer || echo KABOOM $?; } | consumer
    • Пример: producer | { _timeout 5 consumer1; consumer2; } producer | { _timeout 5 consumer1; consumer2; }
    • Пример: { while date; do sleep .3; done; } | _timeout 5 cat | less { 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`; ) } 

    Заметки:

    • Строго говоря, вам это не нужно ; в ; ) ; ) , однако это делает ситуацию более последовательной ; } ; } -case. И 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 "[email protected]"; }; _timeout 0.3 __orig__git_ps1 "[email protected]"; ) } 

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

    Edit3: git – это тяжелая игра, поэтому для удовлетворения потребностей требуется двойной трюк.

    Edit4: Забыл _ в первый _timeout для примера GIT реального мира.

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

    тайм-аут , вероятно, первый подход к попытке. Возможно, вам понадобится уведомление или другая команда для выполнения, если время истекает. После довольно многого поиска и экспериментов я придумал этот сценарий 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 

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

     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 

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

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

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

    Простой скрипт с ясностью кода. Сохранить в /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 "[email protected]" # Stop timeout kill $timeout_pid &>/dev/null 

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

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

    Заканчивается на завершающую команду:

     $ run 10 sleep 2 $ 

    Существует также cratimeout Мартин Cracauer (написанный в 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 без домашнего или макропорта, которая похожа на / usr / bin / timeout (на основе Tino’s ответ). Валидация параметров, помощь, использование и поддержка других сигналов – упражнение для читателя.

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

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

    Ваш процесс висит или ломается через несколько секунд? Затем выясните, зачем и зачем это исправлять.

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

    Мне была предоставлена ​​проблема сохранения контекста оболочки и разрешения тайм-аутов, единственная проблема с этим – это прекратит выполнение скрипта в тайм-ауте, но это прекрасно с учетом потребностей, которые я представил:

     #!/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=$! "[email protected]" || _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 называемый scripts dir

     #! /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 "[email protected]" - #! /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 "[email protected]" 

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

    Теперь я использую следующее:

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

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

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

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

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

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

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

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

    Очень упрощенно:

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

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

    Давайте будем гением компьютера.