la testa mangia personaggi extra


15

Il seguente comando shell doveva stampare solo le righe dispari del flusso di input:

echo -e "aaa\nbbb\nccc\nddd\n" | (while true; do head -n 1; head -n 1 >/dev/null; done)

Ma invece si limita a stampare la prima riga: aaa.

Lo stesso non accade quando viene utilizzato con l' opzione -c( --bytes):

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 >/dev/null; done)

Questo comando viene emesso 1234512345come previsto. Ma questo funziona solo nel coreutils attuazione del headprogramma di utilità. L' implementazione di busybox consuma ancora caratteri extra, quindi l'output è giusto 12345.

Immagino che questo specifico modo di implementazione sia fatto a fini di ottimizzazione. Non puoi sapere dove finisce la linea, quindi non sai quanti caratteri devi leggere. L'unico modo per non utilizzare caratteri aggiuntivi dal flusso di input è leggere il flusso byte per byte. Ma la lettura dallo stream di un byte alla volta potrebbe essere lenta. Quindi immagino che headlegge il flusso di input su un buffer abbastanza grande e quindi conti le righe in quel buffer.

Lo stesso non si può dire per il caso in cui --bytessi utilizza l'opzione. In questo caso sai quanti byte devi leggere. Quindi puoi leggere esattamente questo numero di byte e non di più. L' implementazione di corelibs sfrutta questa opportunità, ma quella di busybox no, legge ancora più byte di quanto richiesto in un buffer. Probabilmente viene fatto per semplificare l'implementazione.

Quindi la domanda. È corretto che l' headutilità consumi più caratteri dal flusso di input di quanto non sia stato chiesto? Esiste una sorta di standard per le utility Unix? E se esiste, specifica questo comportamento?

PS

Devi premere Ctrl+Cper interrompere i comandi sopra. Le utility Unix non mancano di leggere oltre EOF. Se non si desidera premere, è possibile utilizzare un comando più complesso:

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 | [ `wc -c` -eq 0 ] && break >/dev/null; done)

che non ho usato per semplicità.


2
Neardupe unix.stackexchange.com/questions/48777/… e unix.stackexchange.com/questions/84011/… . Inoltre, se questo titolo fosse stato nei film.SX la mia risposta sarebbe Zardoz :)
dave_thompson_085

Risposte:


30

È corretto che l'utility head consumi più caratteri dal flusso di input di quanto non sia stato chiesto?

Sì, è permesso (vedi sotto).

Esiste una sorta di standard per le utility Unix?

Sì, POSIX volume 3, Shell e utility .

E se esiste, specifica questo comportamento?

Nella sua introduzione fa:

Quando un'utilità standard legge un file di input ricercabile e termina senza errori prima che raggiunga la fine del file, l'utilità deve assicurarsi che l'offset del file nella descrizione del file aperto sia posizionato correttamente subito dopo l'ultimo byte elaborato dall'utilità. Per i file che non sono ricercabili, lo stato dell'offset del file nella descrizione del file aperto per quel file non è specificato.

headè una delle utility standard , quindi un'implementazione conforme a POSIX deve implementare il comportamento sopra descritto.

GNU head non provare a lasciare il descrittore di file nella posizione corretta, ma è impossibile cercare su tubi, così nel test non riesce a ripristinare la posizione. Puoi vederlo usando strace:

$ echo -e "aaa\nbbb\nccc\nddd\n" | strace head -n 1
...
read(0, "aaa\nbbb\nccc\nddd\n\n", 8192) = 17
lseek(0, -13, SEEK_CUR)                 = -1 ESPIPE (Illegal seek)
...

I readritorni 17 byte (tutti gli input disponibili), headelabora quattro di questi e quindi tenta di tornare 13 byte, ma non può. (Puoi anche vedere qui quel GNUhead usa un buffer da 8 KiB.)

Quando dici headdi contare i byte (che non è standard), sa quanti byte leggere, quindi può (se implementato in quel modo) limitarne la lettura di conseguenza. Ecco perché il tuo head -c 5test funziona: GNU headlegge solo cinque byte e quindi non ha bisogno di cercare di ripristinare la posizione del descrittore di file.

Se scrivi il documento in un file e lo usi invece, otterrai il comportamento che stai cercando:

$ echo -e "aaa\nbbb\nccc\nddd\n" > file
$ < file (while true; do head -n 1; head -n 1 >/dev/null; done)
aaa
ccc

2
Si possono usare le utilità line(ora rimosse da POSIX / XPG ma ancora disponibili su molti sistemi) o read( IFS= read -r line) invece che leggono un byte alla volta per evitare il problema.
Stéphane Chazelas,

3
Nota che head -c 5la lettura di 5 byte o un buffer completo dipende dall'implementazione (nota anche che head -cnon è standard), non puoi fare affidamento su questo. Dovresti dd bs=1 count=5avere la garanzia che non saranno letti più di 5 byte.
Stéphane Chazelas,

Grazie @ Stéphane, ho aggiornato la -c 5descrizione.
Stephen Kitt,

Si noti che l' headintegrato di ksh93legge un byte alla volta con head -n 1quando l'ingresso non è ricercabile.
Stéphane Chazelas,

1
@anton_rh, ddfunziona correttamente solo con le pipe bs=1se si utilizza un countdato che le letture sulle pipe possono restituire meno di quanto richiesto (ma almeno un byte a meno che non venga raggiunto eof). GNU ddha iflag=fullblockche può alleviarlo però.
Stéphane Chazelas,

6

da POSIX

L' utility head deve copiare i suoi file di input nell'output standard, terminando l'output per ciascun file in un punto designato.

Non dice nulla su quanto head deve leggere dall'input. Chiederlo di leggere byte per byte sarebbe sciocco, poiché nella maggior parte dei casi sarebbe estremamente lento.

Questo è, tuttavia, affrontato nel readbuiltin / utility: tutte le shell che riesco a trovare readdalle pipe un byte alla volta e il testo standard può essere interpretato nel senso che ciò deve essere fatto, per poter leggere solo quella singola riga:

L' utilità di lettura deve leggere un'unica riga logica dall'input standard in una o più variabili shell.

Nel caso di read, che viene utilizzato negli script di shell, un caso d'uso comune sarebbe qualcosa del genere:

read someline
if something ; then 
    someprogram ...
fi

Qui, l'input standard di someprogramè lo stesso di quello della shell, ma ci si può aspettare che someprogramarrivi a leggere tutto ciò che viene dopo la prima riga di input consumata da reade non ciò che è stato lasciato dopo una lettura bufferizzata da read. D'altra parte, usare headcome nel tuo esempio è molto più raro.


Se vuoi davvero cancellare ogni altra riga, sarebbe meglio (e più veloce) usare qualche strumento in grado di gestire l'intero input in una volta sola, ad es.

$ seq 1 10 | sed -ne '1~2p'   # GNU sed
$ seq 1 10 | sed -e 'n;d'     # works in GNU sed and the BSD sed on macOS

$ seq 1 10 | awk 'NR % 2' 
$ seq 1 10 | perl -ne 'print if $. % 2'

Ma vedi la sezione "INPUT FILES" dell'introduzione POSIX al volume 3 ...
Stephen Kitt,

1
POSIX dice: "Quando un'utilità standard legge un file di input ricercabile e termina senza errori prima che raggiunga la fine del file, l'utilità deve assicurarsi che l'offset del file nella descrizione del file aperto sia posizionato correttamente appena dopo l'ultimo byte elaborato da l'utilità. Per i file non ricercabili, lo stato dell'offset del file nella descrizione del file aperto per quel file non è specificato. "
AlexP

2
Si noti che a meno che non si utilizzi -r, è readpossibile leggere più di una riga (senza di IFS=essa si spogliano anche gli spazi e le schede iniziali e finali (con il valore predefinito di $IFS)).
Stéphane Chazelas,

@AlexP, sì, Stephen ha appena collegato quella parte.
ilkkachu,

Si noti che l' headintegrato di ksh93legge un byte alla volta con head -n 1quando l'ingresso non è ricercabile.
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.