أمر سطر الأوامر لقتل أمر تلقائيًا بعد فترة زمنية معينة
-
03-07-2019 - |
سؤال
أرغب في إيقاف أمر ما تلقائيًا بعد فترة زمنية معينة.لدي في الاعتبار واجهة مثل هذا:
% 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. *
وبلدي الاختلاف على بيرل أونيلينير يمنحك حالة خروج بدون التلويث مع شوكة () والانتظار ()، ودون التعرض لخطر قتل عملية خاطئة:
#!/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+ العمليات في الثانية الواحدة. يمكن إصلاح هذه.
وأنا استخدم "وقت المحدد"، وهي حزمة المتاحة في مستودع ديبيان.
#!/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
والنصي لديك سيكون لقراءة معرف المنتج وتعيين متغير. أنا لست ماهرة أكثر من اللازم، ولكن نفترض أن هذا قابل للتحقيق.