pipe, {lista; } só funciona com alguns programas

13

Precisa de explicações de usuários avançados para esse comportamento imprevisível:

ps -eF | { head -n 1;grep worker; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root       441     2  0     0     0   2 paź15 ?       00:00:00 [kworker/2:1H]

tudo parece bem enquanto

ls -la / | { head -n 1;grep sbin; }

exibe apenas a saída de head

... Eu pensei stdout 2>&1e não funciona nem para mim é estranho, alguma explicação ou sugere como lidar com isso?

ast
fonte
1
O último deve imprimir tudo. O heade grepnão fazer nada lá.
Jordanm 18/10
sim você está certo. Mas, em vez disso, por que ps -eF funciona enquanto ls -la / not?
quer

Respostas:

9

Eu fiz algumas investigações usando stracee parece que é devido à maneira como o programa no lado esquerdo do pipeline está gravando no terminal. Quando o lscomando é executado, ele grava todos os dados em um único write(). Isso causahead com que consuma todo o stdin.

Por outro lado, psgrava dados em lotes, portanto, apenas o primeiro write()é consumido heade, então, ele existe. Chamadas posteriores para write()irão para o recém-geradogrep processo .

Isso significa que não funcionaria se o processo que você está tentando grepnão ocorresse no primeiro write(), poisgrep não consegue ver todos os dados (ele vê ainda menos que apenas os dados menos a primeira linha).

Aqui está um exemplo de tentativa de grep para o pid 1 no meu sistema:

$ ps -eF | { head -n2; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root         1     0  0  1697  3768   2 Oct03 ?        00:00:03 /lib/systemd/systemd
$ ps -eF | grep '/lib/systemd/systemd$'
root         1     0  0  1697  3768   2 Oct03 ?        00:00:03 /lib/systemd/systemd
$ ps -eF | { head -n1; grep '/lib/systemd/systemd$'; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD

Seu ps -eFexemplo funciona apenas por acaso.

jordanm
fonte
grande e abrangente expalnation muito obrigado
ast
1
Na verdade, é mais uma condição de corrida. Só que é mais lento executando várias write()chamadas. Se o headforam lentos para realizá-la de read()chamada (de tal modo que o tampão de tubo tinha todos os dados no mesmo), que iria apresentar o mesmo comportamento em ambos lse ps.
Patrick
6

Isso é causado por buffer na glibc. No caso de lsa saída estar em um buffer interno e, como tal, é passada apenas para head. Para isso ps -eF, a saída é maior e, assim que headtermina, o seguinte grepobtém as partes restantes da saída (mas não a totalidade) de ps.

Você pode se livrar dele descompactando o pipe - por exemplo, com sed -u(não tenho certeza de que não seja uma extensão GNU):

$ ls -al / | sed -u "#" | { head -n 1; grep bin; }
total 76
drwxr-xr-x   2 root root  4096 Oct  2 21:52 bin
drwxr-xr-x   2 root root  8192 Oct  3 01:54 sbin
peterph
fonte
4

O que está acontecendo é que head -n 1 lê mais de uma linha. Para uma taxa de transferência ideal, o head lê trechos de bytes, para que ele possa ler 1024 bytes de cada vez e, em seguida, procure nesses bytes a primeira quebra de linha. Como a quebra de linha pode ocorrer no meio desses 1024 bytes, o restante dos dados é perdido. Não pode ser colocado de volta no cano. Portanto, o próximo processo que executa apenas obtém os bytes 1025 e depois.

Seu primeiro comando é bem-sucedido porque o kworkerprocesso é posterior àquele primeiro pedaço quehead lê.

Para que isso funcione, headseria necessário ler 1 caractere por vez. Mas isso é extremamente lento, por isso não.
A única maneira de fazer algo assim com eficiência é fazer com que um único processo faça "head" e "grep".

Aqui estão duas maneiras de fazer isso:

echo -e '1\n2\n3\n4\n5' | perl -ne 'print if $i++ == 0 || /4/'

ou

echo -e '1\n2\n3\n4\n5' | awk '{if (NR == 1 || /4/) print }'

Tem muito mais...

Patrick
fonte
sim, eu conheci o 'modo de awk' para lidar com essa tarefa, mas queria saber por que o comportamento era tão imprevisível com {list; } Obrigado por esclarecer como funciona. Estou impressionado com todas as respostas acima
ast
2

Se você deseja apenas a primeira linha ou duas, o seguinte tipo de truque funciona e evita os problemas de buffer causados ​​pelo uso de dois comandos diferentes para ler o fluxo de saída:

$ ps -eF   | { IFS= read -r x ; echo "$x" ; grep worker; }
$ ls -la / | { IFS= read -r x ; echo "$x" ; grep sbin; }

O readé incorporado ao shell e não consome um buffer inteiro de entrada apenas para gerar uma linha, portanto, o uso readdeixa todo o restante da saída para o seguinte comando.

Se você deseja acentuar os problemas de buffer mostrados nos seus exemplos que usam dois comandos diferentes, adicione a sleepa eles para eliminar os problemas de tempo e permita que o comando à esquerda gere toda a sua saída antes que os comandos à direita tentem ler qualquer um dos comandos. isto:

$ ps -eF   | { sleep 5 ; head -n 1 ; grep worker; }
$ ls -la / | { sleep 5 ; head -n 1 ; grep sbin; }

Agora, os dois exemplos acima falham da mesma maneira - ele headlê um buffer inteiro da saída apenas para produzir uma linha e esse buffer não está disponível para o seguinte grep.

Você pode ver o problema de armazenamento em buffer ainda mais claramente usando alguns exemplos que numeram as linhas de saída, para saber quais linhas estão faltando:

$ ps -eF          | cat -n | { sleep 5 ; head -n 1 ; head ; }
$ ls -la /usr/bin | cat -n | { sleep 5 ; head -n 1 ; head ; }

Uma maneira simples de ver o problema do buffer é usar seqisso gera uma lista de números. Podemos facilmente saber quais números estão faltando:

$ seq 1 100000    | { sleep 5 ; head -n 1 ; head ; }
1

1861
1862
1863
1864
1865
1866
1867
1868
1869

Minha solução de truque usando o shell para ler e ecoar a primeira linha funciona corretamente, mesmo com o atraso do sono adicionado:

$ seq 1 100000 | { sleep 5 ; IFS= read -r x ; echo "$x" ; head ; }
1
2
3
4
5
6
7
8
9
10
11

Abaixo está um exemplo completo mostrando os headproblemas de buffer, mostrando como headconsome um buffer inteiro da saída apenas para produzir suas cinco linhas por vez. Esse buffer consumido não está disponível para o próximo headcomando na sequência:

$ seq 1 100000 | { sleep 5 ; head -5 ; head -5 ; head -5 ; head -5 ; }
1
2
3
4
5

1861
1862
1863
1864
499
3500
3501
3502
3503
7
5138
5139
5140
5141

Observando o número 1861acima, podemos calcular o tamanho do buffer usado headcontando a seqsaída de 1para 1860:

$ seq 1 1860 | wc -c
8193

Vemos isso em headbuffer lendo 8KB completos (8 * 1024 bytes) da saída do tubo de cada vez, até para produzir apenas algumas linhas de sua própria saída.

Ian D. Allen
fonte