Elimina l'intervallo di linee sopra il motivo con sed (o awk)


28

Ho il seguente codice che rimuoverà le righe con il modello bananae 2 righe dopo di esso:

sed '/banana/I,+2 d' file

Fin qui tutto bene! Ma ne ho bisogno per rimuovere 2 righe prima banana , ma non riesco a ottenerlo con un "segno meno" o qualsiasi altra cosa (simile a cosa grep -v -B2 banana filedovrebbe fare ma non lo fa):

teresaejunior@localhost ~ > LC_ALL=C sed '-2,/banana/I d' file
sed: invalid option -- '2'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,-2 d' file
sed: -e expression #1, char 16: unexpected `,'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,2- d' file
sed: -e expression #1, char 17: unknown command: `-'

1
Il più semplice è quello di caricare tutti i dati in una matrice, saltare le linee indesiderate allora produrre ciò che rimane: awk '{l[m=NR]=$0}/banana/{for(i=NR-2;i<=NR;i++)delete l[i]}END{for(i=1;i<=m;i++)if(i in l)print l[i]}'. Questo non è efficiente, quindi questo è solo un suggerimento, non una soluzione.
arte

6
Fallo e basta tac file | sed ... | tac. : P
angus,

@angus Non ci ho pensato;)
Teresa e Junior

1
avresti potuto fare sed '/banana/,+2d' file che funzionerà anche
Akaks

1
Se sei aperto all'utilizzo di awk, è piuttosto semplice: awk 'tolower($0)~/bandana/{print prev[!idx];print prev[idx]} {idx=!idx;prev[idx]=$0}' filein poiché questo è un commento e non una risposta (ci sono già altre risposte), non entrerò in troppi dettagli, ma il punto cruciale è che hai sempre il due precedenti record in prev [0] e prev [1], il "più fresco" a seconda dell'iterazione ma sempre in prev[idx], quindi quando si stampa, si stampa in ordine !idxquindi idx. Indipendentemente da ciò, alternare idxe inserire il record corrente prev[idx].
Luv2 del

Risposte:


22

Sed non fa marcia indietro: una volta che ha elaborato una linea, ha finito. Quindi “trova una linea e stampa le precedenti N linee” non funzionerà così com'è, a differenza di “trova una linea e stampa le successive N linee” che è facile da innestare.

Se il file non è troppo lungo, poiché sembra che tu stia bene con le estensioni GNU, puoi usare tacper invertire le righe del file.

tac | sed '/banana/I,+2 d' | tac

Un altro angolo di attacco è mantenere una finestra scorrevole in uno strumento come awk. Adattarsi da C'è qualche alternativa agli switch -A -B -C di grep (per stampare poche righe prima e dopo)? (avviso: minimamente testato):

#!/bin/sh
{ "exec" "awk" "-f" "$0" "$@"; } # -*-awk-*-
# The array h contains the history of lines that are eligible for being "before" lines.
# The variable skip contains the number of lines to skip.
skip { --skip }
match($0, pattern) { skip = before + after }
NR > before && !skip { print NR h[NR-before] }
{ delete h[NR-before]; h[NR] = $0 }
END { if (!skip) {for (i=NR-before+1; i<=NR; i++) print h[i]} }

Uso: /path/to/script -v pattern='banana' -v before=2


2
sedpuò fare anche finestre scorrevoli, ma lo script risultante è in genere così illeggibile che è più semplice da usare awk.
jw013,

@Gilles .. La awksceneggiatura non è del tutto corretta; così com'è stampa le righe vuote e manca le ultime righe. Questo sembra risolverlo, ma potrebbe non essere l'ideale o giusto: if (NR-before in h) { print...; delete...; }... e nella ENDsezione: for (i in h) print h[i]... Inoltre, lo script awk stampa la riga corrispondente, ma la tac/secversione no; ma la domanda è un po 'ambigua su questo .. Lo script awk "originale", a cui hai fornito un link, funziona bene .. Mi piace ... Non sono sicuro di come la' mod 'sopra influisca sulla stampa dopo linee ...
Peter

@ Peter.O Grazie, lo script awk dovrebbe essere migliore ora. E mi ci sono voluti meno di 6-8 anni!
Gilles 'SO- smetti di essere malvagio' il

19

Questo è abbastanza facile con ex o vim -e

    vim -e - $file <<@@@
g/banana/.-2,.d
wq
@@@

L'espressione recita: per ogni riga contenente banana nell'intervallo dalla riga corrente -2 alla riga corrente, eliminare.

La cosa interessante è che l'intervallo può contenere anche ricerche all'indietro e in avanti, ad esempio questo eliminerà tutte le sezioni del file che iniziano con una riga contenente apple e terminano con una riga contenente orange e contenente una riga con banana:

    vim -e - $file <<@@@
g/banana/?apple?,/orange/d
wq
@@@

7

Utilizzando la "finestra scorrevole" in perl:

perl -ne 'push @lines, $_;
          splice @lines, 0, 3 if /banana/;
          print shift @lines if @lines > 2
          }{ print @lines;'

6

Puoi farlo abbastanza semplicemente con sed:

printf %s\\n    1 2 3 4match 5match 6 \
                7match 8 9 10 11match |
sed -e'1N;$!N;/\n.*match/!P;D'

Non so perché qualcuno direbbe diversamente, ma per trovare una linea e stampare le righe precedenti sed incorpora la Pprimitiva \nRint incorporata che scrive solo fino al primo carattere di ewline nello spazio del modello. La Dprimitiva elete complementare rimuove lo stesso segmento di spazio del modello prima di riciclare ricorsivamente la sceneggiatura con ciò che rimane. E per arrotondare, c'è una primitiva per aggiungere la Nlinea di input ext allo spazio modello seguendo un \ncarattere di ewline inserito .

In modo che una riga di seddovrebbe essere tutto ciò che serve. Sostituisci semplicemente matchcon qualunque sia la tua regexp e sei d'oro. Anche questa dovrebbe essere una soluzione molto veloce .

Si noti inoltre che conterà correttamente un matchaltro immediatamente precedente matchcome sia un trigger per silenziare l'output per le due righe precedenti e anche per silenziare la sua stampa:


1
7match
8
11match

Affinché funzioni per un numero arbitrario di linee, tutto ciò che devi fare è ottenere un vantaggio.

Così:

    printf %s\\n     1 2 3 4 5 6 7match     \
                     8match 9match 10match  \
                     11match 12 13 14 15 16 \
                     17 18 19 20match       |
    sed -e:b -e'$!{N;2,5bb' -e\} -e'/\n.*match/!P;D'

1
11match
12
13
14
20match

... cancella le 5 righe che precedono qualsiasi corrispondenza.


1

Utilizzando man 1 ed:

str='
1
2
3
banana
4
5
6
banana
8
9
10
'

# using Bash
cat <<-'EOF' | ed -s <(echo "$str")  | sed -e '1{/^$/d;}' -e '2{/^$/d;}'
H
0i


.
,g/banana/km\
'm-2,'md
,p
q
EOF
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.