Leia tudo no buffer de um pipe sem esperar

5

Existe uma maneira de ler tudo já no buffer de um pipe, canalizá-los para outro programa e sair imediatamente sem esperar por mais entrada?

Tem que ser seguro binário. Não há problemas em instalar programas adicionais.

Por exemplo, o seguinte comando deve gerar a e não b , e elevar SIGPIPE no segundo eco (pelo menos se o sistema não tiver muita carga):

{ echo a; sleep 2s; echo b;} | { sleep 1s; my_command;}
    
por user23013 11.12.2016 / 13:19

2 respostas

2

Se você fizer uma chamada read() do sistema em um pipe, ela será interrompida se não houver nada no pipe, mas caso contrário, retornará imediatamente com o que está lá.

Agora, para o que está lá , com o Linux 4.4 no meu sistema multicore amd64, pelo menos (YMMV com outros sistemas ou outras versões do Linux), se um ou mais processos estiverem atualmente fazendo write() (ou outra chamada do sistema de escrita) na outra extremidade, possivelmente de mais do que a capacidade do pipe (64KiB por padrão nas versões atuais do Linux), o planejador vai e volta entre esses processos e aquele lendo do canal várias vezes durante essa chamada do sistema read() e read() podem retornar muito mais do que a capacidade do canal.

O comando dd é a interface CLI para a chamada do sistema read .

dd bs=1G count=1

(note que nem todas as dd implementações suportam esses G sufixos) faz um read() do tamanho 1GiB de stdin (seguido por um write() no stdout)

Com o GNU dd , você pode evitar o bloqueio no pipe vazio com iflag=nonblock . Isso define o pipe no modo non-blocking, mas observe que ele deixa o não bloqueio posteriormente, o que pode não ser desejável. Por exemplo:

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

dê:

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

Como o pipe se torna não bloqueador para wc também.

E, como dito anteriormente, com ou sem nonblock , você pode acabar lendo mais do que caberia no canal:

$ (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

(e você obteria números diferentes de uma corrida para a próxima).

Outra abordagem em sistemas que suportam isso é usar FIONREAD ioctl() para consultar quantos dados estão no canal antes de fazer a leitura.

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
  }'

Observe que, como é feito em duas etapas, se outro processo também estiver lendo o canal ao mesmo tempo, ainda poderá acabar bloqueando se esse outro processo esvaziar o canal entre o ioctl() e o read() .

    
por 23.01.2017 / 23:20
0

Você pode usar um programa C simples para ler stdin , escrever em stdout e sair sem realmente esperar pelo EOF e usá-lo como um intermediário na cadeia de tubulação.

Aqui está um exemplo simples:

main.c

#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;
}

Compile este programa usando gcc -o test main.c .

Então, se você executar { echo a; sleep 2s; echo b; } | { sleep 1s; ./test; } | { ./mycommand; } , mycommand receberá apenas o a , já que foi o único caractere no stdin quando ./test leu a partir dele.

Para verificar isso, você pode executar { echo a; sleep 2s; echo b; } | { sleep 1s; ./test; } | cat , que mostrará apenas a na tela.

    
por 23.01.2017 / 17:45