regex - pattern - ¿Cómo dar salida solo a los grupos capturados con sed?
sed replace string (8)
¿Hay alguna manera de decirle a sed
que muestre solo los grupos capturados? Por ejemplo, dada la entrada:
This is a sample 123 text and some 987 numbers
y patrón:
/([/d]+)/
¿Puedo obtener solo 123 y 987 salidas en la forma formateada por referencias anteriores?
ejecución (es) de dígitos
Esta respuesta funciona con cualquier cuenta 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 manera de decirle a sed que muestre solo los grupos capturados?
Sí. Reemplace 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 backquotes 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) imprimir 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 cuenta de carreras 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
Que 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
Acerca de / d
y patrón:
/([/d]+)/
Sed no reconoce la sintaxis ''/ d'' (acceso directo). El equivalente de ascii usado arriba [0-9]
no es exactamente equivalente. La única solución alternativa es usar una clase de caracteres: ''[[: digit:]] `.
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 series de dígitos.
Por supuesto, como la respuesta se está ejecutando dentro del shell, podemos definir un par de variables para acortar la 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 ha explicado, es mejor usar el comando s/…/…/gp
:
$ 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 la repetición de dígitos como la escritura de un comando corto (er).
Creo que el patrón dado en la pregunta fue solo a modo de ejemplo, y el objetivo era igualar 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 están con tcsh (sí, sé que es la cáscara incorrecta) con CYGWIN. (Editar: para bash, eliminar set, y los espacios alrededor de =.)
La clave para que esto funcione es decirle a sed
que excluya lo que no desea que se genere, así como que especifique lo que quiere.
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 predeterminado para 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
- imprimir la sustitución (
p
)
En general, en sed
se capturan grupos utilizando paréntesis y se obtiene lo que se captura utilizando una referencia posterior:
echo "foobarbaz" | sed ''s/^foo/(.*/)baz$//1/''
Saldrá "bar". Si usa -r
( -E
para 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 anteriores. Las referencias anteriores están numeradas en el orden en que aparecen los grupos, pero se pueden usar en cualquier orden y se pueden repetir:
echo "foobarbaz" | sed -r ''s/^foo(.*)b(.)z$//2 /1 /2/''
da salida a "una barra a".
Si tiene GNU grep
(también puede funcionar en BSD, incluido OS X):
echo "$string" | grep -Po ''/d+''
o variaciones tales como:
echo "$string" | grep -Po ''(?<=/D )(/d+)''
La opción -P
habilita expresiones regulares compatibles con Perl. Ver man 3 pcrepattern
o man 3 pcresyntax
.
No es lo que pidió 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
Sed tiene hasta nueve patrones recordados, pero necesitas usar paréntesis escapados para recordar partes de la expresión regular.
Vea here para ejemplos y más detalles
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
$
puedes usar grep
grep -Eow "[0-9]+" file
Renunciar y usar Perl
Ya que sed
no lo corta, solo tiremos la toalla y usemos Perl, al menos es LSB mientras que las extensiones grep
GNU no son :-)
Imprima toda la parte coincidente, no se necesitan grupos que coincidan o busque detrás de:
cat <<EOS | perl -lane ''print m//d+/g'' a1 b2 a34 b56 EOS
Salida:
12 3456
Solo coincidencia 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 lookbehind:
cat <<EOS | perl -lane ''print m/(?<=a)(/d+)/'' a1 b2 a34 b56 EOS
Campos múltiples:
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 lookbehind:
cat EOS<< | perl -lane ''print m/(?<=a)(/d+)/g'' a1 b2 a34 b56 a78 b90 EOS
Salida:
1 3478