¿Cómo usar sed, awk o gawk para imprimir solo lo que coincide?

100

Veo muchos ejemplos y páginas de manual sobre cómo hacer cosas como buscar y reemplazar usando sed, awk o gawk.

Pero en mi caso, tengo una expresión regular que quiero ejecutar en un archivo de texto para extraer un valor específico. No quiero buscar y reemplazar. Esto se llama desde bash. Usemos un ejemplo:

Ejemplo de expresión regular:

.*abc([0-9]+)xyz.*

Archivo de entrada de ejemplo:

a
b
c
abc12345xyz
a
b
c

Tan simple como suena, no puedo entender cómo llamar a sed / awk / gawk correctamente. Lo que esperaba hacer es que mi script bash tenga:

myvalue=$( sed <...something...> input.txt )

Las cosas que he probado incluyen:

sed -e 's/.*([0-9]).*/\\1/g' example.txt # extracts the entire input file
sed -n 's/.*([0-9]).*/\\1/g' example.txt # extracts nothing
Stéphane
fuente
10
Wow ... la gente votó en contra de esta pregunta -1? ¿Es realmente tan inapropiada una pregunta?
Stéphane
Parece perfectamente apropiado, usar Regex y poderosas utilidades de línea de comando como sed / awk o cualquier editor como vi, emacs o teco puede ser más como una programación que simplemente usar alguna aplicación antigua. En mi opinión, esto pertenece a SO más que a SU.
Publicado el
Quizás fue rechazado porque en su forma inicial no definía claramente algunos de sus requisitos. Todavía no lo hace, a menos que lea los comentarios del OP a las respuestas (incluido el que eliminé cuando las cosas se pusieron en forma de pera).
Pabellón

Respuestas:

42

Mi sed(Mac OS X) no funcionó con +. Intenté en su *lugar y agregué una petiqueta para imprimir coincidencia:

sed -n 's/^.*abc\([0-9]*\)xyz.*$/\1/p' example.txt

Para hacer coincidir al menos un carácter numérico sin +, usaría:

sed -n 's/^.*abc\([0-9][0-9]*\)xyz.*$/\1/p' example.txt
Mouviciel
fuente
Gracias, esto también funcionó para mí una vez que usé * en lugar de +.
Stéphane
2
... y la opción "p" para imprimir la coincidencia, que yo tampoco conocía. Gracias de nuevo.
Stéphane
2
Tuve que escapar del +y luego funcionó para mí:sed -n 's/^.*abc\([0-9]\+\)xyz.*$/\1/p'
Pausado hasta nuevo aviso.
3
Eso es porque no está utilizando el formato RE moderno, por lo tanto, + es un carácter estándar y se supone que debe expresarlo con la sintaxis {,}. Puede agregar la opción use -E sed para activar el formato RE moderno. Verifique re_format (7), específicamente el último párrafo de DESCRIPCIÓN developer.apple.com/library/mac/#documentation/Darwin/Reference/…
anddam
33

Puedes usar sed para hacer esto

 sed -rn 's/.*abc([0-9]+)xyz.*/\1/gp'
  • -n no imprima la línea resultante
  • -resto hace que no tenga el escape del grupo de captura parens ().
  • \1 el partido del grupo de captura
  • /g partido global
  • /p imprimir el resultado

Escribí una herramienta para mí que lo hace más fácil.

rip 'abc(\d+)xyz' '$1'
Ilia Choly
fuente
3
¡Esta es, con mucho, la mejor respuesta y la mejor explicada hasta ahora!
Nik Reiman
Con alguna explicación, es mucho mejor comprender qué está mal con nuestro problema. Gracias !
r4phG
17

Utilizo perlpara hacer esto más fácil para mí. p.ej

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/'

Esto ejecuta Perl, la -nopción le indica a Perl que lea en una línea a la vez desde STDIN y ejecute el código. La -eopción especifica la instrucción a ejecutar.

La instrucción ejecuta una expresión regular en la línea leída y, si coincide, imprime el contenido del primer conjunto de corchetes ( $1).

También puede hacer esto con varios nombres de archivo al final. p.ej

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/' example1.txt example2.txt

PÁGINAS.
fuente
Gracias, pero no tenemos acceso a perl, por lo que estaba preguntando sobre sed / awk / gawk.
Stéphane
5

Si su versión de lo grepadmite, puede usar la -oopción para imprimir solo la parte de cualquier línea que coincida con su expresión regular.

Si no es así, aquí está lo mejor sedque se me ocurrió:

sed -e '/[0-9]/!d' -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'

... que elimina / omite sin dígitos y, para las líneas restantes, elimina todos los caracteres iniciales y finales que no son dígitos. (Solo supongo que su intención es extraer el número de cada línea que contiene uno).

El problema con algo como:

sed -e 's/.*\([0-9]*\).*/&/' 

.... o

sed -e 's/.*\([0-9]*\).*/\1/'

... es que sedsolo admite coincidencias "codiciosas" ... por lo que el primero. * coincidirá con el resto de la línea. A menos que podamos usar una clase de carácter negada para lograr una coincidencia no codiciosa ... o una versión sedcompatible con Perl u otras extensiones para sus expresiones regulares, no podemos extraer una coincidencia de patrón precisa con el espacio de patrón (una línea ).

Jim Dennis
fuente
Puede combinar dos de sus sedcomandos de esta manera:sed -n 's/[^0-9]*\([0-9]\+\).*/\1/p'
Detenido hasta nuevo aviso.
Anteriormente no conocía la opción -o en grep. Bueno saber. Pero imprime la coincidencia completa, no el "(...)". Entonces, si está haciendo coincidir "abc ([[: dígito:]] +) xyz", obtendrá el "abc" y "xyz", así como los dígitos.
Stéphane
¡Gracias por recordarme grep -o! Estaba tratando de hacer esto sedy luché con mi necesidad de encontrar múltiples coincidencias en algunas líneas. Mi solución es stackoverflow.com/a/58308239/117471
Bruno Bronosky
3

Puede utilizar awkcon match()para acceder al grupo capturado:

$ awk 'match($0, /abc([0-9]+)xyz/, matches) {print matches[1]}' file
12345

Esto intenta coincidir con el patrón abc[0-9]+xyz. Si lo hace, almacena sus cortes en la matriz matches, cuyo primer elemento es el bloque [0-9]+. Dado que match() devuelve la posición del carácter, o índice, de donde comienza esa subcadena (1, si comienza al principio de la cadena) , activa la printacción.


Con greppuede utilizar una mirada hacia atrás y hacia adelante:

$ grep -oP '(?<=abc)[0-9]+(?=xyz)' file
12345

$ grep -oP 'abc\K[0-9]+(?=xyz)' file
12345

Esto comprueba el patrón [0-9]+cuando se produce dentro abcy xyzya sólo imprime los dígitos.

fedorqui 'Así que deja de hacer daño'
fuente
2

perl es la sintaxis más limpia, pero si no tiene perl (no siempre está ahí, según tengo entendido), entonces la única forma de usar gawk y los componentes de una expresión regular es usar la función gensub.

gawk '/abc[0-9]+xyz/ { print gensub(/.*([0-9]+).*/,"\\1","g"); }' < file

la salida del archivo de entrada de muestra será

12345

Nota: gensub reemplaza toda la expresión regular (entre //), por lo que debe colocar. * Antes y después de ([0-9] +) para eliminar el texto antes y después del número en la sustitución.

Mark Lakata
fuente
2
Una solución inteligente y viable si necesita (o desea) usar gawk. Notó esto, pero para que quede claro: awk que no es GNU no tiene gensub () y, por lo tanto, no es compatible con esto.
cincodenada
¡Agradable! Sin embargo, puede ser mejor utilizarlo match()para acceder a los grupos capturados. Vea mi respuesta para esto.
fedorqui 'SO deja de dañar'
1

Si desea seleccionar líneas, elimine los bits que no desea:

egrep 'abc[0-9]+xyz' inputFile | sed -e 's/^.*abc//' -e 's/xyz.*$//'

Básicamente, selecciona las líneas que desea egrepy luego las usa sedpara quitar los bits antes y después del número.

Puedes ver esto en acción aquí:

pax> echo 'a
b
c
abc12345xyz
a
b
c' | egrep 'abc[0-9]+xyz' | sed -e 's/^.*abc//' -e 's/xyz.*$//'
12345
pax> 

Actualización: obviamente, si su situación real es más compleja, los RE necesitarán modificarlos. Por ejemplo, si siempre tuvo un solo número enterrado dentro de cero o más números no numéricos al principio y al final:

egrep '[^0-9]*[0-9]+[^0-9]*$' inputFile | sed -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'
paxdiablo
fuente
Interesante ... Entonces, ¿no hay una manera simple de aplicar una expresión regular compleja y recuperar lo que está en la sección (...)? Porque mientras veo lo que hiciste aquí primero con grep y luego con sed, nuestra situación real es mucho más compleja que eliminar "abc" y "xyz". Se usa la expresión regular porque pueden aparecer muchos textos diferentes a ambos lados del texto que me gustaría extraer.
Stéphane
Estoy seguro de que es una mejor manera si las ER son realmente compleja. Quizás si proporciona algunos ejemplos más o una descripción más detallada, podríamos ajustar nuestras respuestas para que se adapten.
paxdiablo
0

El caso del OP no especifica que puede haber múltiples coincidencias en una sola línea, pero para el tráfico de Google, también agregaré un ejemplo para eso.

Dado que la necesidad del OP es extraer un grupo de un patrón, el uso grep -orequerirá 2 pasadas. Pero, todavía encuentro que esta es la forma más intuitiva de hacer el trabajo.

$ cat > example.txt <<TXT
a
b
c
abc12345xyz
a
abc23451xyz asdf abc34512xyz
c
TXT

$ cat example.txt | grep -oE 'abc([0-9]+)xyz'
abc12345xyz
abc23451xyz
abc34512xyz

$ cat example.txt | grep -oE 'abc([0-9]+)xyz' | grep -oE '[0-9]+'
12345
23451
34512

Dado que el tiempo del procesador es básicamente gratuito, pero la legibilidad humana no tiene precio, tiendo a refactorizar mi código en función de la pregunta, "dentro de un año, ¿qué voy a pensar que hace esto?" De hecho, para el código que tengo la intención de compartir públicamente o con mi equipo, incluso abriré man greppara averiguar cuáles son las opciones largas y sustituirlas. Al igual que:grep --only-matching --extended-regexp

Bruno Bronosky
fuente
-1

puedes hacerlo con el caparazón

while read -r line
do
    case "$line" in
        *abc*[0-9]*xyz* ) 
            t="${line##abc}"
            echo "num is ${t%%xyz}";;
    esac
done <"file"
ghostdog74
fuente
-3

Por awk. Usaría el siguiente script:

/.*abc([0-9]+)xyz.*/ {
            print $0;
            next;
            }
            {
            /* default, do nothing */
            }
Pierre
fuente
Esto no ([0-9+])genera el valor numérico , esto genera la línea completa.
Mark Lakata
-3
gawk '/.*abc([0-9]+)xyz.*/' file
ghostdog74
fuente
2
Esto no parece funcionar. Imprime la línea completa en lugar de la coincidencia.
Stéphane
en su archivo de entrada de muestra, ese patrón es la línea completa. ¿¿¿Derecha??? si sabe que el patrón va a estar en un campo específico: use $ 1, $ 2, etc., por ejemplo, gawk '$ 1 ~ /.*abc([0-9]+)xyz.*/' file
ghostdog74