سؤال

هذه الإجابة ل أمر سطر الأوامر لقتل أمر تلقائيًا بعد فترة زمنية معينة

يقترح طريقة من سطر واحد لإنهاء مهلة أمر طويل الأمد من سطر أوامر bash:

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

ولكن من الممكن أن ينتهي أمر معين "يعمل لفترة طويلة" قبل انتهاء المهلة.(دعنا نسميه أمرًا "عادةً ما يعمل لفترة طويلة ولكن سريعًا في بعض الأحيان"، أو com.tlrbsf للمتعة.)

لذا فإن هذا النهج الأنيق ذو الخطوط الواحدة يواجه بعض المشاكل.لأول مرة sleep ليست مشروطة، بحيث تحدد حدًا أدنى غير مرغوب فيه للوقت الذي يستغرقه التسلسل حتى ينتهي.فكر في 30 ثانية أو 2 متر أو حتى 5 أمتار للنوم، عندما com.tlrbsf ينتهي الأمر في ثانيتين - وهو أمر غير مرغوب فيه للغاية.ثانيا، kill غير مشروط، لذلك سيحاول هذا التسلسل قتل العملية غير الجارية والتذمر بشأنها.

لذا...

هل هناك طريقة لانتهاء مهلة عملية طويلة الأمد ولكن سريعة في بعض الأحيان ("tlrbsf") الأمر بذلك

  • لديه تطبيق bash (السؤال الآخر يحتوي بالفعل على إجابات Perl وC)
  • سينتهي في وقت سابق من الاثنين: com.tlrbsf إنهاء البرنامج، أو انقضاء المهلة
  • لن يقتل العمليات غير الموجودة/غير العاملة (أو اختياريًا:سوف لن يشتكي عن قتل سيء)
  • لا يجب أن تكون بطانة واحدة
  • يمكن تشغيله تحت Cygwin أو Linux

...وللحصول على نقاط المكافأة، يتم تشغيل com.tlrbsf الأمر في المقدمة وأي "سكون" أو عملية إضافية في الخلفية، مثل stdin/stdout/stderr الخاص بالأمر com.tlrbsf هل يمكن إعادة توجيه الأمر كما لو تم تشغيله مباشرة؟

إذا كان الأمر كذلك، يرجى مشاركة الرمز الخاص بك.إذا لم يكن كذلك، يرجى توضيح السبب.

لقد أمضيت بعض الوقت في محاولة اختراق المثال المذكور أعلاه ولكني وصلت إلى الحد الأقصى لمهاراتي في الباش.

هل كانت مفيدة؟

المحلول

أعتقد أن هذا هو بالضبط ما تطلبه:

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

يقوم المراقب بقتل أمرك بعد انتهاء المهلة المحددة؛ينتظر البرنامج النصي المهمة البطيئة وينهي المراقب.لاحظ أن wait لا يعمل مع العمليات التي تكون تابعة لقشرة مختلفة.

أمثلة:

  • يعمل your_command لأكثر من ثانيتين وتم إنهاؤه

تمت مقاطعة أمرك

( 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 كما تريد ;)

أفضل "الحد الزمني"، الذي يحتوي على حزمة على الأقل في دبيان.

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

  • يحتاج باش 4.3 ل wait -n

  • يعطي 137 إذا تم قتل الأمر، وإلا فإن القيمة المرجعة للأمر.
  • يعمل للأنابيب.(لست بحاجة إلى الذهاب إلى المقدمة هنا!)
  • يعمل مع أوامر أو وظائف Shell الداخلية أيضًا.
  • يتم تشغيله في غلاف فرعي، لذلك لا يتم تصدير أي متغير إلى الغلاف الحالي، آسف.

إذا لم تكن بحاجة إلى رمز الإرجاع، فيمكن جعل ذلك أسهل:

_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 البرنامج النصي الذي تم دمج وظائفه في الإصدارات الأساسية الأحدث

كيندا hacky، لكنه يعمل.لا يعمل إذا كانت لديك عمليات مقدمة أخرى (الرجاء مساعدتي في إصلاح هذا!)

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 بقلم Martin 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 دون استخدام home-brew أو macports المشابهة لـ /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
) }

لقد واجهت مشكلة الحفاظ على سياق الصدفة والسماح بالمهلات، والمشكلة الوحيدة فيها هي أنها ستوقف تنفيذ البرنامج النصي عند انتهاء المهلة - ولكن لا بأس بذلك مع الاحتياجات التي قدمتها لي:

#!/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 على جهاز بعيد وأريد قتل shell وchilds إذا توقف الأمر.

أستخدم الآن ما يلي:

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

إذا كنت تريد انتهاء مهلة العملية وإسكات إخراج مهمة/معرف الهوية، فقم بتشغيل:

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

يؤدي هذا إلى وضع العملية ذات الخلفية في غلاف فرعي حتى لا ترى مخرجات المهمة.

لدي وظيفة cron تستدعي برنامج نصي php، وفي بعض الأحيان، تتعطل في برنامج php النصي.كان هذا الحل مثاليًا بالنسبة لي.

أنا أستعمل:

scripttimeout -t 60 /script.php

في 99% من الحالات، يكون الجواب هو عدم تنفيذ أي منطق للمهلة.منطق المهلة هو في أي موقف تقريبًا علامة تحذير حمراء تشير إلى شيء ما آخر خطأ ويجب إصلاحه بدلاً من.

هل العملية الخاصة بك معلقة أو تنقطع بعد n ثانية في بعض الأحيان؟ثم اكتشف السبب وأصلح ذلك بدلاً من ذلك.

جانبًا، للقيام بحل strager بشكل صحيح، تحتاج إلى استخدام الانتظار "$SPID" بدلاً من fg 1، نظرًا لأنه في البرامج النصية ليس لديك التحكم في الوظيفة (ومحاولة تشغيله أمر غبي).علاوة على ذلك، يعتمد الشكل 1 على حقيقة أنك لم تبدأ أي مهام أخرى سابقًا في البرنامج النصي، وهو افتراض سيئ.

طريقة مبسطة جداً:

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

مع pkill (خيار -F) يمكنك إنهاء أمرك المحدد باستخدام الوسائط أو تحديد -n لتجنب إنهاء العملية القديمة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top