¿Cómo generar solo grupos capturados con sed?

278

¿Hay alguna forma de decirle sedque solo genere grupos capturados? Por ejemplo, dada la entrada:

This is a sample 123 text and some 987 numbers

y patrón:

/([\d]+)/

¿Podría obtener solo 123 y 987 de salida en el formato formateado por referencias anteriores?

Pablo
fuente
Tenga en cuenta que la captura de grupo requiere sedactivar expresiones regulares extendidas con la -Ebandera.
peterh - Reinstala a Monica el

Respuestas:

333

La clave para que esto funcione es indicar sedque excluya lo que no desea que se genere, así como especificar lo que desea.

string='This is a sample 123 text and some 987 numbers'
echo "$string" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

Esto dice:

  • no predetermine imprimir cada línea ( -n)
  • excluir cero o más no dígitos
  • incluir uno o más dígitos
  • excluir uno o más no dígitos
  • incluir uno o más dígitos
  • excluir cero o más no dígitos
  • imprime la sustitución ( p)

En general, en los sedgrupos de captura con paréntesis y salida de lo que captura con una referencia inversa:

echo "foobarbaz" | sed 's/^foo\(.*\)baz$/\1/'

dará salida a "bar". Si usa -r( -Epara OS X) para expresiones regulares extendidas, no necesita escapar de los paréntesis:

echo "foobarbaz" | sed -r 's/^foo(.*)baz$/\1/'

Puede haber hasta 9 grupos de captura y sus referencias posteriores. Las referencias posteriores están numeradas en el orden en que aparecen los grupos, pero pueden usarse en cualquier orden y pueden repetirse:

echo "foobarbaz" | sed -r 's/^foo(.*)b(.)z$/\2 \1 \2/'

salidas "una barra a".

Si tiene GNU grep(también puede funcionar en BSD, incluido OS X):

echo "$string" | grep -Po '\d+'

o variaciones como:

echo "$string" | grep -Po '(?<=\D )(\d+)'

La -Popción habilita las expresiones regulares compatibles con Perl. Ver man 3 pcrepatterno man 3 pcresyntax.

Pausado hasta nuevo aviso.
fuente
24
Como nota, OSX Mountain Lion ya no es compatible con PCRE en grep.
yincrash
1
Como nota al margen, la opción grep -o no es compatible con Solaris 9. Además, Solaris 9 no es compatible con la opción sed -r. :(
Daniel Kats
77
Pídale a su administrador de sistemas que instale gsed. Te sorprendería lo que unas pocas donas te darán ...
avgvstvs
3
Tenga en cuenta que es posible que necesite prefijar el '(' y ')' con '\', no sé por qué.
lumbric
77
@lumbric: si te refieres al sedejemplo, si usas la -ropción (o -Epara OS X, IIRC) no necesitas escapar de los paréntesis. La diferencia es que entre las expresiones regulares básicas y las expresiones regulares extendidas ( -r).
Pausado hasta nuevo aviso.
55

Sed tiene hasta nueve patrones recordados, pero debe usar paréntesis escapados para recordar partes de la expresión regular.

Vea aquí para ejemplos y más detalles

Peter McG
fuente
58
sed -e 's/version=\(.+\)/\1/' input.txtEsto le sigue la salida del conjunto entrada.txt
Pablo
@Pablo, en tu patrón tienes que escribir en \+lugar de +. Y no entiendo por qué la gente usa -esolo un comando sed.
Fredrick Gauss
1
use sed -e -n 's/version=\(.+\)/\1/p' input.txtsee: mikeplate.com/2012/05/09/…
awattar
1
Sugeriría usar sed -Epara usar las llamadas expresiones regulares "modernas" o "extendidas" que se parecen mucho más a Perl / Java / JavaScript / Go / cualquier tipo de sabor. (Compare con grep -Eo egrep.) La sintaxis predeterminada tiene esas extrañas reglas de escape y se considera "obsoleta". Para obtener más información sobre las diferencias entre los dos, ejecute man 7 re_format.
AndrewF
31

puedes usar grep

grep -Eow "[0-9]+" file
ghostdog74
fuente
44
@ ghostdog74: absolutamente de acuerdo con usted. ¿Cómo puedo hacer que greo produzca solo grupos capturados?
Pablo
1
@Michael: por eso la oopción está ahí: unixhelp.ed.ac.uk/CGI/man-cgi?grep : -o, --only-matching Muestra solo la parte de una línea coincidente que coincide con PATTERN
Bert F
14
@Bert F: Entiendo la parte correspondiente, pero no es capturar grupo. Lo que quiero es tener así ([0-9] +). + ([Abc] {2,3}) para que haya 2 grupos de captura. Quiero generar SOLO grupos de captura por referencias o de alguna otra manera.
Pablo
Hola Michael. ¿Conseguiste extraer el enésimo grupo capturado por grep?
doc_id
1
@Pablo: grep solo genera lo que coincide. Para darle múltiples grupos, use múltiples expresiones: grep -Eow -e "[0-9]+" -e "[abc]{2,3}"no sé cómo podría requerir que esas dos expresiones estén en una línea aparte de las tuberías de un grep anterior (que aún no podría funcionar si cualquiera de los patrones coincide más de una vez en una línea )
idbrii
13

corrida (s) de dígitos

Esta respuesta funciona con cualquier recuento de grupos de dígitos. Ejemplo:

$ echo 'Num123that456are7899900contained0018166intext' |
> sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

Respuesta ampliada

¿Hay alguna forma de decirle a sed que solo genere grupos capturados?

Si. reemplazar todo el texto por el grupo de captura:

$ echo 'Number 123 inside text' | sed 's/[^0-9]*\([0-9]\{1,\}\)[^0-9]*/\1/'
123

s/[^0-9]*                           # several non-digits
         \([0-9]\{1,\}\)            # followed by one or more digits
                        [^0-9]*     # and followed by more non-digits.
                               /\1/ # gets replaced only by the digits.

O con sintaxis extendida (menos comillas inversas y permite el uso de +):

$ echo 'Number 123 in text' | sed -E 's/[^0-9]*([0-9]+)[^0-9]*/\1/'
123

Para evitar imprimir el texto original cuando no hay un número, use:

$ echo 'Number xxx in text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1/p'
  • (-n) No imprime la entrada por defecto.
  • (/ p) imprime solo si se realizó un reemplazo.

Y para hacer coincidir varios números (y también imprimirlos):

$ echo 'N 123 in 456 text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1 /gp'
123 456

Eso funciona para cualquier recuento de ejecuciones de dígitos:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

Lo cual es muy similar al comando grep:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | grep -Po '\d+'
123
456
7899900
0018166

Sobre \ d

y patrón: /([\d]+)/

Sed no reconoce la sintaxis '\ d' (acceso directo). El equivalente ascii utilizado anteriormente [0-9]no es exactamente equivalente. La única solución alternativa es usar una clase de caracteres: '[[: dígito:]] `.

La respuesta seleccionada usa tales "clases de caracteres" para construir una solución:

$ str='This is a sample 123 text and some 987 numbers'
$ echo "$str" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

Esa solución solo funciona para (exactamente) dos corridas de dígitos.

Por supuesto, a medida que la respuesta se ejecuta dentro del shell, podemos definir un par de variables para acortar dicha respuesta:

$ str='This is a sample 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D+($d+)$D*/\1 \2/p"

Pero, como ya se explicó, utilizar un s/…/…/gpcomando es mejor:

$ str='This is 75577 a sam33ple 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D*/\1 /gp"
75577 33 123 987

Eso cubrirá tanto las repeticiones de dígitos como la escritura de un comando corto (er).

Isaac
fuente
Sorprendido después de leer la respuesta aceptada y votada, me desplacé hacia abajo para escribir sobre su alcance limitado y para abordar el espíritu de la pregunta. Debería haber adivinado que alguien ya lo habría hecho hace años. Esto está muy bien explicado y es la verdadera respuesta correcta.
Amit Naidu
9

Creo que el patrón dado en la pregunta fue solo a modo de ejemplo, y el objetivo era hacer coincidir cualquier patrón.

Si tiene un sed con la extensión GNU que permite la inserción de una nueva línea en el espacio del patrón, una sugerencia es:

> set string = "This is a sample 123 text and some 987 numbers"
>
> set pattern = "[0-9][0-9]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
123
987
> set pattern = "[a-z][a-z]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
his
is
a
sample
text
and
some
numbers

Estos ejemplos son con tcsh (sí, sé que es el shell incorrecto) con CYGWIN. (Editar: Para bash, elimine el conjunto y los espacios alrededor =.)

Joseph Quinsey
fuente
@Joseph: gracias, sin embargo, según mi tarea, siento que grep es más natural, como sugirió ghostdog74. Solo necesito descubrir cómo hacer que grep genere solo los grupos de captura, no toda la coincidencia.
Pablo
2
Solo una nota, pero el signo más '+' significa 'uno o más', lo que eliminaría la necesidad de repetirse en los patrones. Entonces, "[0-9] [0-9] *" se convertiría en "[0-9] +"
RandomInsano
44
@RandomInsano: para usar el +, necesitarías escapar de él o usar la -ropción ( -Epara OS X). También puede usar \{1,\}( -ro -Esin el escape).
Pausado hasta nuevo aviso.
9

Renunciar y usar Perl

Como sedno lo corta, simplemente tiremos la toalla y usemos Perl, al menos es LSB, mientras que las grepextensiones GNU no son :-)

  • Imprima toda la parte coincidente, no se necesitan grupos coincidentes ni mirar hacia atrás:

    cat <<EOS | perl -lane 'print m/\d+/g'
    a1 b2
    a34 b56
    EOS

    Salida:

    12
    3456
  • Coincidencia única por línea, a menudo campos de datos estructurados:

    cat <<EOS | perl -lape 's/.*?a(\d+).*/$1/g'
    a1 b2
    a34 b56
    EOS

    Salida:

    1
    34

    Con mirar atrás:

    cat <<EOS | perl -lane 'print m/(?<=a)(\d+)/'
    a1 b2
    a34 b56
    EOS
  • Múltiples campos:

    cat <<EOS | perl -lape 's/.*?a(\d+).*?b(\d+).*/$1 $2/g'
    a1 c0 b2 c0
    a34 c0 b56 c0
    EOS

    Salida:

    1 2
    34 56
  • Múltiples coincidencias por línea, a menudo datos no estructurados:

    cat <<EOS | perl -lape 's/.*?a(\d+)|.*/$1 /g'
    a1 b2
    a34 b56 a78 b90
    EOS

    Salida:

    1 
    34 78

    Con mirar atrás:

    cat EOS<< | perl -lane 'print m/(?<=a)(\d+)/g'
    a1 b2
    a34 b56 a78 b90
    EOS

    Salida:

    1
    3478
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
1
¿Qué no obtuvo con el final de la pregunta: "con sed"?
Moonchild
A @Moonchild Googlers no les importa.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Encontré esto útil. No todos los problemas de expresiones regulares de la línea de comandos deben resolverse con sed.
PPPaul
5

Tratar

sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

Tengo esto bajo Cygwin:

$ (echo "asdf"; \
   echo "1234"; \
   echo "asdf1234adsf1234asdf"; \
   echo "1m2m3m4m5m6m7m8m9m0m1m2m3m4m5m6m7m8m9") | \
  sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

1234
1234 1234
1 2 3 4 5 6 7 8 9
$
Bert F
fuente
2

No es lo que solicitó el OP (capturar grupos) pero puede extraer los números usando:

S='This is a sample 123 text and some 987 numbers'
echo "$S" | sed 's/ /\n/g' | sed -r '/([0-9]+)/ !d'

Da lo siguiente:

123
987
Thomas Bratt
fuente