pipe, {list; } funziona solo con alcuni programmi


13

Hai bisogno di spiegazioni da parte degli utenti esperti per un comportamento così imprevedibile:

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]

tutto sembra ok mentre

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

visualizza solo l'output di head

... ci ho pensato stdout 2>&1e non funziona né per me è strano, qualche spiegazione o suggerire come gestirlo?


1
L'ultimo dovrebbe stampare tutto. L' heade grepfare niente.
jordanm,

si hai ragione. Ma invece di ciò, perché ps -eF funziona mentre ls -la / no?
ast

Risposte:


9

Ho fatto qualche indagine usando stracee sembra essere dovuto al modo in cui il programma sul lato sinistro della pipeline sta scrivendo sul terminale. Quando il lscomando viene eseguito, scrive tutti i dati in un singolo write(). Questo fa headconsumare tutto lo stdin.

D'altra parte psscrive i dati in batch, quindi solo il primo write()viene consumato heade quindi esiste. Le chiamate successive a write()passeranno al grepprocesso appena generato .

Ciò significa che non funzionerebbe se il processo che stai tentando di fare grepnon si è verificato nel primo write(), dal momento grepche non riesce a vedere tutti i dati (vede anche meno dei soli dati meno la prima riga).

Ecco un esempio di come provare a grep per pid 1 sul mio 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

Il tuo ps -eFesempio funziona solo per caso.


grande e completa iscrizione esplicativa grazie mille
ast

1
In realtà è più una condizione di gara. È solo che è più lento eseguire più write()chiamate. Se l' headesecuzione della sua read()chiamata fosse lenta (in modo tale che il buffer della pipe contenesse tutti i dati), mostrerebbe lo stesso comportamento su entrambi lse ps.
Patrick,

6

Ciò è causato dal buffering in glibc. Nel caso lsdell'output si trova in un buffer interno e come tale viene passato solo a head. Per l' ps -eFoutput è più grande e quindi una volta headterminato, quanto segue grepottiene le parti rimanenti dell'output (ma non dell'intero) ps.

Puoi sbarazzartene annullando il buffering della pipe, ad esempio con sed -u(non sono sicuro che non sia un'estensione 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

4

Quello che sta succedendo è che head -n 1legge più di 1 riga. Per un throughput ottimale, head legge blocchi di byte, quindi potrebbe leggere 1024 byte alla volta, quindi cercare quei byte per la prima interruzione di riga. Poiché l'interruzione di riga potrebbe verificarsi nel mezzo di quei 1024 byte, il resto dei dati andrà perso. Non può essere rimesso sul tubo. Quindi il prossimo processo che esegue ottiene solo byte 1025 e attivo.

Il tuo primo comando sembra riuscire perché il kworkerprocesso è dopo quel primo pezzo che headlegge.

Affinché ciò funzioni, headdovrebbe leggere 1 carattere alla volta. Ma questo è estremamente lento, quindi non lo è.
L'unico modo per fare qualcosa di simile in modo efficiente è avere un singolo processo che faccia sia "head" che "grep".

Ecco 2 modi per farlo:

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

o

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

Ce ne sono molti altri ...


sì, ho conosciuto "way of awk" per gestire questo compito, ma mi chiedevo perché il comportamento fosse così imprevedibile con {list; }. Grazie per chiarire come funziona. Sono impressionato con tutte le risposte di cui sopra
ast

2

Se si desidera solo la prima o la prima riga, il seguente tipo di trucco funziona ed evita i problemi di buffering causati dall'uso di due diversi comandi per leggere il flusso di output:

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

Il readè incorporato al guscio e non consuma un intero buffer di ingresso solo per uscita una linea, in modo da utilizzare readle foglie tutto il resto dell'output per il comando.

Se vuoi accentuare i problemi di buffering mostrati dai tuoi esempi che usano due diversi comandi, aggiungi sleepa loro per eliminare i problemi di temporizzazione e consenti al comando a sinistra di generare tutto il suo output prima che i comandi a destra provino a leggere uno dei esso:

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

Ora, entrambi gli esempi precedenti falliscono allo stesso modo: headlegge un intero buffer dell'output solo per produrre una riga e quel buffer non è disponibile per quanto segue grep.

Puoi vedere il problema del buffering ancora più chiaramente usando alcuni esempi che numerano le linee di output in modo da poter dire quali righe mancano:

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

Un modo semplice per visualizzare il problema di buffering è utilizzare seqche genera un elenco di numeri. Possiamo facilmente dire quali numeri mancano:

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

1861
1862
1863
1864
1865
1866
1867
1868
1869

La mia soluzione con i trucchi che utilizza la shell per leggere ed eco la prima riga funziona correttamente anche con il ritardo di sospensione aggiunto:

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

Di seguito è riportato un esempio completo che mostra i headproblemi di buffering, che mostra come headconsuma un intero buffer dell'output solo per produrre le sue cinque righe ogni volta. Quel buffer consumato non è disponibile per il headcomando successivo nella sequenza:

$ 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

Guardando il numero 1861sopra, possiamo calcolare la dimensione del buffer utilizzato headcontando l' seqoutput da 1a 1860:

$ seq 1 1860 | wc -c
8193

Vediamo che headè buffering leggendo un intero 8KB (8 * 1024 byte) dell'output di pipe alla volta, anche per produrre solo poche righe del proprio output.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.