4

我试图在 bash 脚本中逐行收集几个子进程的输出,以便将其转发到另一个进程。

我没有发现任何东西可以保证子流程的输出不会混合,但对我来说重要的是每条输出线都正确地连接到输出端。输出之间的顺序无关紧要。

这是混合/乱码输出的示例:

#!/bin/bash

for i in {1..1000}; do
    ( { echo BEGIN; dmesg; echo END; } | tr -d '\n'; echo ) &
done

wait

运行这个:

$ ./test_output.sh | perl -ne 'print "$1\n" if m/(.{1,20}BEGIN.{0,20})/' | head
 0.000000] SRAT: PXMBEGIN[    0.000000] Initi
ME through PCIe PME BEGIN[    0.000000] Initi
ME through PCIe PME BEGIN[    0.000000] Initi
[    0.209816] pci 0BEGIN[    0.000000] Initi
ciehp 0000:00:16.1:pBEGIN[    0.000000] Initi
CI: Updating contextBEGIN[    0.000000] Initi
l family 2[    0.588BEGIN[    0.000000] Initi
ME through PCIe PME BEGIN[    0.000000] Initi
CI: Updating contextBEGIN[    0.000000] Initi
3922 pages, LIFO batBEGIN[    0.000000] Initi

您可以看到多行内容混合。

当然,没有&一切都很好。

所以现在,我别无选择,只能将每个孩子的输出重定向到一个文件,然后在一个 big 之后waitcat所有这些文件。

使用 GNU 并行运行相同的工作可以完成部分工作,但在我的环境中它不是一个选项。

GNU 并行确保命令的输出与按顺序运行命令时的输出相同。这使得使用 GNU 并行的输出作为其他程序的输入成为可能。

因此 GNU 并行将在每个作业完成后立即写入每个作业输出,并且它会注意不混合输出。那挺好的。但我也有兴趣尽快获得每个作业的输出,即不等待作业退出。有“-u”开关,但它会混合作业输出。

我是否需要玩 fifo、选择甚至编写 perl 脚本?

--

我想我已经找到了为什么/如何/何时输出在 man 7 管道中混合

POSIX.1-2001 说小于 PIPE_BUF 字节的 write(2)s 必须是原子的:输出数据作为连续序列写入管道。超过 PIPE_BUF 字节的写入可能是非原子的:内核可能会将数据与其他进程写入的数据交错。POSIX.1-2001 要求 PIPE_BUF 至少为 512 字节。(在 Linux 上,PIPE_BUF 为 4096 字节。)

4

1 回答 1

1

这是我的第一次抽奖。这是一个简单的脚本,它在后台启动 stdin 上给出的所有命令(不确定这是我想要的),并逐行收集这些命令的输出。

#!/usr/bin/env perl

use strict;
use warnings;

use IO::Select;
use POSIX qw(strftime);


my $SELECT_TIMEOUT = 1;
my $TAG_SEPARATOR = '|';
my $TAG_TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%S';


sub multiplex {
    my @commands = @_;
    my %tags = (); # fd -> cmd

    my $sel = IO::Select->new();

    for my $cmd (@commands) {
    $cmd =~ s/^\s+|\s+$//g;

    my $fd;
    if (!open($fd, "-|", $cmd)) {
        warn "Cannot start '$cmd': $!";
        next;
    }
    else {
        $tags{$fd} = $cmd;
        $sel->add($fd);
    }
    }


    while ($sel->handles > 0) {
    my @handles = $sel->can_read($SELECT_TIMEOUT);

    # maybe something went wrong
    if (!@handles) {
        for my $fd ($sel->has_exception($SELECT_TIMEOUT)) {
        $sel->remove($fd);
        }
        next;
    }

    my $now = strftime($TAG_TIMESTAMP_FORMAT, localtime(time()));

    for my $fd (@handles) {
        if (defined(my $line = <$fd>)) {
        if ($TAG_SEPARATOR) {
            $line = join($TAG_SEPARATOR, $now, $tags{$fd}, $line);
        }
        print $line;
        }
        else {
        # EOF
        $sel->remove($fd);
        }
    }
    }
}




multiplex(<STDIN>);
于 2013-05-22T18:39:15.813 回答