Cómo seleccionar líneas entre dos patrones de marcadores que pueden ocurrir varias veces con awk / sed


119

¿Usando awko sedcómo puedo seleccionar líneas que ocurren entre dos patrones de marcadores diferentes? Puede haber varias secciones marcadas con estos patrones.

Por ejemplo: suponga que el archivo contiene:

abc
def1
ghi1
jkl1
mno
abc
def2
ghi2
jkl2
mno
pqr
stu

Y el patrón inicial es abcy el patrón final es mno Entonces, necesito la salida como:

def1
ghi1
jkl1
def2
ghi2
jkl2

Estoy usando sed para que coincida con el patrón una vez:

sed -e '1,/abc/d' -e '/mno/,$d' <FILE>

¿Hay alguna manera en sedo awk hacerlo varias veces hasta el final del archivo?

Respuestas:


188

Úselo awkcon una bandera para activar la impresión cuando sea necesario:

$ awk '/abc/{flag=1;next}/mno/{flag=0}flag' file
def1
ghi1
jkl1
def2
ghi2
jkl2

¿Como funciona esto?

  • /abc/ coincide con las líneas que tienen este texto, así como /mno/ hace.
  • /abc/{flag=1;next}establece el flagcuando el textoabc se encuentra . Luego, se salta la línea.
  • /mno/{flag=0}desarma el flagcuando el textomno se encuentra .
  • El final flages un patrón con la acción predeterminada, que es print $0: si flages igual a 1, se imprime la línea.

Para obtener una descripción más detallada y ejemplos, junto con los casos en los que los patrones se muestran o no, consulte ¿Cómo seleccionar líneas entre dos patrones? .


30
Si desea imprimir todo entre el patrón e incluido , puede usar awk '/abc/{a=1}/mno/{print;a=0}a' file.
Scai

6
¡Sí, @scai! o incluso awk '/abc/{a=1} a; /mno/{a=0}' file- con esto, poniendo acondición antes de la /mno/hacemos evaluar la línea como verdadera (e imprimirla) antes de configurar a=0. De esta forma podemos evitar escribir print.
fedorqui 'SO deja de hacer daño'

12
@scai @fedorqui Para incluir la salida del patrón, puede hacerloawk '/abc/,/mno/' file
Jotne

1
@hkasera awk '/abc/{flag=1}/mno/{flag=0}flag' filedebería hacer.
fedorqui 'SO deja de dañar'

2
@EirNym ese es un escenario extraño que se puede manejar de formas muy diferentes: ¿qué líneas le gustaría imprimir? Probablemente awk 'flag; /PAT1/{flag=1; next} /PAT1/{flag=0}' filelo haría.
fedorqui 'SO deja de dañar'

45

Usando sed:

sed -n -e '/^abc$/,/^mno$/{ /^abc$/d; /^mno$/d; p; }'

La -nopción significa no imprimir de forma predeterminada.

El patrón busca líneas que contengan solo abcto just mnoy luego ejecuta las acciones en el { ... }. La primera acción borra la abclínea; el segundo la mnolínea; e pimprime las líneas restantes. Puede relajar las expresiones regulares según sea necesario. Cualquier línea fuera del rango de abc.. mnosimplemente no se imprime.


¡Gracias por la respuesta y la explicación! :)
dvai

@JonathanLeffler ¿puedo saber cuál es el propósito de usar?-e
Kasun Siyambalapitiya

1
@KasunSiyambalapitiya: Principalmente significa que me gusta usarlo. Formalmente, especifica que el siguiente argumento es (parte de) el script que seddebe ejecutarse. Si desea o necesita usar varios argumentos para incluir el script completo, debe usarlos -eantes de cada uno de esos argumentos; de lo contrario, es opcional (pero explícito).
Jonathan Leffler

@JonathanLeffler Gracias
Kasun Siyambalapitiya

¡Agradable! (Prefiero sed sobre awk.) Cuando se usan expresiones regulares complejas, sería bueno no tener que repetirlas. ¿No es posible eliminar la primera / última línea del rango "seleccionado"? ¿O aplicar primero da todas las líneas hasta la primera coincidencia, y luego otra da todas las líneas que comienzan con la segunda coincidencia?
hans_meine

18

Esto podría funcionar para usted (GNU sed):

sed '/^abc$/,/^mno$/{//!b};d' file

Elimine todas las líneas excepto aquellas entre las líneas que comienzan abcymno



Esto es asombroso. El {//!b}evita que abcy mnose incluyan en la salida, pero no puedo entender cómo. ¿Podrías explicar?
Brendan

1
@Brendan la instrucción //!blee si la línea actual no es ninguna de las líneas que coinciden con el rango, se rompe y, por lo tanto, imprime esas líneas; de lo contrario, todas las demás líneas se eliminan.
potong

13
sed '/^abc$/,/^mno$/!d;//d' file

golf dos personajes mejor que ppotong {//!b};d

Las barras diagonales vacías //significan: "reutilizar la última expresión regular utilizada". y el comando hace lo mismo que el más comprensible:

sed '/^abc$/,/^mno$/!d;/^abc$/d;/^mno$/d' file

Esto parece ser POSIX :

Si un RE está vacío (es decir, no se especifica ningún patrón) sed se comportará como si se hubiera especificado el último RE usado en el último comando aplicado (ya sea como una dirección o como parte de un comando sustituto).


1
Creo que la segunda solución terminará sin nada, ya que el segundo comando también es un rango. Sin embargo, felicitaciones por el primero.
potong

@potong cierto! Tengo que estudiar más por qué funciona el primero. ¡Gracias!
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

7

De los enlaces de la respuesta anterior, el que lo hizo por mí, que se ejecuta kshen Solaris, fue este:

sed '1,/firstmatch/d;/secondmatch/,$d'
  • 1,/firstmatch/d: desde la línea 1 hasta la primera vez que encuentre firstmatch , elimine.
  • /secondmatch/,$d: desde la primera aparición de secondmatch hasta el final del archivo, eliminar.
  • El punto y coma separa los dos comandos, que se ejecutan en secuencia.

Solo por curiosidad, ¿por qué el limitador de rango ( 1,) viene antes /firstmatch/? Supongo que esto también podría expresarse '/firstmatch/1,d;/secondmatch,$d'.
Luke Davis

2
Con "1, / firstmatch / d" estás diciendo "desde la línea 1 hasta la primera vez que encuentres 'firstmatch', eliminar". Mientras que, con "/ secondmatch /, $ d" se dice "desde la primera aparición de 'secondmatch' hasta el final del archivo, eliminar". el punto y coma separa los dos comandos, que se ejecutan en secuencia.
FanDeLaU

2
perl -lne 'print if((/abc/../mno/) && !(/abc/||/mno/))' your_file

Es bueno saber el equivalente de perl, ya que es una muy buena alternativa tanto a awk como a sed.
Akhan

2

algo como esto funciona para mí:

file.awk:

BEGIN {
    record=0
}

/^abc$/ {
    record=1
}

/^mno$/ {
    record=0;
    print "s="s;
    s=""
}

!/^abc|mno$/ {
    if (record==1) {
        s = s"\n"$0
    }   
}

utilizando: awk -f file.awk data...

editar: La solución O_o fedorqui es mucho mejor / más bonita que la mía.


3
En GNU awk if (record=1)debería ser if (record==1), es decir, doble = - ver operadores de comparación de gawk
George Hawkins

2

La respuesta de Don_crissti de ¿ Mostrar solo texto entre 2 patrones coincidentes ?

firstmatch="abc"
secondmatch="cdf"
sed "/$firstmatch/,/$secondmatch/!d;//d" infile

que es mucho más eficiente que la aplicación de AWK, consulte aquí .


No creo que aquí tenga mucho sentido vincular las comparaciones de tiempo, ya que los requisitos de las preguntas son bastante diferentes, de ahí las soluciones.
fedorqui 'SO deja de dañar'

2
No estoy de acuerdo porque deberíamos tener algunos criterios para comparar respuestas. Solo unos pocos tienen aplicaciones SED.
Léo Léopold Hertz 준영

0

Intenté usar awkpara imprimir líneas entre dos patrones mientras que el patrón2 también coincide con el patrón1 . Y la línea pattern1 también debe imprimirse.

por ejemplo, fuente

package AAA
aaa
bbb
ccc
package BBB
ddd
eee
package CCC
fff
ggg
hhh
iii
package DDD
jjj

debería tener una salida de

package BBB
ddd
eee

Donde patrón1 es package BBB, patrón2 es package \w*. Tenga en cuenta que CCCno es un valor conocido, por lo que no se puede comparar literalmente.

En este caso, ni @scai's awk '/abc/{a=1}/mno/{print;a=0}a' fileni @fedorqui's awk '/abc/{a=1} a; /mno/{a=0}' filefuncionan para mí.

Finalmente logré solucionarlo awk '/package BBB/{flag=1;print;next}/package \w*/{flag=0}flag' filejaja

Un poco más de esfuerzo resulta en awk '/package BBB/{flag=1;print;next}flag;/package \w*/{flag=0}' fileimprimir también la línea patrón2, es decir,

package BBB
ddd
eee
package CCC
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.