You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Perl脚本并行启动bash脚本并获取各进程退出码方案

解决Perl并行启动进程并获取每个进程退出码的问题

你的需求完全可以通过Perl的waitpid函数实现,它能非阻塞地跟踪子进程的退出状态,比单纯用kill 0检查存活状态更可靠,还能直接解析出每个进程的退出码或终止信号,正好对应你想要的类似PHPproc_get_status的功能。

核心思路

  • 使用waitpid($pid, WNOHANG)进行非阻塞检查:不会阻塞主进程的循环,同时能判断进程是否结束
  • 通过POSIX提供的状态解析函数(WEXITSTATUS、WIFSIGNALED等),从$?中提取具体的退出码或终止信号
  • 处理完结束的进程后,记得关闭文件句柄并从进程数组中移除,避免资源泄漏和无效检查

修改后的完整代码

首先需要导入POSIX模块的相关常量,然后调整进程监控逻辑:

use warnings;
use strict;
use IPC::Open3;
use IO::Handle;
use POSIX qw(:signal_h :sys_wait_h); # 导入wait相关常量

my $timeLimit = 60*60; # 时间限制
my $startTime = time();
my @commands = ('bash script1.sh', 'bash script2.sh'); # 示例命令
my @processes = ();

foreach my $cmd (@commands) {
    my $stdout = IO::Handle->new;
    my $stderr = IO::Handle->new;
    # 注意:你原来的代码里用了$fullcmd,但定义的是$cmd,这里统一成$cmd
    my $pid = open3(undef, $stdout, $stderr, $cmd);
    push @processes, {
        pid => $pid,
        out => $stdout,
        err => $stderr,
        cmd => $cmd
    };
}

do {
    if (time() - $startTime > $timeLimit) {
        # 超时:给所有进程发送SIGINT信号
        kill 2, $_->{pid} foreach (@processes);
        @processes = ();
        last;
    } else {
        # 遍历进程数组,检查每个进程状态
        for (my $i = 0; $i < @processes; $i++) {
            my $proc = $processes[$i];
            my $pid = $proc->{pid};
            
            # 非阻塞等待进程结束
            my $wait_result = waitpid($pid, WNOHANG);
            
            if ($wait_result > 0) {
                # 进程已结束,解析退出状态
                my $exit_status = $?;
                
                if (WIFEXITED($exit_status)) {
                    # 进程正常退出,获取退出码
                    my $exit_code = WEXITSTATUS($exit_status);
                    print "Command '$proc->{cmd}' (PID $pid) exited normally with code: $exit_code\n";
                    # 这里可以添加日志记录逻辑
                } elsif (WIFSIGNALED($exit_status)) {
                    # 进程被信号终止,获取信号编号
                    my $signal = WTERMSIG($exit_status);
                    print "Command '$proc->{cmd}' (PID $pid) was killed by signal: $signal\n";
                }
                
                # 关闭stdout/stderr句柄,释放资源
                close $proc->{out};
                close $proc->{err};
                
                # 从数组中移除已结束的进程
                splice(@processes, $i, 1);
                $i--; # 数组长度变化,调整索引避免跳过下一个进程
            } elsif ($wait_result == -1) {
                # waitpid出错(比如PID不存在),直接移除该进程
                print "Error waiting for PID $pid: $!\n";
                splice(@processes, $i, 1);
                $i--;
            }
            # wait_result == 0 说明进程仍在运行,无需处理
        }
    }
} while (@processes > 0);

关键细节说明

  1. 为什么不用kill 0?
    kill 0只能检查进程是否存在,但如果进程已经变成僵尸进程(父进程未调用wait收割),kill 0会返回true,但实际上进程已经结束。而waitpid会自动收割僵尸进程,同时获取真实的退出状态,是更可靠的方式。

  2. 状态解析函数的作用

    • WIFEXITED($exit_status):判断进程是否正常退出
    • WEXITSTATUS($exit_status):获取正常退出的退出码(范围0-255)
    • WIFSIGNALED($exit_status):判断进程是否被信号终止
    • WTERMSIG($exit_status):获取终止进程的信号编号
  3. 文件句柄的关闭
    用open3创建的stdout/stderr句柄必须手动关闭,否则会导致资源泄漏,尤其是在长时间运行的脚本中。

内容的提问来源于stack exchange,提问作者arunoruto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:18:46