Come ottenere tutte le linee tra la prima e l'ultima occorrenza dei modelli?


8

Come posso tagliare un file (flusso di input bene) in modo da ottenere solo le linee che vanno dalla prima occorrenza del pattern fooall'ultima occorrenza del pattern bar?

Ad esempio, considerare il seguente input:

A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest

Mi aspetto questo risultato:

foo
this 
foo
bar
something
something else
foo
bar

3
Flusso a passaggio singolo o un file? Questo è molto più facile da fare quando è consentito l'accesso casuale. Con un file troverai semplicemente il primo fooe l'ultimo bare stampi tutto il resto, se non altro. Con un flusso dovresti leggere fino al primo fooe bufferizzare tutte le righe successive in memoria fino a EOF, svuotando il buffer ogni volta che barviene visto un. Ciò potrebbe significare buffering dell'intero flusso in memoria.
jw013,

Risposte:


6
sed -n '/foo/{:a;N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba};'

La corrispondenza del modello sed /first/,/second/legge le righe una ad una. Quando una linea corrisponde ad /first/essa, la ricorda e attende con impazienza la prima corrispondenza per il /second/modello. Allo stesso tempo applica tutte le attività specificate per quel modello. Dopo che il processo si avvia ancora e ancora fino alla fine del file.

Non è quello di cui abbiamo bisogno. Dobbiamo cercare l'ultima corrispondenza del /second/modello. Pertanto costruiamo costruzioni che cercano solo la prima voce /foo/. Quando trovato il ciclo ainizia. Aggiungiamo una nuova riga al buffer delle corrispondenze con Ne controlliamo se corrisponde al modello /bar/. In tal caso, lo stampiamo e cancelliamo il buffer di corrispondenza e saltiamo all'inizio del ciclo con ba.

Inoltre, è necessario eliminare il simbolo di nuova riga dopo la pulizia del buffer con /^\n/s/^\n//. Sono sicuro che esiste una soluzione molto migliore, purtroppo non mi è venuta in mente.

Spero sia tutto chiaro.


1
Funziona! Sarebbe fantastico se tu potessi guidarci attraverso la costruzione di un tale comando. Mi sentirei stupido semplicemente copiandolo / incollandolo da qualche sito Web online;)
rahmu,

1
Mi dispiace non ho pubblicato la spiegazione con la risposta. Ora è nel post.
corsa il

In alcune sedversioni, ad es. BSD sed (che si trova sui Mac), i tag devono essere seguiti da una nuova riga o fine della stringa, quindi è necessario il seguente ritocco: sed -n -e '/foo/{:a' -e 'N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba' -e '};' questo funziona anche su GNU sed, quindi penso che questa modifica ( -eargs multipli terminare un arg dopo il nome di ciascun ramo) è una buona abitudine portatile da prendere quando si usano i rami in sed.
Wildcard il

4

Lo farei con un po 'di fodera Perl.

cat <<EOF | perl -ne 'BEGIN { $/ = undef; } print $1 if(/(foo.*bar)/s)'
A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest
EOF

i rendimenti

foo
this 
foo
bar
something
something else
foo
bar

3
Se questo fosse code-golf, potresti usare al Eposto ee -00777invece del $/bit (vedi perlrun (1)). Il che lo accorcerebbe a: perl -0777 -nE 'say /(foo.*bar)/s'ancora leggibile.
Thor,

1
Non sapevo di queste bandiere! Sono sicuro che soprattutto -0[octal]troverà la strada nel mio flusso di lavoro! Grazie per quello
user1146332

3

Ecco una soluzione sed GNU a due passaggi che non richiede molta memoria:

< infile                                     \
| sed -n '/foo/ { =; :a; z; N; /bar/=; ba }' \
| sed -n '1p; $p'                            \
| tr '\n' ' '                                \
| sed 's/ /,/; s/ /p/'                       \
| sed -n -f - infile

Spiegazione

  • La prima sedinvocazione passa all'infile e trova la prima occorrenza di fooe tutte le occorrenze successive di bar.
  • Questi indirizzi vengono quindi modellati in una nuova sedsceneggiatura con due invocazioni di sede una tr. L'output del terzo sedè [start_address],[end_address]p, senza parentesi.
  • L'invocazione finale di sedpassa infilenuovamente, stampando gli indirizzi trovati e tutto il resto.

2

Se il file di input si adatta comodamente alla memoria, è semplice .

Se il file di input è enorme, è possibile utilizzare csplitper dividerlo in pezzi all'inizio fooe successivamente, barquindi assemblare i pezzi. I pezzi vengono chiamati piece-000000000, piece-000000001ecc. Scegli un prefisso (qui, piece-) che non si scontrerà con altri file esistenti.

csplit -f piece- -n 9 - '%foo%' '/bar/' '{*}' <input-file

(Sui sistemi non Linux, dovrai usare un numero elevato all'interno delle parentesi graffe, ad esempio {999999999}, e passare l' -kopzione. Quel numero è il numero di barpezzi.)

Puoi assemblare tutti i pezzi con cat piece-*, ma questo ti darà tutto dopo il primo foo. Quindi prima rimuovi l'ultimo pezzo. Poiché i nomi dei file prodotti da csplitnon contengono caratteri speciali, è possibile lavorarli senza prendere precauzioni speciali per la citazione, ad es. Con

rm $(echo piece-* | sed 's/.* //')

o equivalentemente

rm $(ls piece-* | tail -n 1)

Ora puoi unire tutti i pezzi e rimuovere i file temporanei:

cat piece-* >output
rm piece-*

Se vuoi rimuovere i pezzi quando sono concatenati per risparmiare spazio su disco, fallo in un ciclo:

mv piece-000000000 output
for x in piece-?????????; do
  cat "$x" >>output; rm "$x"
done

1

Ecco un altro modo con sed:

sed '/foo/,$!d;H;/bar/!d;s/.*//;x;s/\n//' infile

Aggiunge ogni riga /foo/,$nell'intervallo (le righe !non comprese in questo intervallo vengono deliminate) al Hvecchio spazio. Le righe non corrispondenti barvengono quindi eliminate. Sulle linee corrispondenti, lo spazio del motivo viene svuotato, xmodificato con lo spazio di attesa e la linea vuota iniziale nello spazio del motivo viene rimossa.

Con un input enorme e poche occorrenze di barquesto dovrebbe essere (molto) più veloce che trascinare ogni linea nello spazio del pattern e poi, ogni volta, controllare lo spazio del pattern per bar.
Ha spiegato:

sed '/foo/,$!d                     # delete line if not in this range
H                                  # append to hold space
/bar/!d                            # if it doesn't match bar, delete 
s/.*//                             # otherwise empty pattern space and
x                                  # exchange hold buffer w. pattern space then
s/\n//                             # remove the leading newline
' infile

Certo, se questo è un file (e si adatta alla memoria) potresti semplicemente eseguire:

 ed -s infile<<'IN'
.t.
/foo/,?bar?p
q
IN

perché ed può cercare avanti e indietro.
È anche possibile leggere un output del comando nel buffer di testo se la shell supporta la sostituzione di processo:

printf '%s\n' .t. /foo/,?bar?p q | ed -s <(your command)

o in caso contrario, con gnu ed:

printf '%s\n' .t. /foo/,?bar?p q | ed -s '!your command'

0

Utilizzando qualsiasi awk in qualsiasi shell su qualsiasi sistema UNIX e senza leggere l'intero file o il flusso di input in memoria contemporaneamente:

$ awk '
    f {
        rec = rec $0 ORS
        if (/bar/) {
            printf "%s", rec
            rec = ""
        }
        next
    }
    /foo/ { f=1; rec=$0 ORS }
' file
foo
this
foo
bar
something
something else
foo
bar

0

Grep potrebbe farlo anche (beh, GNU grep):

<infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'

<infile grep -ozP '        #  call grep to print only the matching section (`-o`)
                           #  use NUL for delimiter (`-z`) (read the whole file).
                           #  And using pcre regex.
(?s)foo.*bar               #  Allow the dot (`.`) to also match newlines.
' | tr '\0' '\n'           #  Restore the NULs to newlines.

Per l'input dal corpo della domanda:

$ <infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'
foo
this 
foo
bar
something
something else
foo
bar
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.