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

StackOverflow https://stackoverflow.com/questions/601543

سؤال

أرغب في إيقاف أمر ما تلقائيًا بعد فترة زمنية معينة.لدي في الاعتبار واجهة مثل هذا:

% constrain 300 ./foo args

والذي من شأنه تشغيل "./foo" مع "args" ولكنه سيقتله تلقائيًا إذا استمر في العمل بعد 5 دقائق.

قد يكون من المفيد تعميم الفكرة على قيود أخرى، مثل القتل التلقائي لعملية ما إذا كانت تستخدم قدرًا كبيرًا من الذاكرة.

هل هناك أي أدوات موجودة تفعل ذلك، أو هل كتب أي شخص مثل هذا الشيء؟

تمت الإضافة:الحل الذي قدمه جوناثان هو بالضبط ما كان يدور في ذهني وهو يعمل كالسحر على نظام التشغيل Linux، لكن لا يمكنني تشغيله على نظام التشغيل Mac OSX.لقد تخلصت من SIGRTMIN الذي يسمح له بالتجميع بشكل جيد، لكن الإشارة لا يتم إرسالها إلى العملية الفرعية.هل يعرف أحد كيفية جعل هذا العمل على ماك؟

[أضيف:لاحظ أن هناك تحديثًا متاحًا من Jonathan يعمل على نظام Mac وفي أي مكان آخر.]

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

المحلول

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

تحت * NIX، أ alarm(2) يتم توريثه عبر execve(2) وSIGALRM قاتل افتراضيًا.لذلك، يمكنك في كثير من الأحيان ببساطة:

$ doalarm () { perl -e 'alarm shift; exec @ARGV' "$@"; } # define a helper function

$ doalarm 300 ./foo.sh args

أو تثبيت أ المجمع C تافهة للقيام بذلك نيابة عنك.

مزايا يتم تضمين معرف PID واحد فقط، والآلية بسيطة.لن تقتل العملية الخاطئة إذا، على سبيل المثال، ./foo.sh تم الخروج "بسرعة كبيرة جدًا" وتم إعادة استخدام معرف المنتج (PID) الخاص به.لا تحتاج إلى العديد من عمليات الصدفة الفرعية التي تعمل بشكل متضافر، وهو ما يمكن إجراؤه بشكل صحيح ولكنه عرضة للسباق.

سلبيات لا يمكن للعملية المقيدة بالوقت التعامل مع المنبه الخاص بها (على سبيل المثال، alarm(2), ualarm(2), setitimer(2))، لأن هذا من المحتمل أن يؤدي إلى مسح الإنذار الموروث.من الواضح أنه لا يمكنه حظر أو تجاهل SIGALRM، على الرغم من أنه يمكن قول الشيء نفسه عن SIGINT وSIGTERM وما إلى ذلك.لبعض الأساليب الأخرى.

يتم تنفيذ بعض الأنظمة (القديمة جدًا، على ما أعتقد). sleep(2) من ناحية alarm(2), وحتى يومنا هذا، يستخدمه بعض المبرمجين alarm(2) كآلية مهلة داخلية خام للإدخال/الإخراج والعمليات الأخرى.ومع ذلك، من خلال تجربتي، تنطبق هذه التقنية على الغالبية العظمى من العمليات التي تريد تقييدها بفترة زمنية.

نصائح أخرى

وGNU Coreutils يتضمن <م> مهلة الأمر، مثبتة بشكل افتراضي على العديد من أنظمة.

https://www.gnu.org/software/ coreutils / دليل / html_node / مهلة invocation.html

لمشاهدة free -m لمدة دقيقة واحدة، ثم قتله عن طريق إرسال إشارة الأجل:

timeout 1m watch free -m

وربما أنا لا فهم السؤال، ولكن هذا يبدو وقابلة للتنفيذ مباشرة، على الأقل في باش:

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

وهذا يعمل الأمر الأول، داخل قوسين، لمدة خمس ثوان، ثم يقتل ذلك. تدير العملية برمتها بشكل متزامن، أي أنك لن تكون قادرا على استخدام قذيفة الخاص بك بينما كان مشغولا انتظار الأمر بطيئا. إذا لم يكن ما تريد، ينبغي أن يكون من الممكن إضافة آخر و.

والمتغير $! هو المضمن باش الذي يحتوي على معرف العملية من المستوى الفرعي الأكثر بدأت في الآونة الأخيرة. ومن المهم أن لا يكون لديك وداخل القوس، فعل ذلك بهذه الطريقة يفقد معرف العملية.

ولدي برنامج يسمى timeout أن يفعل ذلك - مكتوب في C، في الأصل في عام 1989 ولكن تحديثها دوريا منذ ذلك الحين


تحديث: فشل هذا الرمز إلى تجميع على ماك X لأنه لم يتم تعريف SIGRTMIN، وفشل في مهلة عند تشغيل على ماكنتوش لأن وظيفة signal() هناك استئناف wait() بعد العصر والتنبيه إلى - وهو ليس السلوك المطلوب. لدي نسخة جديدة من timeout.c الذي يتعامل مع كل هذه المشاكل (باستخدام sigaction() بدلا من signal()). كما كان من قبل، في الاتصال بي عن ملف مضغوط بطريقة gzip القطران 10K مع شفرة المصدر والصفحة اليدوية (انظر ملفي الشخصي).


/*
@(#)File:           $RCSfile: timeout.c,v $
@(#)Version:        $Revision: 4.6 $
@(#)Last changed:   $Date: 2007/03/01 22:23:02 $
@(#)Purpose:        Run command with timeout monitor
@(#)Author:         J Leffler
@(#)Copyright:      (C) JLSS 1989,1997,2003,2005-07
*/

#define _POSIX_SOURCE       /* Enable kill() in <unistd.h> on Solaris 7 */
#define _XOPEN_SOURCE 500

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "stderr.h"

#define CHILD       0
#define FORKFAIL    -1

static const char usestr[] = "[-vV] -t time [-s signal] cmd [arg ...]";

#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_timeout_c[] = "@(#)$Id: timeout.c,v 4.6 2007/03/01 22:23:02 jleffler Exp $";
#endif /* lint */

static void catcher(int signum)
{
    return;
}

int main(int argc, char **argv)
{
    pid_t   pid;
    int     tm_out;
    int     kill_signal;
    pid_t   corpse;
    int     status;
    int     opt;
    int     vflag = 0;

    err_setarg0(argv[0]);

    opterr = 0;
    tm_out = 0;
    kill_signal = SIGTERM;
    while ((opt = getopt(argc, argv, "vVt:s:")) != -1)
    {
        switch(opt)
        {
        case 'V':
            err_version("TIMEOUT", &"@(#)$Revision: 4.6 $ ($Date: 2007/03/01 22:23:02 $)"[4]);
            break;
        case 's':
            kill_signal = atoi(optarg);
            if (kill_signal <= 0 || kill_signal >= SIGRTMIN)
                err_error("signal number must be between 1 and %d\n", SIGRTMIN - 1);
            break;
        case 't':
            tm_out = atoi(optarg);
            if (tm_out <= 0)
                err_error("time must be greater than zero (%s)\n", optarg);
            break;
        case 'v':
            vflag = 1;
            break;
        default:
            err_usage(usestr);
            break;
        }
    }

    if (optind >= argc || tm_out == 0)
        err_usage(usestr);

    if ((pid = fork()) == FORKFAIL)
        err_syserr("failed to fork\n");
    else if (pid == CHILD)
    {
        execvp(argv[optind], &argv[optind]);
        err_syserr("failed to exec command %s\n", argv[optind]);
    }

    /* Must be parent -- wait for child to die */
    if (vflag)
        err_remark("time %d, signal %d, child PID %u\n", tm_out, kill_signal, (unsigned)pid);
    signal(SIGALRM, catcher);
    alarm((unsigned int)tm_out);
    while ((corpse = wait(&status)) != pid && errno != ECHILD)
    {
        if (errno == EINTR)
        {
            /* Timed out -- kill child */
            if (vflag)
                err_remark("timed out - send signal %d to process %d\n", (int)kill_signal, (int)pid);
            if (kill(pid, kill_signal) != 0)
                err_syserr("sending signal %d to PID %d - ", kill_signal, pid);
            corpse = wait(&status);
            break;
        }
    }

    alarm(0);
    if (vflag)
    {
        if (corpse == (pid_t) -1)
            err_syserr("no valid PID from waiting - ");
        else
            err_remark("child PID %u status 0x%04X\n", (unsigned)corpse, (unsigned)status);
    }

    if (corpse != pid)
        status = 2; /* Dunno what happened! */
    else if (WIFEXITED(status))
        status = WEXITSTATUS(status);
    else if (WIFSIGNALED(status))
        status = WTERMSIG(status);
    else
        status = 2; /* Dunno what happened! */

    return(status);
}

إذا كنت تريد رمز 'الرسمية' ل 'stderr.h "و" stderr.c "، في الاتصال بي (انظر ملفي الشخصي).

وهناك أيضا ulimit، والتي يمكن استخدامها للحد من وقت التنفيذ المتاحة لعمليات فرعية.

ulimit -t 10

ويحد من عملية إلى 10 ثانية من وقت وحدة المعالجة المركزية.

لاستخدامها في الواقع للحد من عملية جديدة، بدلا من العملية الحالية، قد ترغب في استخدام برنامج نصي المجمع:

#! /usr/bin/env python

import os
os.system("ulimit -t 10; other-command-here")

والأوامر الأخرى يمكن أن يكون أي أداة. لقد تم تشغيل جافا، بيثون، C ونظام إصدارات خوارزميات الفرز المختلفة، وتسجيل متى أخذوا، بينما الحد وقت التنفيذ إلى 30 ثانية. تطبيق الكاكاو بيثون إنشاء أسطر الأوامر المختلفة - بما في ذلك الحجج - وتجميعها مرات في ملف CSV، ولكنه كان حقا زغب فقط على رأس الأمر المقدمة أعلاه

وبيرل بطانة واحدة، لمجرد ركلات:

perl -e '$s = shift; $SIG{ALRM} = sub { print STDERR "Timeout!\n"; kill INT => $p }; exec(@ARGV) unless $p = fork; alarm $s; waitpid $p, 0' 10 yes foo

وهذا يطبع "فو" لمدة عشر ثوان، ثم انقضاء المهلة. استبدال '10' مع أي عدد من الثواني، و "نعم فو" مع أي أمر.

والأمر مهلة من أوبونتو / ديبيان عندما تم تجميعها من مصدر للعمل على ماك. داروين

10.4. *

http://packages.ubuntu.com/lucid/timeout

وبلدي الاختلاف على بيرل أونيلينير يمنحك حالة خروج بدون التلويث مع شوكة () والانتظار ()، ودون التعرض لخطر قتل عملية خاطئة:

#!/bin/sh
# Usage: timelimit.sh secs cmd [ arg ... ]
exec perl -MPOSIX -e '$SIG{ALRM} = sub { print "timeout: @ARGV\n"; kill(SIGTERM, -$$); }; alarm shift; $exit = system @ARGV; exit(WIFEXITED($exit) ? WEXITSTATUS($exit) : WTERMSIG($exit));' "$@"

وأساسا شوكة () وانتظر () مخفية داخل النظام (). يتم تسليم SIGALRM لعملية الأصل الذي ثم يقتل نفسه والطفل من خلال إرسال SIGTERM إلى مجموعة العملية برمتها (- $$). في حال من غير المحتمل أن الطفل يخرج ويحصل على إعادة استخدام معرف المنتج الطفل قبل قتل () يحدث، وهذا لن يقتل عملية خاطئة لأن عملية جديدة مع معرف المنتج على طفل يبلغ من العمر لن يكون في المجموعة العملية نفسها عملية بيرل الأم .

ووكميزة إضافية، يخرج السيناريو أيضا مع ما هو ربما حالة الخروج الصحيح.

وجرب شيئا مثل:

# This function is called with a timeout (in seconds) and a pid.
# After the timeout expires, if the process still exists, it attempts
# to kill it.
function timeout() {
    sleep $1
    # kill -0 tests whether the process exists
    if kill -0 $2 > /dev/null 2>&1 ; then
        echo "killing process $2"
        kill $2 > /dev/null 2>&1
    else
        echo "process $2 already completed"
    fi
}

<your command> &
cpid=$!
timeout 3 $cpid
wait $cpid > /dev/null 2>&
exit $?

لديها الجانب السلبي أنه إذا تم استخدامها الخاص عملية "معرف المنتج ضمن المهلة، قد قتل في عملية خاطئة. هذا من المستبعد جدا، ولكن قد يكون بداية 20000+ العمليات في الثانية الواحدة. يمكن إصلاح هذه.

وأنا استخدم "وقت المحدد"، وهي حزمة المتاحة في مستودع ديبيان.

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

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

يقوم المراقب بإنهاء المهمة البطيئة بعد انتهاء المهلة المحددة؛ينتظر البرنامج النصي المهمة البطيئة وينهي المراقب.

أمثلة:

  • تم تشغيل المهمة البطيئة لأكثر من ثانيتين وتم إنهاؤها

تمت مقاطعة المهمة البطيئة

( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "Slow task finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "Slow task interrupted"
fi
  • انتهت هذه المهمة البطيئة قبل انتهاء المهلة المحددة

انتهت المهمة البطيئة

( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "Slow task finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "Slow task interrupted"
fi

وهناك تعديل طفيف للبيرل أونيلينير سوف تحصل على حق وضع الخروج.

perl -e '$s = shift; $SIG{ALRM} = sub { print STDERR "Timeout!\n"; kill INT => $p; exit 77 }; exec(@ARGV) unless $p = fork; alarm $s; waitpid $p, 0; exit ($? >> 8)' 10 yes foo

والأساس، والخروج ($؟ >> 8) وإحالة الوضع الخروج من فرعي أو جانبي. أنا فقط اختار 77 في حالة خروج عن المهلة.

وماذا عن استخدام الأداة تتوقع؟

## run a command, aborting if timeout exceeded, e.g. timed-run 20 CMD ARGS ...
timed-run() {
  # timeout in seconds
  local tmout="$1"
  shift
  env CMD_TIMEOUT="$tmout" expect -f - "$@" <<"EOF"
# expect script follows
eval spawn -noecho $argv
set timeout $env(CMD_TIMEOUT)
expect {
   timeout {
      send_error "error: operation timed out\n"
      exit 1
   }
   eof
}
EOF
}

وباش النقي:


#!/bin/bash

if [[ $# < 2 ]]; then
  echo "Usage: $0 timeout cmd [options]"
  exit 1
fi

TIMEOUT="$1"
shift

BOSSPID=$$

(
  sleep $TIMEOUT
  kill -9 -$BOSSPID
)&
TIMERPID=$!

trap "kill -9 $TIMERPID" EXIT

eval "$@"

وليس هناك وسيلة لتحديد وقت محدد مع "في" لتفعل هذا؟

$ at 05:00 PM kill -9 $pid

ويبدو أبسط كثيرا.

إذا كنت لا تعرف ما هو عدد معرف المنتج سيكون، وأفترض أن هناك طريقة لالنصي قراءته مع اوكس ملاحظة والبقرى، ولكن غير متأكد من كيفية تنفيذ ذلك.

$   | grep someprogram
tony     11585  0.0  0.0   3116   720 pts/1    S+   11:39   0:00 grep someprogram
tony     22532  0.0  0.9  27344 14136 ?        S    Aug25   1:23 someprogram

والنصي لديك سيكون لقراءة معرف المنتج وتعيين متغير. أنا لست ماهرة أكثر من اللازم، ولكن نفترض أن هذا قابل للتحقيق.

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