bash - usos - Eliminar n1 líneas anteriores y n2 líneas siguientes con respecto a una línea que contiene un patrón
usar grep (5)
La idea es leer 5 líneas sin imprimirlas. Si encuentra el patrón, elimine las líneas no impresas y las 4 líneas abajo. Si no encuentra el patrón, recuerde la línea actual e imprima la primera línea no impresa. Al final, imprima lo que no está impreso.
sed -n -e ''/XXXX/,+4{x;s/.*//;x;d}'' -e ''1,5H'' -e ''6,${H;g;s//n//;P;s/[^/n]*//;h}'' -e ''${g;s//n//;p;d}'' fv.out
Por supuesto, esto solo funciona si tiene una ocurrencia de su patrón en el archivo. Si tiene muchas, necesita leer 5 líneas nuevas después de encontrar su patrón, y se vuelve complicado si vuelve a tener su patrón en esas líneas. En este caso, creo que sed no es la herramienta adecuada.
sed -e ''/XXXX/,+4d'' fv.out
Tengo que encontrar un patrón particular en un archivo y borrar 5 líneas arriba y 4 líneas debajo de él simultáneamente. Descubrí que la línea anterior elimina la línea que contiene el patrón y cuatro líneas debajo de ella.
sed -e ''/XXXX/,~5d'' fv.out
En el manual de sed se dio que ~ representa las líneas seguidas por el patrón. Pero cuando lo intenté, fueron las líneas que seguían el patrón que se borraron.
Entonces, ¿cómo elimino 5 líneas arriba y 4 líneas debajo de una línea que contiene el patrón simultáneamente?
Una solución que usa awk
:
awk ''$0 ~ "XXXX" { lines2del = 5; nlines = 0; }
nlines == 5 { print lines[NR%5]; nlines-- }
lines2del == 0 { lines[NR%5] = $0; nlines++ }
lines2del > 0 { lines2del-- }
END { while (nlines-- > 0) { print lines[(NR - nlines) % 5] } }'' fv.out
Actualizar:
Este es el guión explicado:
- Recuerdo las últimas 5 líneas en las
lines
matriz utilizando índices rotatorios (NR% 5; NR es el número de registro, en este caso, líneas). - Si encuentro el patrón en la línea actual (
$0 ~ "XXXX
;$0
es el registro actual: en este caso una línea, y~
es el operador de coincidencia Expresión regular extendida ), restablezco el número de líneas leídas y las notas que tengo 5 líneas para eliminar (incluida la línea actual). - Si ya leí 5 líneas, imprimo la línea actual.
- Si no tengo líneas para eliminar (lo cual también es cierto si he leído 5 líneas, pongo la línea actual en el búfer e incremento el número de líneas. Observe cómo se disminuye el número de líneas y luego se incrementa si una línea es impreso.
- Si es necesario eliminar líneas, no imprimo nada y disminuyo el número de líneas para eliminar.
- Al final del script, imprimo todas las líneas que están en el conjunto.
Mi versión original del script fue la siguiente, pero terminé optimizándola para la versión anterior:
awk ''$0 ~ "XXXX" { lines2del = 5; nlines = 0; }
lines2del == 0 && nlines == 5 { print lines[NR%5]; lines[NR%5] }
lines2del == 0 && nlines < 5 { lines[NR%5] = $0; nlines++ }
lines2del > 0 { lines2del-- }
END { while (nlines-- > 0) { print lines[(NR - nlines) % 5] } }'' fv.out
awk
es una gran herramienta! Recomiendo encarecidamente que encuentres un tutorial en la red y lo leas. Una cosa importante: awk
funciona con Extended Regular Expressions ( ERE ). Su sintaxis es un poco diferente de Standard Regular Expression ( RE ) que se usa en sed
, pero todo lo que se puede hacer con RE se puede hacer con ERE.
Esto podría funcionar para usted:
sed ''H;$!d;g;s//([^/n]*/n/)/{5/}[^/n]*PATTERN/([^/n]*/n/)/{5/}//g;s/.//'' file
o esto:
awk --posix -vORS='''' -vRS=''([^/n]*/n){5}[^/n]*PATTERN([^/n]*/n){5}'' 1 file
una solución sed más eficiente:
sed '':a;/PATTERN/,+4d;//([^/n]*/n/)/{5/}/{P;D};$q;N;ba'' file
Una forma de usar sed
, suponiendo que los patrones no son lo suficientemente cercanos entre sí:
Contenido de script.sed
:
## If line doesn''t match the pattern...
/pattern/ ! {
## Append line to ''hold space''.
H
## Copy content of ''hold space'' to ''pattern space'' to work with it.
g
## If there are more than 5 lines saved, print and remove the first
## one. It''s like a FIFO.
//(/n[^/n]*/)/{6/}/ {
## Delete the first ''/n'' automatically added by previous ''H'' command.
s/^/n//
## Print until first ''/n''.
P
## Delete data printed just before.
s/[^/n]*//
## Save updated content to ''hold space''.
h
}
### Added to fix an error pointed out by potong in comments.
### =======================================================
## If last line, print lines left in ''hold space''.
$ {
x
s/^/n//
p
}
### =======================================================
## Read next line.
b
}
## If line matches the pattern...
/pattern/ {
## Remove all content of ''hold space''. It has the five previous
## lines, which won''t be printed.
x
s/^.*$//
x
## Read next four lines and append them to ''pattern space''.
N ; N ; N ; N
## Delete all.
s/^.*$//
}
Correr como:
sed -nf script.sed infile
Si está satisfecho con la salida del resultado a un archivo en lugar de stdout, vim
puede hacerlo de manera bastante eficiente:
vim -c ''g/pattern/-5,+4d'' -c ''w! outfile|q!'' infile
o
vim -c ''g/pattern/-5,+4d'' -c ''x'' infile
para editar el archivo en el lugar.