2012-01-22 19 views
7

Sto cercando di trovare un one-liner di shell succinta che mi dia tutte le righe in un file fino al un po 'di pattern.Modo succinto di stampare tutte le linee fino all'ultima riga che corrisponde a un dato motivo

Il caso d'uso sta scaricando tutte le righe in un file di registro finché non vedo qualche indicatore che indica che il server è stato riavviato.

Ecco un stupido guscio unico modo che:

tail_file_to_pattern() { 
    pattern=$1 
    file=$2 

    tail -n$((1 + $(wc -l $file | cut -d' ' -f1) - $(grep -E -n "$pattern" $file | tail -n 1 | cut -d ':' -f1))) $file 
} 

Un modo un po 'più affidabile Perl che prende il file stdin:

perl -we ' 
    push @lines => $_ while <STDIN>; 
    my $pattern = $ARGV[0]; 
    END { 
     my $last_match = 0; 
     for (my $i = @lines; $i--;) { 
      $last_match = $i and last if $lines[$i] =~ /$pattern/; 
     } 
     print @lines[$last_match..$#lines]; 
    } 
' 

E, naturalmente, si potrebbe fare che essere più efficiente aprendo il file, cercando fino alla fine e cercando fino a quando non hai trovato una linea corrispondente.

E 'facile da stampare tutto come della prima occorrenza, ad es .:

sed -n '/PATTERN/,$p' 

Ma non hanno escogitato un modo per stampare tutto come del ultima avvenimento.

+1

Il titolo dice "tutte le linee fino al l'ultimo modello "ma i tuoi due script di esempio stampano tutte le linee dall'ultimo modello alla fine. Presumo che sia il titolo a essere fuorviante? –

+0

Se il pattern sarà di solito presente e vicino alla fine, potresti prendere in considerazione [File :: ReadBackwards] (http://search.cpan.org/perldoc?File::ReadBackwards) (passando in un buffer fino a raggiungere il modello o l'inizio del file). – ikegami

risposta

3

suggerisco una semplificazione dello script di shell:

tail -n +$(grep -En "$pattern" "$file" | tail -1 | cut -d: -f1) "$file" 

E 'sostanzialmente più conciso perché:

  • utilizza l'opzione di coda + per stampare dalla linea data fino alla fine, invece di dover per calcolare la distanza da lì fino alla fine.
  • Utilizza modi più concisi per esprimere le opzioni della riga di comando.

E corregge un bug citando $ file (quindi funzionerà su file i cui nomi contengono spazi).

4

alternativa: tac "$file" | sed -n '/PATTERN/,$p' | tac

EDIT: Se non si dispone di tac emulare definendo

tac() { 
    cat -n | sort -nr | cut -f2 
} 

brutto ma POSIX.

+0

Non ho un binario 'tac'. Dato che l'OP non ha specificato un sistema operativo, è probabilmente meglio offrire soluzioni che funzionino a tutti i livelli. – ghoti

+0

Puoi usare 'tail -r' al posto di' tac'. Anche se questa soluzione non è esattamente quale (il corpo di) la domanda sta chiedendo. Per questo, avresti bisogno di "sed -n" 1,/$ {pattern}/p "'. –

+1

@ghoti: Beh, sembra che tu non stia usando GNU/coreutils. Apparentemente 'tac' non è POSIX. Se insisti su POSIX, usa 'cat -n | ordina -nr | cut -f2' invece di 'tac' (Oh, stiamo diventando di nuovo brutti!) –

4

Carica i dati in un array riga per riga e allontana l'array quando trovi una corrispondenza di modello. Stampa ciò che è rimasto alla fine.

while (<>) { 
    @x=() if /$pattern/; 
    push @x, $_; 
} 
print @x; 

come un one-liner:

perl -ne '@x=() if /$pattern/;push @x,$_;END{print @x}' input-file 
3

comando di Sed q farà il trucco:

sed "/$pattern/q" $file 

che stamperà tutte le linee fino a quando si arriva alla linea con il modello . Dopo questo, sed stamperà quell'ultima riga e uscirà.

+0

Questo fa quello che suggeriscono il titolo e la prima riga della domanda, ma non quello che l'interrogante in realtà vuole, penso. Vuole tutte le linee * dopo * e includendo l'ultima riga che corrisponde a un dato motivo. –

+0

@RobDavis - Hai ragione. Ho letto il primo paragrafo e ho pensato _ "Ehi, questo è semplice" _. Probabilmente dovrò inventare qualcosa con Awk –

6

Ecco una soluzione solo-sed. Per stampare ogni linea in $filepartendo l'ultima riga che corrisponde $pattern:

sed -e "H;/${pattern}/h" -e '$g;$!d' $file 

Nota che, come i tuoi esempi, questo funziona correttamente solo se il file contiene il modello. Altrimenti, emette l'intero file.

Ecco una ripartizione di ciò che fa, con i comandi sed tra parentesi:

  • [H] Appendi ogni linea di di sed "spazio hold", ma non eco a stdout [d].
  • Quando incontriamo il modello, [h] butta via lo spazio di attesa e ricomincia da capo con la linea corrispondente.
  • Quando arriviamo alla fine del file, copia lo spazio di attesa nello spazio pattern [g] in modo che risuoni per lo stdout.

Si noti inoltre che è probabile che si rallenti con file di grandi dimensioni, dal momento che qualsiasi soluzione a passaggio singolo dovrà conservare una serie di linee in memoria.

+0

+1: è una gestione sedentaria e potente. È una linea e fa ciò che l'OP vuole. –

1

Questo titolo di domande e descrizione non corrispondono.

Per il titolo della domanda, +1 per la risposta di @David W.. Inoltre:

sed -ne '1,/PATTERN/p' 

Per la domanda nel corpo, hai già alcune soluzioni.

Si noti che tac è probabilmente specifico per Linux. Non sembra esistere in BSD o OSX. Se vuoi una soluzione multi-piattaforma, non fare affidamento su tac.

Ovviamente, quasi tutte le soluzioni richiedono che i dati vengano archiviati in memoria o inviati una volta per l'analisi e una seconda volta per l'elaborazione. Per exampel:

#!/usr/local/bin/bash 

tmpfile="/tmp/`basename $0`,$$" 
trap "rm $tmpfile" 0 1 2 5 
cat > $tmpfile 

n=`awk '/PATTERN/{n=NR}END{print NR-n+1}' $tmpfile` 

tail -$n $tmpfile 

Nota che il mio uso di tail è per FreeBSD. Se usi Linux, probabilmente avrai bisogno di tail -n $n $tmpfile.

+0

Puoi usare 'tail -r' su OSX per ottenere la funzionalità di' tac'. –

+0

È vero, ma non è multipiattaforma poiché l'opzione '-r' non esiste in Linux. Se ne raccomando uno, sarebbe ipocrita da parte mia non raccomandare l'altro. :) – ghoti

+0

Capisco e sono completamente d'accordo - stavo semplicemente facendo notare, soprattutto per i lettori in futuro, che se vogliono usare 'tac' su OS X, possono usare' tail -r' invece ... piuttosto che lasciando la tua affermazione dicendo che non sembra esistere. –

1

Rob Davis mi ha fatto notare quello che detto che volevi non è quello che si ha realmente chiesto:

Hai detto:

Sto cercando di trovare un guscio succinta uno -liner che mi darà tutte le righe in un file fino al un po 'di schema.

ma poi alla fine del tuo post, lei ha detto:

Ma io non sono venuto su con un modo per stampare tutto come del l'ultimo evento frequente.

Ti ho già dato la risposta per il tuo first question. Ecco una risposta di una riga per la tua seconda domanda: Stampa da un'espressione regolare alla fine del file:

awk '{ if ($0 ~ /'"$pattern"'/) { flag = 1 } if (flag == 1) { print $0 } }' $file 

Un simile Perl one-liner:

export pattern="<regex>" 
export file="<file>" 
perl -ne '$flag=1 if /$ENV{pattern}/;print if $flag;' $file 
+0

Tranne che vuole le linee dopo l'ultima occorrenza del pattern, credo. –

+0

@RobDavis - Hai ragione. La tua [soluzione] (http://stackoverflow.com/a/8967705/368630) è la migliore. È indipendente da una linea e da una piattaforma. –

Problemi correlati