Usando testa e coda per afferrare diversi set di linee e salvare nello stesso file


10

Quindi questo è per i compiti, ma non farò la domanda specifica per i compiti.

Devo usare testa e coda per prendere diversi set di linee da un file. Quindi, come le linee 6-11 e le linee 19-24 e salvarle entrambe su un altro file. So che posso farlo usando append come

head -11 file|tail -6 > file1; head -24 file| tail -6 >> file1. 

Ma non credo che dovremmo.
Esiste un modo specifico per combinare i comandi head e tail e quindi salvare nel file?


1
Ti stanno specificamente chiedendo di usare heade tail? In tal caso, la tua soluzione è praticamente la migliore che puoi fare. Se hai il permesso di usare altri programmi sedo awkpotresti consentire soluzioni migliori (ad es. Con meno invocazioni di processo).
n.

Sì, ci stanno chiedendo di usare la testa e la coda. La ringrazio per la risposta.
user2709291

Una cosa che posso aggiungere: è possibile aggirare il reindirizzamento uscita aggiungendo ( >>) racchiudendo i due comandi visualizzati tra parentesi a riorientare la loro produzione concatenati: (head -11 file | tail -6; head -24 file | tail -6) > file1. Dipende davvero dalle preferenze personali che sono più belle.
n.

Grazie che funzionerà molto bene. Lo apprezzo molto.
user2709291,

Risposte:


11

Puoi farlo con headl'aritmetica sola e di base, se raggruppi i comandi { ... ; }usando un costrutto simile

{ head -n ...; head -n ...; ...; } < input_file > output_file

dove tutti i comandi condividono lo stesso input (grazie @mikeserv ).
Ottenere le linee 6-11 e le linee 19-24 equivale a:

head -n 5 >/dev/null  # dump the first 5 lines to `/dev/null` then
head -n 6             # print the next 6 lines (i.e. from 6 to 11) then
head -n 7 >/dev/null  # dump the next 7 lines to `/dev/null` ( from 12 to 18)
head -n 6             # then print the next 6 lines (19 up to 24)

Quindi, in sostanza, eseguiresti:

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } < input_file > output_file

6

È possibile utilizzare il { … }costrutto di raggruppamento per applicare l'operatore di reindirizzamento a un comando composto.

{ head -n 11 file | tail -n 6; head -n 24 file | tail -n 6; } >file1

Invece di duplicare le prime linee M + N e mantenere solo le ultime N, puoi saltare le prime linee M e duplicare la successiva N. Questo è misurabile in modo molto più veloce su file di grandi dimensioni . Fai attenzione che l' +Nargomento di tailnon è il numero di righe da saltare, ma uno più quello - è il numero della prima riga da stampare con le righe numerate da 1.

{ tail -n +6 file | head -n 6; tail -n +19 file | head -n 6; } >file1

In entrambi i casi, il file di output viene aperto solo una volta, ma il file di input viene attraversato una volta per ogni frammento da estrarre. Che ne dici di raggruppare gli input?

{ tail -n +6 | head -n 6; tail -n +14 | head -n 6; } <file >file1

In generale, questo non funziona. (Potrebbe funzionare su alcuni sistemi, almeno quando l'input è un file normale.) Perché? A causa del buffer di input . La maggior parte dei programmi, incluso tail, non legge il loro input byte per byte, ma pochi kilobyte alla volta, perché è più veloce. Quindi taillegge alcuni kilobyte, salta un po 'all'inizio, passa un po' di più heade si ferma, ma ciò che viene letto viene letto e non è disponibile per il comando successivo.

Un altro approccio consiste nell'utilizzare il headpiping /dev/nullper saltare le linee.

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } <file >file1

Ancora una volta, questo non è garantito per funzionare, a causa del buffering. Capita di funzionare con il headcomando da GNU coreutils (quello trovato su sistemi Linux non incorporati), quando l'input proviene da un normale file. Questo perché una volta che questa implementazione di headha letto ciò che vuole, imposta la posizione del file sul primo byte che non ha prodotto. Questo non funziona se l'input è una pipe.

Un modo più semplice per stampare diverse sequenze di righe da un file è chiamare uno strumento più generalista come sed o awk . (Questo può essere più lento, ma è importante solo per file estremamente grandi.)

sed -n -e '6,11p' -e '19,24p' <file >file1
sed -e '1,5d' -e '12,18d' -e '24q' <file >file1
awk '6<=NR && NR<=11 || 19<=NR && NR<=24' <file >file1
awk 'NR==6, NR==11; NR==19, NR==24' <file >file1

2
Non succede, è un comportamento standard specificato, anche se certamente, come dici tu, una pipe non è una fonte di input affidabile per l'input condiviso. DEFAULT DESCRIZIONE DELL'UTILITÀ : Quando un'utilità standard legge un file di input ricercabile e termina senza errori prima che raggiunga la fine del file, l'utilità garantirà che l'offset del file nella descrizione del file aperto sia posizionato correttamente appena dopo l'ultimo byte elaborato da l'utilità.
Mikeserv,

2

So che hai detto che devi usare testa e coda, ma sed è sicuramente lo strumento più semplice per il lavoro qui.

$ cat foo
a 1 1
a 2 1
b 1 1
a 3 1
c 3 1
c 3 1
$ sed -ne '2,4p;6p' foo
a 2 1
b 1 1
a 3 1
c 3 1

Puoi persino costruire i blocchi in una stringa con qualche altro processo ed eseguirlo attraverso sed.

$ a="2,4p;6p"
$ sed -ne $a foo
a 2 1
b 1 1
a 3 1
c 3 1

-n nega l'output, quindi si specificano gli intervalli da stampare con p, con il primo e l'ultimo numero dell'intervallo separati da una virgola.

Detto questo, puoi eseguire il raggruppamento dei comandi suggerito da @don_crissti, oppure scorrere il file alcune volte con head / tail afferrando un pezzo di linee ogni volta che passi.

$ head -4 foo | tail -3; head -6 foo | tail -1
a 2 1
b 1 1
a 3 1
c 3 1

Più righe in un file e più blocchi hai, più sed sarà efficiente.


2

Con sedte potresti fare:

sed '24q;1,5d;12,18d' <infile >outfile

... Forse si potrebbe avere una soluzione più efficiente con head. Don ha già dimostrato come potrebbe funzionare molto bene, ma ci ho anche provato. Qualcosa che potresti fare per gestire questo caso specifico:

for   n in 5 6 7 6
do    head -n"$n" >&"$((1+n%2))"
done  <infile >outfile 2>/dev/null

... che chiamerebbe head4 volte scrivendo su outfileo su a /dev/nullseconda che il valore di quella iterazione $nsia un numero pari o dispari.

Per casi più generali, l'ho messo insieme da alcune altre cose che avevo già:

somehead()( 
### call it like:
### somehead -[repeat] [-][numlines]* <infile >outfile
    set -e -- "${1#-}" "$@"                             #-e for arg validation
    r=; cd -- "${TMP:-/tmp}"                            #go to tmp
    dd bs=4096 of="$$$$" <&4 2>&3 &                     #dd <in >tmpfile &bg
    until [ -s "$$$$" ]; do :; done                     #wait while tmpfile empty
    exec <"$$$$" 4<&-;   rm "$$$$"                      #<tmpfile; rm tmpfile
    [ "$3${1}0" -ne "$3${2#?}0" ]          ||           #validate args - chk $1
            shift "$(((r=-${1:--1})||1))"; shift        #shift 1||2
    while [ "$(((r+=(_n=1))-1))" -ne 0 ]   &&           #while ! $rptmax &&
          IFS= read -r l                   &&           #      ! EOF     &&
          printf "%.$(($1>0?${#l}+1:0))s" "$l           #      ? printf  do
";  do    for n do [ "${n#-}" -gt 0 ]      || exit      #args all -[nums>0]
          head "-n$((${n#-}-_n))" >&"$((n>(_n=0)?1:3))" #head -n?$1 >?[+-]
    done; done                                          #done and done
)   4<&0 3>/dev/null                                    #4<for dd 3>for head

Questo può fare le tue cose come:

 seq 100 | somehead -1 -5 6 -7 6

... che stampa ...

6
7
8
9
10
11
19
20
21
22
23
24

Si aspetta che il suo primo argomento sia un conteggio delle ripetizioni con il prefisso a -, o, in mancanza, solo a -. Se viene fornito un conteggio, questo ripeterà il modello di linea indicato nei seguenti argomenti quante volte specificato e si interromperà non appena lo avrà fatto.

Per ogni arg che segue interpreterà un numero intero negativo per indicare un conteggio di righe su cui scrivere /dev/nulle un numero intero positivo per indicare un conteggio di righe su cui scrivere stdout.

Quindi nell'esempio sopra stampa le prime 5 righe su /dev/null, le successive 6 su stdout, le successive 7 su /dev/nulle le successive 6 su stdout. Dopo aver raggiunto l'ultimo dei suoi argomenti e completamente -1ripetuto attraverso il conteggio delle ripetizioni, si chiude. Se il primo argomento fosse stato -2, avrebbe ripetuto il processo ancora una volta, o se fosse stato il più -a lungo possibile.

Per ogni ciclo arg il whileciclo viene elaborato una volta. Nella parte superiore di ogni ciclo stdinviene letta la prima riga da nella variabile shell $l. Questo è necessario perché while head </dev/null; do :; donesi ripeterà indefinitamente - headindica al suo ritorno quando ha raggiunto la fine del file. Quindi il controllo contro EOF è dedicato reade printfscriverà $lpiù una nuova riga stdoutsolo se il secondo argomento è un numero intero positivo.

Il readcontrollo complica un po 'il ciclo perché immediatamente dopo viene chiamato un altro ciclo - un forciclo che scorre su args 2-$#come rappresentato $nper ogni iterazione del suo whileciclo genitore . Ciò significa che per ogni iterazione il primo arg deve essere decrementato di uno dal valore specificato nella riga di comando, ma tutti gli altri devono conservare i loro valori originali, e quindi il valore del $_nmarcatore var viene sottratto da ciascuno, ma contiene sempre e solo un valore maggiore di 0 per il primo arg.

Ciò costituisce il ciclo principale della funzione, ma la maggior parte del codice è nella parte superiore e ha lo scopo di consentire alla funzione di bufferizzare in modo pulito anche un tubo come input. Funziona chiamando prima uno sfondo ddper copiarlo in un file tmp in uscita a blocchi di 4k un pezzo. La funzione quindi imposta un ciclo di attesa - che non dovrebbe quasi mai completare nemmeno un singolo ciclo completo - solo per garantire che ddabbia effettuato almeno una singola scrittura nel file prima che la funzione sostituisca il suo stdin con un descrittore di file collegato al file tmp e successivamente scollega immediatamente il file conrm. Ciò consente alla funzione di elaborare in modo affidabile il flusso senza richiedere trap o altrimenti per la pulizia - non appena la funzione rilascia la richiesta sul file fd il file tmp cesserà di esistere perché il suo unico collegamento al filesystem denominato è già stato rimosso.


0

Utilizzare una funzione bash come questa:

seq 1 30 > input.txt
f(){ head $1 input.txt | tail $2 >> output.txt ;}; f -11 -2; f -24 -3
cat output.txt
10
11
22
23
24

Questo è un po 'eccessivo in questo caso, ma se i tuoi filtri diventano più grandi può diventare un vantaggio.

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.