unix - head AND tail del file


131

Supponi di avere un file txt, qual è il comando per visualizzare contemporaneamente le prime 10 righe e le ultime 10 righe del file?

vale a dire se il file è lungo 200 righe, quindi visualizzare le righe 1-10 e 190-200 in una volta sola.


Cosa intendi con "in una volta sola"?
cnicutar,

@cnicutar ie. non andando a capo -10 file guardando i dati e poi separatamente andando a -10 file e guardando i dati
toop

@toop Se vuoi un vero esempio funzionante, vedi stackoverflow.com/a/44849814/99834
sorin

Risposte:


208

Puoi semplicemente:

(head; tail) < file.txt

E se hai bisogno di usare le pipe per qualche motivo, allora in questo modo:

cat file.txt | (head; tail)

Nota: stamperà le linee duplicate se il numero di linee in file.txt è inferiore alle linee di testa predefinite + linee di coda predefinite.


54
A rigor di termini, questo non ti dà la coda del file originale, ma la coda del flusso dopo headha consumato le prime 10 righe del file. (Confronta questo con head < file.txt; tail < file.txtun file con meno di 20 righe). Solo un punto molto piccolo da tenere a mente. (Ma ancora +1.)
chepner il

15
Bello. Se si desidera un divario tra le parti della testa e della coda: (testa; eco; coda) <file.txt
Simon Hibbs,

3
Curioso del perché / come funziona. Alla domanda come una nuova domanda: stackoverflow.com/questions/13718242
zellyn

9
@nametal In realtà, potresti anche non ottenere così tanto. Mentre visualizzahead solo le prime 10 righe del suo input, non è garantito che non ne abbia consumato più per trovare la fine della decima riga, lasciando meno input per la visualizzazione. less
Chepner,

20
Mi dispiace dirlo, ma la risposta funziona solo in alcuni casi. seq 100 | (head; tail)mi dà solo i primi 10 numeri. Solo su dimensioni di input molto più grandi (come seq 2000) la coda ottiene un input.
modulare

18

ed è il standard text editor

$ echo -e '1+10,$-10d\n%p' | ed -s file.txt

2
Cosa succede se il file ha più o meno di 200 righe? E non conosci il numero di righe ab initio?
Paolo,

@Paul Ho cambiato sedined
kev

14

Per un flusso puro (ad es. Output da un comando), puoi usare 'tee' per fork il flusso e inviare un flusso alla testa e uno alla coda. Ciò richiede l'utilizzo della funzione '> (elenco)' di bash (+ / dev / fd / N):

( COMMAND | tee /dev/fd/3 | head ) 3> >( tail )

o usando / dev / fd / N (o / dev / stderr) più subshells con reindirizzamento complicato:

( ( seq 1 100 | tee /dev/fd/2 | head 1>&3 ) 2>&1 | tail ) 3>&1
( ( seq 1 100 | tee /dev/stderr | head 1>&3 ) 2>&1 | tail ) 3>&1

(Nessuno di questi funzionerà in csh o tcsh.)

Per qualcosa con un controllo un po 'migliore, puoi usare questo comando perl:

COMMAND | perl -e 'my $size = 10; my @buf = (); while (<>) { print if $. <= $size; push(@buf, $_); if ( @buf > $size ) { shift(@buf); } } print "------\n"; print @buf;'

1
+1 per il supporto dello stream. Potresti riutilizzare stderr:COMMAND | { tee >(head >&2) | tail; } |& other_commands
jfs il

2
tra l'altro, si rompe per i file più grandi della dimensione del buffer (8K sul mio sistema). cat >/dev/nulllo risolve:COMMAND | { tee >(head >&2; cat >/dev/null) | tail; } |& other_commands
jfs il

Ho adorato la soluzione, ma dopo aver giocato per un po 'ho notato che in alcuni casi la coda correva davanti alla testa ... non ci sono ordini garantiti tra heade tailcomandi: \ ...
Jan

7
(sed -u 10q; echo ...; tail) < file.txt

Solo un'altra variazione sul (head;tail)tema, ma evitando il problema iniziale di riempimento del buffer per file di piccole dimensioni.


4

head -10 file.txt; tail -10 file.txt

A parte questo, dovrai scrivere il tuo programma / script.


1
Nizza, ho sempre usato cate heado tailconvogliato, bello sapere che posso usare singolarmente!
Paolo,

Come posso quindi reindirizzare questi primi 10 + ultimi 10 in un altro comando?
toop

1
@Paul - con 'your_program' come wc -l restituisce 10 anziché 20
toop

3
o, senza dover spawnare una subshell: { head file; tail file; } | prog(sono necessari lo spazio all'interno delle parentesi graffe e il punto e virgola finale)
glenn jackman

1
Caspita ... un voto negativo per avere una risposta abbastanza simile agli altri (eppure timestamp prima di loro) dopo quasi due anni, da parte di qualcuno che ha scelto di non pubblicare perché ha votato verso il basso. Bello!
Mah,

4

Basato sul commento di JF Sebastian :

cat file | { tee >(head >&3; cat >/dev/null) | tail; } 3>&1

In questo modo è possibile elaborare la prima riga e il resto in modo diverso in un'unica pipe, utile per lavorare con i dati CSV:

{ echo N; seq 3;} | { tee >(head -n1 | sed 's/$/*2/' >&3; cat >/dev/null) | tail -n+2 | awk '{print $1*2}'; } 3>&1
N * 2
2
4
6

3

il problema qui è che i programmi orientati al flusso non conoscono in anticipo la lunghezza del file (perché potrebbe non essercene uno, se è un flusso reale).

strumenti come tailbufferizzare le ultime n righe visualizzate e attendere la fine del flusso, quindi stampare.

se vuoi farlo in un solo comando (e farlo funzionare con qualsiasi offset, e non ripetere le righe se si sovrappongono) dovrai emulare questo comportamento che ho citato.

prova questo awk:

awk -v offset=10 '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' yourfile

ha bisogno di più lavoro per evitare problemi quando offset è più grande del file
Samus_24

Sì, funziona con l'output convogliato, non solo con i file: a.out | awk -v ...
Camille Goudeseune,

infatti :) ma questo è il comportamento normale di awk, la maggior parte dei programmi a riga di comando funziona su stdin quando viene invocato senza argomenti.
Samus_

1
Molto vicino al comportamento desiderato, ma sembra che per <10 righe aggiunga nuove righe extra.
sorin,

3

Ci è voluto molto tempo per finire con questa soluzione che, a quanto pare, è l'unica che ha coperto tutti i casi d'uso (finora):

command | tee full.log | stdbuf -i0 -o0 -e0 awk -v offset=${MAX_LINES:-200} \
          '{
               if (NR <= offset) print;
               else {
                   a[NR] = $0;
                   delete a[NR-offset];
                   printf "." > "/dev/stderr"
                   }
           }
           END {
             print "" > "/dev/stderr";
             for(i=NR-offset+1 > offset ? NR-offset+1: offset+1 ;i<=NR;i++)
             { print a[i]}
           }'

Elenco delle caratteristiche:

  • uscita live per head (ovviamente che per tail non è possibile)
  • nessun uso di file esterni
  • barra di avanzamento di un punto per ogni riga dopo MAX_LINES, molto utile per attività di lunga durata.
  • barra di avanzamento su stderr, assicurando che i punti di avanzamento siano separati dalla testa + dalla coda (molto utile se si desidera convogliare lo stdout)
  • evita possibili ordini di registrazione errati a causa del buffering (stdbuf)
  • evitare di duplicare l'output quando il numero totale di righe è inferiore a head + tail.

2

Ho cercato questa soluzione per un po '. Ho provato io stesso con sed, ma il problema di non conoscere in anticipo la lunghezza del file / stream era insormontabile. Di tutte le opzioni disponibili sopra, mi piace la soluzione awk di Camille Goudeseune. Ha fatto notare che la sua soluzione ha lasciato righe vuote in più nell'output con un set di dati sufficientemente piccolo. Qui fornisco una modifica della sua soluzione che rimuove le righe extra.

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { a_count=0; for (i in a) {a_count++}; for (i=NR-a_count+1; i<=NR; i++) print a[i] }' ; }

1

Bene, puoi sempre metterli insieme. In questo modo, head fiename_foo && tail filename_foo. Se ciò non è sufficiente, potresti scrivere una funzione bash nel tuo file .profile o in qualsiasi file di accesso che usi:

head_and_tail() {
    head $1 && tail $1
}

E, poi richiamarlo dalla shell di richiesta: head_and_tail filename_foo.


1

Prime 10 righe di file.ext, quindi le sue ultime 10 righe:

cat file.ext | head -10 && cat file.ext | tail -10

Ultime 10 righe del file, quindi le prime 10:

cat file.ext | tail -10 && cat file.ext | head -10

È quindi possibile reindirizzare l'output anche altrove:

(cat file.ext | head -10 && cat file.ext | tail -10 ) | your_program


5
Perché usare cat quando puoi semplicemente chiamare head -10 file.txt?
jstarek,

Puoi rendere variabile il numero di linee, quindi la chiamata è qualcosa di simile a: head_ tail (foo, m, n) - restituendo il primo m snd e le ultime n righe di testo?
ricardo,

@ricardo che comporterebbe crei uno script bash che prende 3 args e li passa a taile heado una funzione alias-ing esso.
Paul,


1

attingendo alle idee sopra (testato bash & zsh)

ma usando un alias "hat" Head and Tails

alias hat='(head -5 && echo "^^^------vvv" && tail -5) < '


hat large.sql

0

Perché non utilizzare sedper questa attività?

sed -n -e 1,+9p -e 190,+9p textfile.txt


3
Funziona con file di lunghezza nota, ma non con file di lunghezza sconosciuta.
Kevin,

0

Per gestire pipe (flussi) e file, aggiungilo al tuo file .bashrc o .profile:

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' ; }

Quindi non puoi solo

headtail 10 < file.txt

ma anche

a.out | headtail 10

(Questo aggiunge ancora righe vuote spurie quando 10 supera la lunghezza dell'input, a differenza del vecchio normale a.out | (head; tail). Grazie, precedenti risponditori.)

Nota: headtail 10no headtail -10.


0

Basandosi su ciò che @Samus_ ha spiegato qui su come funziona il comando di @Aleksandra Zalcman, questa variazione è utile quando non puoi individuare rapidamente dove inizia la coda senza contare le linee.

{ head; echo "####################\n...\n####################"; tail; } < file.txt

O se inizi a lavorare con qualcosa di diverso da 20 righe, un conteggio delle righe potrebbe persino essere d'aiuto.

{ head -n 18; tail -n 14; } < file.txt | cat -n

0

Per stampare le prime 10 e ultime 10 righe di un file, puoi provare questo:

cat <(head -n10 file.txt) <(tail -n10 file.txt) | less


0
sed -n "1,10p; $(( $(wc -l ${aFile} | grep -oE "^[[:digit:]]+")-9 )),\$p" "${aFile}"

NOTA : la variabile aFile contiene il percorso completo del file .


0

Direi che a seconda della dimensione del file, potrebbe non essere desiderabile leggere attivamente nel suo contenuto. In quella circostanza, penso che alcuni semplici script di shell dovrebbero essere sufficienti.

Ecco come di recente ho gestito questo per un numero di file CSV molto grandi che stavo analizzando:

$ for file in *.csv; do echo "### ${file}" && head ${file} && echo ... && tail ${file} && echo; done

Questo stampa le prime 10 righe e le ultime 10 righe di ciascun file, stampando anche il nome del file e alcuni puntini di sospensione prima e dopo.

Per un singolo file di grandi dimensioni, è possibile semplicemente eseguire quanto segue per lo stesso effetto:

$ head somefile.csv && echo ... && tail somefile.csv

0

Consuma stdin, ma semplice e funziona per il 99% dei casi d'uso

head_and_tail

#!/usr/bin/env bash
COUNT=${1:-10}
IT=$(cat /dev/stdin)
echo "$IT" | head -n$COUNT
echo "..."
echo "$IT" | tail -n$COUNT

esempio

$ seq 100 | head_and_tail 4
1
2
3
4
...
97
98
99
100
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.