Pipe “trapelate” in Linux


12

Supponiamo che tu abbia una pipeline come la seguente:

$ a | b

Se binterrompe l'elaborazione dello stdin, dopo un po 'la pipa si riempie e scrive, dal asuo stdout, si bloccherà (fino a quando non binizia nuovamente l'elaborazione o muore).

Se volessi evitare questo, potrei essere tentato di usare una pipa più grande (o, più semplicemente, buffer(1)) in questo modo:

$ a | buffer | b

Questo mi avrebbe semplicemente guadagnato più tempo, ma alla fine si asarebbe fermato.

Quello che mi piacerebbe avere (per uno scenario molto specifico a cui mi sto rivolgendo) è avere una pipe "che perde" che, quando piena, lascerebbe cadere alcuni dati (idealmente, riga per riga) dal buffer per far acontinuare elaborazione (come probabilmente si può immaginare, i dati che scorrono nella pipe sono sacrificabili, ovvero avere i dati elaborati da bè meno importante che ariuscire a correre senza bloccare).

Per riassumere, mi piacerebbe avere qualcosa come un buffer limitato e che perde:

$ a | leakybuffer | b

Probabilmente potrei implementarlo abbastanza facilmente in qualsiasi lingua, mi stavo solo chiedendo se c'è qualcosa di "pronto all'uso" (o qualcosa come un bash one-liner) che mi manca.

Nota: negli esempi sto usando pipe normali, ma la domanda vale anche per le pipe nominate


Mentre ho assegnato la risposta di seguito, ho anche deciso di implementare il comando leakybuffer perché la semplice soluzione di seguito presentava alcune limitazioni: https://github.com/CAFxX/leakybuffer


Le pipe nominate si riempiono davvero? Avrei pensato named pipe sono la soluzione a questo, ma non ho potuto dire con certezza.
Carattere jolly

3
Le pipe con nome hanno (per impostazione predefinita) la stessa capacità delle pipe senza nome, AFAIK
CAFxX

Risposte:


14

Il modo più semplice sarebbe passare attraverso un programma che imposta l'output senza blocco. Ecco un semplice perl oneliner (che puoi salvare come leakybuffer ) che lo fa:

così il tuo a | bdiventa:

a | perl -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | b

ciò che fa è leggere l'input e scrivere sull'output (lo stesso di cat(1)) ma l'output è non bloccante - il che significa che se la scrittura fallisce, restituirà un errore e perderà i dati, ma il processo continuerà con la prossima riga di input mentre ignoriamo convenientemente il errore. Il processo è una specie di buffer di linea come desiderato, ma vedere le avvertenze di seguito.

puoi provare ad esempio con:

seq 1 500000 | perl -w -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | \
    while read a; do echo $a; done > output

otterrai outputfile con linee perse (l'output esatto dipende dalla velocità della tua shell ecc.) in questo modo:

12768
12769
12770
12771
12772
12773
127775610
75611
75612
75613

vedi dove la shell ha perso le righe dopo 12773, ma anche un'anomalia - il perl non aveva abbastanza buffer per 12774\nma lo faceva per 1277questo ha scritto proprio questo - e quindi il prossimo numero 75610non inizia all'inizio della riga, rendendolo piccolo brutto.

Ciò potrebbe essere migliorato facendo in modo che il perl rilevi quando la scrittura non ha avuto esito positivo, e poi in seguito prova a svuotare il resto della riga ignorando l'arrivo di nuove righe, ma ciò complicherebbe molto di più lo script perl, quindi viene lasciato come esercizio per il lettore interessato :)

Aggiornamento (per file binari): se non si elaborano le righe terminate di nuova riga (come file di registro o simili), è necessario modificare leggermente il comando, o perl consumerà grandi quantità di memoria (a seconda della frequenza con cui appaiono i caratteri di nuova riga nell'input):

perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (read STDIN, $_, 4096) { print }' 

funzionerà correttamente anche per i file binari (senza consumare memoria aggiuntiva).

Update2 - output di file di testo più gradevole: evitare buffer di output ( syswriteanziché print):

seq 1 500000 | perl -w -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { syswrite STDOUT,$_ }' | \
    while read a; do echo $a; done > output

sembra risolvere i problemi con le "linee unite" per me:

12766
12767
12768
16384
16385
16386

(Nota: è possibile verificare su quali linee sono state tagliate le uscite con: perl -ne '$c++; next if $c==$_; print "$c $_"; $c=$_' outputoneliner)


Adoro gli oneliner: non sono un esperto del Perl, se qualcuno potesse suggerire i miglioramenti di cui sopra sarebbe fantastico
CAFxX

1
Questo sembra funzionare in una certa misura . Ma mentre osservo il mio comando perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_WRONLY|O_NONBLOCK; while (<STDIN>) { print }' | aplay -t raw -f dat --buffer-size=16000, perl sembra allocare continuamente più memoria fino a quando non viene ucciso dal gestore OOM.
Ponkadoodle,

@Wallacoloo grazie per averlo sottolineato, il mio caso era lo streaming di file di registro ... Vedi la risposta aggiornata per le lievi modifiche necessarie per supportare i file binari.
Matija Nalis,

Vedere anche GNU dd's dd oflag=nonblock status=none.
Stéphane Chazelas,

1
Mi dispiace, ancora una volta, il mio male, in realtà le scritture di meno di PIPE_BUF byte (4096 su Linux, che devono essere almeno 512 da POSIX) sono garantite per essere atomiche, quindi il $| = 1tuo syswrite()approccio previene effettivamente le scritture brevi fintanto che le linee sono ragionevolmente brevi.
Stéphane Chazelas,
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.