首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在perl中启动bash命令的超时

在perl中启动bash命令的超时
EN

Stack Overflow用户
提问于 2018-04-26 10:16:34
回答 3查看 942关注 0票数 1

我有一个usecase,其中我必须从perl中触发bash命令,并且需要该命令在指定的超时时间内退出,目前我正在使用这个鼠标。

代码语言:javascript
运行
复制
use System::Timeout qw(timeout);
timeout(10, "my bash script")

(由于超时是必要的,所以我不使用system()进行调用)

如果shell脚本退出非零退出代码或命令超时,则此函数返回1。

发行

  1. 此函数仅根据传递的或失败的命令返回1/0(我需要bash脚本的确切退出代码)。
  2. 如果是1,我就不知道脚本是与非零退出代码一起退出的,还是存在超时(区分超时和shell脚本失败)。
  3. 被调用进程的pid是未知的(因此,如果pid由于超时而失败,我需要杀死它)。

满足上述两项条件对我来说很重要(我非常清楚如何在python中实现这一点,但无法获得perl的解决方案)

我不知道在perl中分叉当前进程,然后使用SIGALRM对其进行监视是否会有帮助(Forking将给我分叉进程的pid,而不是我从那个分叉启动的bash脚本。是否会杀死分叉,也会杀死它启动的bash进程?)

谢谢你的帮助

EN

回答 3

Stack Overflow用户

发布于 2018-04-26 14:06:26

您的系统可能有gnu timeout命令,如果它用超时值杀死子进程并返回命令退出代码,则该退出代码设置为124。如果您没有gnu timeout,您确实提到您有bash,这意味着您可以使用我的bash仿真器用于gnu timeouthttps://github.com/ronaldxs/bash-timeout,我很乐意收到任何反馈。查看System::Timeout的源代码,它基于CPAN模块IPC::Cmd,它建议将以下内容作为另一个起点:

代码语言:javascript
运行
复制
#!/usr/bin/env perl

use Modern::Perl;
use Data::Dump;

use IPC::Cmd 'run_forked';

my $rc = run_forked('sleep 5; exit 3', { timeout => 2 });

dd $rc;

产出:

代码语言:javascript
运行
复制
{
  child_pgid       => 69066,
  err_msg          => "ran more than [2] seconds\n",
  exit_code        => 0,
  ...
  timeout          => 2,
}
票数 1
EN

Stack Overflow用户

发布于 2018-04-26 15:11:07

对于运行外部命令时的高级任务,IPC::Run是一个不错的选择。以下内容应涵盖您提到的所有案例。(我承认,对错误消息使用正则表达式并不是最优雅的解决方案,但这里的重点是演示使用该模块的可能性。)

代码语言:javascript
运行
复制
use warnings;
use strict;
use IPC::Run qw/ start timeout /;
use Try::Tiny;

my @commands = (
        ['perl','-e','sleep 1'], # success
        ['perl','-e','sleep 10'], # failure due to timeout
        ['perl','-e','exit 123'], # failure due to nonzero exit code
        ['perl','-e','kill "INT", $$'], # process exits due to signal
        ['this_command_doesnt_exist'], # other failure
    );

for my $cmd (@commands) {
    my $h;
    try {
        print "\nRunning ",join(' ',@$cmd),"\n";
        $h = start $cmd, timeout(2);
        $h->finish or die "finish with \$?=$?";
        print "Success\n";
    }
    catch {
        if (/timeout/i) {
            warn "Timeout Error: $_";
            warn "killing child process\n";
            defined $h && $h->kill_kill;
        }
        elsif (/\$\?/) {
            warn "Exit Code Error: $_";
            # from http://perldoc.perl.org/functions/system.html
            if ($? == -1) { print "failed to execute: $!\n" }
            elsif ($? & 127)
                { printf "child died with signal %d, %s coredump\n",
                    ($? & 127),  ($? & 128) ? 'with' : 'without' }
            else { printf "child exited with value %d\n", $? >> 8 }
        }
        else { warn "Other Error: $_" }
    };
}

产出(略有修改):

代码语言:javascript
运行
复制
Running perl -e sleep 1
Success

Running perl -e sleep 10
Timeout Error: IPC::Run: timeout on timer #2 at ...
killing child process

Running perl -e exit 123
Exit Code Error: finish with $?=31488 at ...
child exited with value 123

Running perl -e kill "INT", $$
Exit Code Error: finish with $?=2 at ...
child died with signal 2, without coredump

Running this_command_doesnt_exist
Other Error: Command 'this_command_doesnt_exist' not found in ... at ...
票数 1
EN

Stack Overflow用户

发布于 2018-04-26 16:45:33

我会在其他答案中推荐@mr_ron和@haukex的方法。使用经过良好测试的模块(如IPC::RunIPC::Cmd )是安全的方法。无论如何,我在这里尝试了一种更低层次的方法:

代码语言:javascript
运行
复制
#! /usr/bin/env perl

use feature qw(say);
use strict;
use warnings;
use IO::Select;
use IPC::Open3;
use Symbol 'gensym';

# specify a command and a timeout
my $cmd = 'echo Hello; sleep 5; echo Bye; exit 2';
my $timeout = 3;

# Run the command with the given timeout:
local $SIG{CHLD} = 'IGNORE'; # Automatically reap dead children
my $cmd_err = gensym;
my $cmd_pid = open3( my $cmd_in, my $cmd_out, $cmd_err, $cmd );
say "Command PID: ", $cmd_pid;
my $timer_err = gensym;
my $timer_pid = open3( my $timer_in, my $timer_out, $timer_err, "sleep $timeout" );

my $timed_out = 0;
# We only use STDOUT here for simplicity, if needed you can also add
#  the STDERR handle of the command to the select loop..
my $select = IO::Select->new($cmd_out, $timer_out);
OUTER: while (1) {
    my @ready = $select->can_read;
    for my $fh (@ready) {
        my $fd   = $fh->fileno();
        if ( $fd == $timer_out->fileno() ) {
            say "Timed out";
            $timed_out = 1;
            last OUTER;
        }
        else { # The command handle is ready for reading..
            my $line = <$fh>;
            # An undefined value for $line, signals that the command processes
            #  has finished..
            last OUTER if !defined $line;
            print $line; # echo the line from the command to our STDOUT
        }
    }
}
if ( $timed_out ) {
    kill 'KILL', $cmd_pid;
}
else { # The command finished first, the timer may still be running..
    kill 'KILL', $timer_pid;
    waitpid( $cmd_pid, 0 );  # Reap the child, and get exit code
    my $child_exit_status = $? >> 8;
    say "Exit code: ", $child_exit_status;
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50040421

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档