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


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 contra un archivo de texto para extraer un valor específico. No quiero buscar y reemplazar. Esto está siendo llamado desde Bash. Usemos un ejemplo:

Ejemplo de expresión regular:

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

Ejemplo de archivo de entrada:

a
b
c
abc12345xyz
a
b
c

Tan simple como suena esto, no puedo averiguar cómo llamar sed / awk / gawk correctamente. Lo que esperaba hacer, es desde dentro de mi guión bash tienen:

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

Las cosas que he intentado 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
Author: Stéphane, 2009-11-14

10 answers

Mi sed (Mac OS X) no funcionaba con +. Probé * en su lugar y agregué p etiqueta 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
 39
Author: mouviciel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-14 08:50:20

Puedes usar sed para hacer esto

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

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

rip 'abc(\d+)xyz' '$1'
 28
Author: Ilia Choly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-03 19:44:36

Uso perl para hacer esto más fácil para mí. por ejemplo,

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

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

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

También puede hacer esto con varios nombres de archivo al final. por ejemplo,

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

 15
Author: PP.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-28 13:51:47

Si su versión de grep lo soporta, podría usar la opción -o para imprimir solo la porción de cualquier línea que coincida con su expresión regular.

Si no, entonces aquí está lo mejor sed que podría llegar a:

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

... que elimina / salta sin dígitos y, para las líneas restantes, elimina todos los caracteres iniciales y finales que no son dígitos. (Solo estoy adivinando 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 sed solo admite la coincidencia "codiciosa"... así que la primera .* coincidirá con el resto de la línea. A menos que podamos usar una clase de personaje negada para lograr una coincidencia no codiciosa ... o una versión de sed con compatible con Perl u otras extensiones a sus expresiones regulares, no podemos extraer una coincidencia precisa de patrón con el espacio de patrón (una línea).

 5
Author: Jim Dennis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-14 10:56:46

Puede usar awk con 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 slices en el array matches, cuyo primer elemento es el bloque [0-9]+. Desde match() devuelve la posición del carácter, o índice, de donde comienza esa subcadena (1, si comienza al principio de la cadena) , desencadena la acción print.


Con grep puede usar una mirada atrás y mirar 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 ocurre dentro de abc y xyz y solo imprime los dígitos.

 3
Author: fedorqui,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-22 09:01:11

Perl es la sintaxis más limpia, pero si no tiene perl (no siempre está ahí, entiendo), entonces la única manera de usar gawk y componentes de una expresión regular es usar la característica 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 el //), por lo que debe poner el .* antes y después del ([0-9]+) para deshacerse del texto antes y después del número en la sustitución.

 2
Author: Mark Lakata,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-22 22:19:55

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 con egrep y luego usa sed para 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 la situación real es más compleja, el REs tendrá que mí modificado. Por ejemplo, si siempre tenía un solo número enterrado dentro de cero o más no numéricos al principio y fin:

egrep '[^0-9]*[0-9]+[^0-9]*$' inputFile | sed -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'
 1
Author: paxdiablo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-14 08:59:30

Puedes hacerlo con el shell

while read -r line
do
    case "$line" in
        *abc*[0-9]*xyz* ) 
            t="${line##abc}"
            echo "num is ${t%%xyz}";;
    esac
done <"file"
 -1
Author: ghostdog74,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-28 01:58:22

Para awk. Usaría el siguiente script:

/.*abc([0-9]+)xyz.*/ {
            print $0;
            next;
            }
            {
            /* default, do nothing */
            }
 -3
Author: Pierre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-14 08:54:58
gawk '/.*abc([0-9]+)xyz.*/' file
 -3
Author: ghostdog74,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-14 09:18:02