Trova tutte le occorrenze in un file con sed


15

Uso del sistema operativo OPEN STEP 4.2 ... Attualmente sto usando il seguente sedcomando:

sed -n '1,/141.299.99.1/p' TESTFILE | tail -3

Questo comando troverà un'istanza in un file con l'ip di 141.299.99.1 e includerà anche 3 righe prima che sia tutto a posto, con l'eccezione che vorrei anche trovare tutte le istanze dell'IP e le 3 righe prima di esso e non solo il primo.


1
Ti preghiamo di includere sempre il tuo sistema operativo. Le soluzioni dipendono molto spesso dal sistema operativo in uso. Stai usando Unix, Linux, BSD, OSX, qualcos'altro? Quale versione?
terdon

GRANDE PUNTO! L'uso di Open Step versione 4.2 è piuttosto vecchio e le shell incluse non includono molte delle funzionalità menzionate nelle risposte seguenti.
Dale,

Per curiosità: cos'è un sistema OPEN STEP 4.2 e a cosa serve oggi?
Thorbjørn Ravn Andersen,

(e se Perl è disponibile puoi davvero fare molte cose carine solo con quello)
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen Forse è questo: en.wikipedia.org/wiki/OpenStep
Barmar

Risposte:


4

Ecco un tentativo di emulare grep -B3usando una finestra mobile di sed, basata su questo esempio GNU sed (ma si spera conforme a POSIX - con riconoscimento a @ StéphaneChazelas):

sed -e '1h;2,4{;H;g;}' -e '1,3d' -e '/141\.299\.99\.1/P' -e '$!N;D' file

Le prime due espressioni innescano un buffer di pattern multilinea e gli consentono di gestire il caso limite in cui sono presenti meno di 3 righe di contesto precedente prima della prima corrispondenza. L'espressione centrale (corrispondenza regex) stampa una linea dalla parte superiore della finestra fino a quando il testo della corrispondenza desiderato non si è increspato attraverso il buffer del modello. Il finale $!N;Dscorre la finestra di una riga tranne quando raggiunge la fine dell'input.


-enon è specifico per GNU. Per essere POSIX / portatile, ne hai bisogno in quanto non può esserci nulla dopo }(e hai bisogno di un ;prima).
Stéphane Chazelas,

Grazie @ StéphaneChazelas - quindi stai dicendo che per essere POSIX / portatile, il primo gruppo deve essere diviso / modificato come -e '1h;2,4{H;g;}' -e '1,3d'? Non ho un sistema non GNU su cui testare (e allo --posixswitch sed GNU non sembra importare).
steeldriver,

1
Sì, su Linux, puoi testare un'implementazione diversa con il sedtoolchest dal cimelio che è un discendente della tradizionale sed Unix. Le specifiche POSIX / Unix sedsono su pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
Stéphane Chazelas

Ricevo un evento non trovato su nessuno dei due: N; D ': Evento non trovato. Mi manca la sintassi da qualche parte? Grazie!!
Dale,

Mi spiace di aver appena realizzato che la mia modifica più recente ha omesso una virgoletta singola di chiusura dopo la prima espressione. L'ho corretto ora - puoi riprovare con l'espressione sopra, per favore?
steeldriver,

10

grep farà un lavoro migliore di questo:

grep -B 3 141.299.99.1 TESTFILE

I -B 3mezzi per stampare le tre righe prima di ogni corrispondenza. Questo verrà stampato --tra ogni gruppo di linee. Per disabilitarlo, usa--no-group-separator anche.

L' -Bopzione è supportata da GNUgrep e anche dalla maggior parte delle versioni BSD ( OSX , FreeBSD , OpenBSD , NetBSD ), ma tecnicamente non è un'opzione standard.


1
Michael Homer - Grazie. Non ho l'opzione - B. Altre idee?
Dale,

@Dale Puoi installare GNU grep? Questo ti darà la possibilità.
Barmar,

9

Con sedte puoi fare una finestra scorrevole.

sed '1N;$!N;/141.299.99.1/P;D'

Questo lo fa. Ma attenzione - bashil comportamento folle di espandersi ! anche quando citato !!! nella stringa di comando dalla cronologia dei comandi potrebbe renderlo un po 'folle. Prefisso il comando con set +H;se trovi questo è il caso. Per poi riattivarla (ma perchè ???) fareset -H in seguito.

Questo, naturalmente, si applicherebbe solo se si stesse utilizzando bash- anche se non credo che ci si trovi. Sono abbastanza sicuro con cui stai lavorando csh- (che sembra essere la shell il cui comportamento folle bashemula con l'espansione della storia, ma forse non agli estremi che la shell ha preso) . Quindi probabilmente a\! dovrebbe funzionare. Io spero.

È tutto un codice portatile: POSIX descrive così i suoi tre operatori: (anche se vale la pena notare che ho solo confermato che questa descrizione esisteva già nel 2001)

[2addr]N Aggiungi la riga successiva di input, meno la sua \newline finale, allo spazio del pattern, usando un incorporato\n ewline per separare il materiale aggiunto dal materiale originale. Si noti che il numero di riga corrente cambia.

[2addr]P Scrivi lo spazio del modello, fino al primo \n ewline, sull'output standard.

[2addr]D Elimina il segmento iniziale dello spazio del motivo attraverso la prima \newline e avvia il ciclo successivo.

Quindi sulla prima riga aggiungi una linea extra allo spazio del motivo, quindi è simile al seguente:

^line 1s contents\nline 2s contents$

Quindi sulla prima riga e su ogni riga successiva, tranne l'ultima, si aggiunge un'altra riga allo spazio del motivo. Quindi sembra così:

^line 1\nline 2\nline 3$

Se il tuo indirizzo IP si trova dentro di te, vai Palla prima riga, quindi basta la riga 1 qui. Alla fine di ogni ciclo si Delimina lo stesso e si ricomincia da capo con ciò che rimane. Quindi il prossimo ciclo assomiglia a:

^line 2\nline 3\nline 4$

...e così via. Se il tuo IP si trova su uno di questi tre, il più vecchio verrà stampato - ogni volta. Quindi sei sempre solo tre righe avanti.

Ecco un breve esempio. Verrà stampato un buffer di tre righe per ogni numero che termina con zero:

seq 10 52 | sed '1N;$!N;/0\(\n\|$\)/P;D'

10
18
19
20
28
29
30
38
39
40
48
49
50

Questo è un po 'più complicato del tuo caso perché ho dovuto alternare da 0\nNewline o0$ fine dello spazio del modello per assomigliare più da vicino al tuo problema - ma sono leggermente diversi in quanto ciò richiede un ancoraggio - che può essere un po' difficile da fare poiché lo spazio-modello cambia costantemente.

Ho usato i casi dispari di 10 e 52 per dimostrare che fino a quando l'ancoraggio è flessibile, lo è anche l'output. Completamente portabile, posso ottenere gli stessi risultati contando invece sull'algoritmo e facendo:

seq 10 52 | sed '1N;$!N;/[90]\n/P;D'

E amplia la ricerca limitando la mia finestra - da 0 a 9 e 0 e da 3 righe a due.

Ad ogni modo, hai avuto l'idea.


Grazie per tutto il tuo duro lavoro. Siamo spiacenti, dove metterei il nome del file che vorrei che cercasse?
Dale,

@Dale - il mio male. sed '...' $filename. A proposito - ho lasciato i punti dalla tua stringa di ricerca, ma quelli non sono in realtà periodi in uno schema - quelli rappresentano un singolo carattere. Probabilmente dovresti fare oct\.oct\.oct\.octper fuggire in modo che corrispondano solo ai periodi.
Mikeserv,

Ho provato a cat con esso e diversi simboli <> e ottengo l'evento non trovato che ottengo qui con altre soluzioni, quindi mi chiedo se il mio sistema operativo non è compatibile con queste soluzioni.
Dale,

ora risulta con -> N; /141.299.99.1/P; D ': Evento non trovato.
Dale,

@Dale - vedi l'aggiornamento. Dovrebbe aiutarti.
Mikeserv,

4

Dal momento che si parla che non si ha la -Bpossibilità di grep, è possibile utilizzare Perl (per esempio) per fare una scorrevole di una finestra di 4 linee:

perl -ne '
    push @window,$_;
    shift @window if @window > 4;
    print @window if /141\.299\.99\.1/
' your_file

La risposta di Ramesh fa una cosa simile con awk.


Non sono sicuro che la mia versione di Perl lo supporti, ma ci proverò. Grazie mille per aver dedicato del tempo a rispondere alla mia domanda - molto grato!
Dale,

@Dale Prego. Dubito che questo codice faccia uso di funzionalità Perl all'avanguardia.
Joseph R.,

4

Se disponibile, puoi usare pcregrep :

pcregrep -M '.*\n.*\n.*\n141.299.99.1' file

Verifica se ho PCREGREP. Mi piace la compattezza del comando. Molto grato per il tuo tempo e i tuoi sforzi. Grazie!!!
Dale,

4

È possibile implementare lo stesso approccio di base delle altre risposte non grep nella shell stessa (ciò presuppone una shell relativamente recente che supporti =~):

while IFS= read -r line; do 
    [[ $line =~ 141.299.99.1 ]] && printf "%s\n%s\n%s\n%s\n" $a $b $c $line;
    a=$b; b=$c; c=$line; 
done < file 

In alternativa, è possibile slurp l'intero file in un array:

perl -e '@F=<>; 
        for($i=0;$i<=$#F;$i++){
          print $F[$i-3],$F[$i-2],$F[$i-1],$F[$i] if $F[$i]=~/141.299.99.1/
        }' file 

La mia shell è molto vecchia - Steve Jobs Open Step. Ottima idea però e grazie per il tuo tempo !!! Dale
Dale,

@Dale l'approccio perl funzionerà praticamente ovunque. Comunicaci il tuo sistema operativo (aggiungilo alla tua domanda) in questo modo possiamo suggerire cose che funzioneranno per te.
terdon

Se copio il tuo Perl e lo inserisco in NotePad e lo metto su una riga, funziona! Domanda: se volessi, diciamo 10 righe prima del modello di corrispondenza, dove cambierò da 3 a 10? Grazie!
Dale,

Vedo che posso aggiungere più righe indietro aggiungendo più $ F [$ iX], dichiarazioni. Grazie!
Dale,

4

Se il tuo sistema non supporta il grepcontesto, puoi invece provare ack-grep :

ack -B 3 141.299.99.1 file

ack è uno strumento come grep, ottimizzato per i programmatori.


Mi piace la compattezza del comando, ma il mio sistema non supporta la ricerca delle pagine man. Ottima idea e grazie mille per il tuo tempo !!! Dale
Dale,

@Dale: Sorprendente! Qual è il tuo sistema operativo? Se hai perl, puoi usare ack.
cuonglm,

2
awk '/141.299.99.1/{for(i=1;i<=x;)print a[i++];print} {for(i=1;i<x;i++)
     a[i]=a[i+1];a[x]=$0;}'  x=3 filename

In questa awksoluzione, viene utilizzata una matrice che conterrà sempre 3 righe prima del modello corrente. Pertanto, quando il motivo viene abbinato, viene stampato il contenuto dell'array insieme al motivo corrente.

analisi

-bash-3.2$ cat filename
10.0.0.1
10.0.0.2
10.0.0.3
10.0.0.4
141.299.99.1
10.0.0.5
10.0.0.6
10.0.0.7
10.0.0.8
10.0.0.9
10.0.0.10
141.299.99.1
10.0.0.11
10.0.0.12
10.0.0.13
10.0.0.14
10.0.0.15
10.0.0.16
141.299.99.1
10.0.0.17
10.0.0.18
10.0.0.19

Dopo aver eseguito il comando, l'output è,

10.0.0.2
10.0.0.3
10.0.0.4
141.299.99.1
10.0.0.8
10.0.0.9
10.0.0.10
141.299.99.1
10.0.0.14
10.0.0.15
10.0.0.16
141.299.99.1

così dettagliato - grazie mille. Lo proverò. Molto grato per il tuo tempo !! Dale
Dale,

Ho un file di prova e la tua soluzione funziona! Il problema è che quando lo eseguo sul mio file di produzione di grandi dimensioni viene restituito con il numero di record troppo lungo, quindi l'output non è in grado di funzionare con il comando. Il mio comando originale nella parte superiore di questa pagina funziona ma trova solo un'istanza. Apprezzo il vostro aiuto. C'è qualcosa che posso fare con il mio comando originale per farlo trovare più di un istatnce?
Dale,

1

Nella maggior parte di questi, /141.299.99.1/corrisponderà anche (ad esempio) 141a299q99+1o 141029969951perché .in un'espressione regolare può rappresentare qualsiasi personaggio.

Utilizzando /141[.]299[.]99[.]1/è più sicuro, ed è possibile aggiungere un contesto aggiuntivo all'inizio e alla fine di tutto l'espressione regolare per assicurarsi che non corrisponde 3141., .12, .104, etc.


1
Questo è un buon punto - e uno che ho anche considerato. Tuttavia, ho usato la stringa fornita dal richiedente come una partita funzionante nota - e gli ho informato personalmente della stessa quando mi è stata data l'opportunità. Ad ogni modo - non tutte queste - la risposta di steeldriver ha citato la partita di caratteri dall'inizio.
mikeserv,
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.