无需等待即可读取管道缓冲区中的所有内容

无需等待即可读取管道缓冲区中的所有内容

有没有一种方法可以读取管道缓冲区中已有的所有内容,将它们传送到另一个程序,然后立即退出而无需等待更多输入?

它必须是二进制安全的。要求安装额外的程序是可以的。

例如,以下命令应输出aand not b,并在第二个回显时引发 SIGPIPE(至少在系统没有太多负载的情况下):

{ echo a; sleep 2s; echo b;} | { sleep 1s; my_command;}

答案1

如果您read()在管道上执行一个系统调用,如果管道中没有任何内容,它将挂起,否则会立即返回其中的内容。

现在,为了里面有什么,至少在我的 amd64 多核系统上使用 Linux 4.4(对于其他系统或其他版本的 Linux 来说是 YMMV),如果一个或多个进程当前正在write()另一端执行(或其他写入系统调用),可能超过容量管道的容量(当前版本的 Linux 上默认为 64KiB),调度程序将在read()系统调用期间在这些进程和从管道读取数据的进程之间来回多次,并且read()返回的值可能远远超过管道容量。

dd命令是系统调用的 CLI 接口read

dd bs=1G count=1

(请注意,并非所有dd实现都支持这些G后缀)read()从 stdin 执行大小为 1GiB 的操作(后跟write()stdout 上的一个)

使用 GNU dd,您可以使用 避免空管道阻塞iflag=nonblock。这会将管道设置为非阻塞模式,但请注意,它会使其之后保持非阻塞,这可能是不可取的。例如:

(date; sleep 2; date) |
  (sleep 1
   dd bs=1G count=1 status=none iflag=nonblock
   wc -c)

给:

Mon 23 Jan 22:11:30 GMT 2017
wc: 'standard input': Resource temporarily unavailable
0

随着管道wc也变得不阻塞。

正如前面所说,无论有没有nonblock,您最终都会读到超出管道所能容纳的内容:

$ (cat /dev/zero & cat /dev/zero & cat /dev/zero) |
     (sleep 1; dd bs=1G count=1) | wc -c
0+1 records in
0+1 records out
545914880 bytes (546 MB, 521 MiB) copied, 0.48251 s, 1.1 GB/s
545914880

(并且从一次运行到下一次运行你会得到不同的数字)。

FIONREAD ioctl()支持它的系统上的另一种方法是在执行读取之前使用查询管道中有多少数据。

perl -e '
  require "sys/ioctl.ph";
  ioctl(STDIN, &FIONREAD, $n) or die "ioctl: $!\n";
  $n = unpack "L", $n;
  if ($n) {
    sysread STDIN, $text, $n or die "read: $!\n";
    print $text
  }'

请注意,因为它是分两步完成的,所以如果另一个进程同时从管道中读取数据,那么如果另一个进程清空 和 之间的管道,它可能仍然会ioctl()阻塞read()

答案2

您可以使用一个简单的 C 程序来读取stdin、写入stdout和退出,而无需实际等待EOF,并将其用作管道链中的中间体。

这是一个简单的例子:

主程序

#include <stdio.h>
#include <unistd.h>

#define BUFFER_SIZE 1024

int main(int argc, char **argv){
    char buffer[BUFFER_SIZE];

    ssize_t read_size;

    //Read up to BUFFER_SIZE bytes of what's currently at the stdin
    read_size = read(STDIN_FILENO, buffer, BUFFER_SIZE);
    if(read_size > 0){
        write(STDOUT_FILENO, buffer, read_size);
    }

    return 0;
}

使用 编译该程序gcc -o test main.c

然后,如果您运行{ echo a; sleep 2s; echo b; } | { sleep 1s; ./test; } | { ./mycommand; }mycommand只会收到,因为它是从中读取时a的唯一字符。stdin./test

要验证它,您可以运行,这只会在屏幕上{ echo a; sleep 2s; echo b; } | { sleep 1s; ./test; } | cat显示。a

相关内容