Comando per visualizzare le prime e le ultime righe di un file


23

Ho un file con molte righe e ogni riga ha un timestamp all'inizio, come

[Thread-3] (21/09/12 06:17:38:672) logged message from code.....

Quindi, controllo spesso 2 cose da questo file di registro.

  1. Vengono inoltre fornite le prime righe che presentano le condizioni globali e l'ora di inizio.
  2. Ultime righe, che ha lo stato di uscita con alcune altre informazioni.

Esiste un singolo comando rapido e pratico che potrebbe farmi visualizzare solo la prima e l'ultima riga di un file?


2
Quali sono le condizioni globali e non head and tailfunziona per te?
margherita,

Questa è la parte del mio file di registro. Stavo cercando di essere elaborativo. Puoi ignorarlo.
mtk

La tua soluzione mi sta bene. Se vuoi più comodità, trasformalo in una funzione shell (anche un alias potrebbe fare).
vonbrand,

@vonbrand Il problema è che non lo soN
Bernhard,

@ Bernardo, non sono un sed(1)esperto, ma ci sono modi per riporre roba per un uso successivo. Forse vale la pena guardare lì. OTOH, probabilmente tirerei su uno script Perl (o qualsiasi altra cosa) per farlo se usato frequentemente, poiché ne ho più familiarità.
vonbrand,

Risposte:


12

Puoi usarlo sedo awkfarlo con un solo comando. Tuttavia perderai velocità, causa sede awkdovrai comunque eseguire l'intero file. Da un punto di vista della velocità è molto meglio creare una funzione o ogni volta in combinazione di tail+ head. Questo ha il rovescio della medaglia di non funzionare se l'input è una pipe, tuttavia è possibile utilizzare la sostituzione di processo, nel caso in cui la shell lo supporti (vedere l'esempio di seguito).

first_last () {
    head -n 10 -- "$1"
    tail -n 10 -- "$1"
}

e basta lanciarlo come

first_last "/path/to/file_to_process"

per procedere alla sostituzione del processo (solo bash, zsh, ksh come shell):

first_last <( command )

ps. puoi anche aggiungere a grepper verificare se esistono le tue "condizioni globali".


-n 10è il valore predefinito, no?
l0b0

@ l0b0 sì, è l'impostazione predefinita. -n 10non è necessario qui.
corsa

20

@rush ha ragione sull'utilizzare head + tail in modo che sia più efficiente per file di grandi dimensioni, ma per file di piccole dimensioni (<20 righe), alcune righe potrebbero essere emesse due volte.

{ head; tail;} < /path/to/file

sarebbe ugualmente efficiente, ma non avrebbe il problema sopra.


A differenza della soluzione rush, questo non funziona in una shell POSIX.
Marco,

2
@Marco Huh? Qui vengono utilizzati solo costrutti POSIX. Cosa vedi che non va?
Gilles 'SO- smetti di essere malvagio',

2
@Gilles Ho perso lo spazio: {head; tail;} < filefunziona in zsh ma fallisce in sh. { head; tail;} < filefunziona sempre. Scusa per il rumore.
Marco,

@Marco, se ci fossero problemi, sarebbe con head, non conchiglia. POSIX richiede headdi lasciare il cursore nel file appena oltre quelle 10 righe per i file regolari. Potrebbe sorgere un problema per le headimplementazioni non POSIX (le versioni molto vecchie di GNU head erano non conformi in quel caso, ma parliamo di decenni) o se il file non è ricercabile (come named pipe o socket, ma poi il altra soluzione avrebbe lo stesso problema).
Stéphane Chazelas,

1
@FCTW,sudo sh -c '{ head; tail;} < /path/to/file'
Stéphane Chazelas

9

La { head; tail; }soluzione non funzionerebbe su pipe (o socket o altri file non ricercabili) perché headpotrebbe consumare troppi dati mentre legge da blocchi e non può cercare su una pipe potenzialmente lasciando il cursore all'interno del file oltre ciò che tailsi intende selezionare.

Quindi, potresti usare uno strumento che legge un carattere alla volta come quello della shell read(qui usando una funzione che prende il numero di linee di testa e di coda come argomenti).

head_tail() {
  n=0
  while [ "$n" -lt "$1" ]; do
    IFS= read -r line || { printf %s "$line"; break; }
    printf '%s\n' "$line"
    n=$(($n + 1))
  done
  tail -n "${2-$1}"
}
seq 100 | head_tail 5 10
seq 20 | head_tail 5

o implementare tailin awk per esempio come:

head_tail() {
  awk -v h="$1" -v t="${2-$1}" '
    {l[NR%t]=$0}
    NR<=h
    END{
      n=NR-t+1
      if(n <= h) n = h+1
      for (;n<=NR;n++) print l[n%t]
    }'
}

Con sed:

head_tail() {
  sed -e "1,${1}b" -e :1 -e "$(($1+${2-$1})),\$!{N;b1" -e '}' -e 'N;D'
}

(anche se attenzione che alcune sedimplementazioni hanno una bassa limitazione sulla dimensione del loro spazio del modello, quindi fallirebbe per grandi valori del numero di linee di coda).


4

Utilizzando la bashsostituzione del processo, è possibile effettuare le seguenti operazioni:

make_some_output | tee >(tail -n 2) >(head -n 2; cat >/dev/null) >/dev/null

Si noti che le linee non sono garantite per essere in ordine, anche se per file più lunghi di circa 8 KB, molto probabilmente lo saranno. Questo taglio di 8kB è la dimensione tipica del buffer di lettura ed è correlato al motivo per cui | {head; tail;}non funziona per file di piccole dimensioni.

Il cat >/dev/nullè necessario per mantenere il headvivo pipeline. Altrimenti teeuscirà presto, e mentre otterrai l'output tail, sarà da qualche parte nel mezzo dell'input, piuttosto che alla fine.

Infine, perché >/dev/nullinvece di, diciamo, passare tailad un altro |? Nel seguente caso:

make_some_output | tee >(head -n 2; cat >/dev/null) | tail -n 2  # doesn't work

headLo stdout viene inserito nella pipe tailpiuttosto che nella console, che non è affatto ciò che vogliamo.


Quando la testa o la coda finiscono di scrivere l'output desiderato, chiudono lo stdin ed escono. Ecco da dove viene SIGPIPE. Normalmente questa è una buona cosa, stanno scartando il resto dell'output, quindi non c'è motivo per cui l'altro lato del tubo continui a passare il tempo a generarlo.
derobert,

Cosa rende probabile che l'ordine venga confermato? Probabilmente sarà per un file di grandi dimensioni, perché taildeve funzionare più a lungo, ma mi aspetto (e vedo) che fallisce circa la metà del tempo per input brevi.
Gilles 'SO- smetti di essere malvagio' il

Otterrai SIGPIPE tee >(head) >(tail)per gli stessi motivi ( >(...)che tra l'altro è una funzionalità ksh ora supportata sia da zsh che da bash) usa anche le pipe. Potresti farlo ... | (trap '' PIPE; tee >(head) >(tail) > /dev/null)ma vedrai ancora alcuni messaggi di errore di pipe spezzate da tee.
Stéphane Chazelas,

Sul mio sistema (bash 4.2.37, coreutils 8.13), tailè quello che viene ucciso da SIGPIPE, non tee, e tailnon sta scrivendo su una pipe. Quindi deve provenire da un kill(), giusto ?. E questo succede solo quando sto usando la |sintassi. stracedice che teenon sta chiamando kill()... quindi forse bash?
Jander,

1
@Jander, prova a nutrire più di 8k comeseq 100000 | tee >(head -n1) >(tail -n1) > /dev/null
Stéphane Chazelas,

3

Utilizzando ed(che leggerà l'intero file nella RAM):

# cf. http://wiki.bash-hackers.org/howto/edit-ed
printf '%s\n' 'H' '1,10p' '$-10,$p' 'q' | ed -s file

Più breve:ed -s file <<< $'11,$-10d\n,p\nq\n'
don_crissti,

2

La prima soluzione di Stephane in una funzione in modo da poter usare gli argomenti (funziona in qualsiasi shell tipo Bourne o POSIX):

head_tail() {
    head "$@";
    tail "$@";
}

Ora puoi farlo:

head_tail -n 5 < /path/to/file

Questo ovviamente presuppone che tu stia guardando solo un file e come la soluzione di Stephane funzioni (in modo affidabile) solo su file regolari (ricercabili).


2

Con l' opzione -u( --unbuffered) di GNU sed, puoi usare sed -u 2qcome alternativa senza buffer a head -n2:

$ seq 100|(sed -u 2q;tail -n2)
1
2
99
100

(head -n2;tail -n2)fallisce quando le ultime righe fanno parte del blocco dell'input che viene consumato da head:

$ seq 1000|(head -n2;tail -n2)
1
2
999
1000
$ seq 100|(head -n2;tail -n2)
1
2

questa dovrebbe essere la risposta migliore! funziona come un fascino!
Ben Usman,

1

Mi sono imbattuto in qualcosa di simile oggi, dove avevo bisogno solo dell'ultima riga e di alcune righe dalla parte anteriore di un flusso e ho pensato a quanto segue.

sed -n -e '1{h}' -e '2,3{H}' -e '${H;x;p}'

Ho letto questo come: inizializzare lo spazio di trattenimento con il contenuto della prima riga, aggiungere le righe 2-3 nello spazio di trattenimento, in EOF aggiungere l'ultima riga allo spazio di trattenuta, scambiare lo spazio di ritenzione e modello e stampare il motivo spazio.

Forse qualcuno con più sed-fu di me può capire come generalizzare questo per stampare le ultime poche righe del flusso indicati in questa domanda, ma non ho bisogno e non riusciva a trovare un modo semplice per fare matematica basata sul $indirizzo in sedo forse gestendo spazio stretta in modo che solo le ultime righe sono in esso quando EOFviene raggiunto.


1

Puoi provare Perl, se lo hai installato:

perl -e '@_ = <>; @_=@_[0, -3..-1]; print @_'

Funzionerà con la maggior parte dei file, ma legge l'intero file in memoria prima di elaborarlo. Se non hai familiarità con le sezioni Perl, "0" tra parentesi quadre significa "prendi la prima riga" e "-3 ...- 1" significa "prendi le ultime tre righe". Puoi personalizzare entrambi in base alle tue esigenze. Se devi elaborare file molto grandi (ciò che è "grande" può dipendere dalla tua RAM e forse dalle dimensioni dello swap), potresti voler scegliere:

perl -e 'while($_=<>){@_=(@_,$_)[0,-3..-1]}; print @_'

potrebbe essere un po 'più lento, perché esegue una suddivisione in ogni iterazione, ma è indipendente dalla dimensione del file.

Entrambi i comandi dovrebbero funzionare sia in pipe che con file regolari.

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.