No codicioso (reacio) coincidencia de expresiones regulares en sed


407

Estoy tratando de usar sed para limpiar líneas de URL para extraer solo el dominio.

Entonces de:

http://www.suepearson.co.uk/product/174/71/3816/

Quiero:

http://www.suepearson.co.uk/

(ya sea con o sin la barra inclinada, no importa)

Yo he tratado:

 sed 's|\(http:\/\/.*?\/\).*|\1|'

y (escapando del cuantificador no codicioso)

sed 's|\(http:\/\/.*\?\/\).*|\1|'

pero parece que no puedo hacer que el cuantificador no codicioso ( ?) funcione, por lo que siempre termina haciendo coincidir toda la cadena.


54
Una nota al margen: si delimita sus expresiones regulares con "|", no necesita escapar de "/" s. De hecho, la mayoría de las personas delimitan con "|" en lugar de "/" s para evitar las "cercas".
AttishOculus

12
@AttishOculus El primer carácter después de la 's' en una expresión sustituta en sed es el delimitador. De ahí 's ^ foo ^ bar ^' o 's! Foo! Bar!' también funciona
Squidly

1
Para expresiones regulares extendidas, use sed -E 's.... Aún así, no hay un operador reacio.
Ondra Žižka

No responde al título de la pregunta, pero en este caso específico cut -d'/' -f1-3funciona de manera simple .
Petr Javorik

Respuestas:


422

Ni la expresión regular Posix / GNU básica ni extendida reconoce el cuantificador no codicioso; Necesitas una expresión regular posterior. Afortunadamente, la expresión regular de Perl para este contexto es bastante fácil de obtener:

perl -pe 's|(http://.*?/).*|\1|'

13
Para hacerlo en el lugar, use las opciones -pi -e.
realmente agradable

12
No puedo creer que funcionó :-) Lo único que apesta es que ahora mi script tiene una dependencia de Perl :-( En el lado positivo, prácticamente todas las distribuciones de Linux ya tienen Perl, así que probablemente no sea un problema :-)
Freedom_Ben

77
@Freedom_Ben: IIRC perlse requiere por POSIX
MestreLion

44
@ dolphus333: "Ni la expresión regular básica ni extendida de Posix / GNU reconoce el cuantificador no codicioso" significa "no puede usar el cuantificador no codicioso en sed".
caos

3
@ Sérgio, así es como se hace lo solicitado, lo cual es imposible sed, usando una sintaxis básicamente idéntica a la delsed
caos

251

En este caso específico, puede hacer el trabajo sin usar una expresión regular no codiciosa.

Pruebe esta expresión regular no codiciosa en [^/]*lugar de .*?:

sed 's|\(http://[^/]*/\).*|\1|g'

3
¿Cómo hacer que sed coincida con una frase no codiciosa usando esta técnica?
user3694243

66
Lamentablemente no puedes; Ver la respuesta del caos .
Daniel H

Muchas gracias ... ¡ya que perl ya no está en la base de instalación predeterminada en muchas distribuciones de Linux!
st0ne


@DanielH De hecho, es posible unir frases de forma no codiciosa utilizando esta técnica según lo solicitado. Puede tomar algo de dolor escribir cualquiera de los patrones con suficiente precisión. Por ejemplo, al analizar una asignación de valor-clave en la consulta de una URL, puede ser necesario buscar la asignación usando ([^&=#]+)=([^&#]*). Hay casos que no funcionan de esta manera con seguridad, por ejemplo, cuando se analiza el URL para su parte de host y el nombre de ruta con la barra oblicua final, se supone opcional que se excluya de la captura:^(http:\/\/.+?)/?$
Thomas Urban

121

Con sed, generalmente implemento la búsqueda no codiciosa buscando cualquier cosa excepto el separador hasta el separador:

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*\)/.*;\1;p'

Salida:

http://www.suon.co.uk

esto es:

  • no salga -n
  • buscar, unir patrones, reemplazar e imprimir s/<pattern>/<replace>/p
  • use el ;separador de comando de búsqueda en lugar de /hacerlo más fácil para escribirs;<pattern>;<replace>;p
  • recuerda coincidencia entre paréntesis \(... \), luego accesible con \1, \2...
  • partido http://
  • seguido de cualquier cosa entre paréntesis [], [ab/]significaría ao bo/
  • primero ^en los []medios not, seguido por cualquier cosa menos la cosa en el[]
  • por lo que [^/]significa otra cosa que /el carácter
  • *es repetir el grupo anterior, entonces [^/]*significa caracteres excepto /.
  • hasta ahora sed -n 's;\(http://[^/]*\)significa buscar y recordar http://seguido de cualquier carácter excepto /y recordar lo que has encontrado
  • queremos buscar hasta el final del dominio, así que deténgase en el siguiente, /agregue otro /al final: sed -n 's;\(http://[^/]*\)/'pero queremos hacer coincidir el resto de la línea después del dominio, así que agregue.*
  • ahora la coincidencia recordada en el grupo 1 ( \1) es el dominio, así que reemplace la línea coincidente con cosas guardadas en el grupo \1e imprima:sed -n 's;\(http://[^/]*\)/.*;\1;p'

Si también desea incluir una barra diagonal inversa después del dominio, agregue una barra diagonal inversa más en el grupo para recordar:

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*/\).*;\1;p'

salida:

http://www.suon.co.uk/

8
Con respecto a las ediciones recientes: los paréntesis son un tipo de carácter entre corchetes, por lo que no es incorrecto llamarlos entre paréntesis, especialmente si sigue la palabra con los caracteres reales, como lo hizo el autor. Además, es el uso preferido en algunas culturas, por lo que reemplazarlo con el uso preferido en su propia cultura parece un poco grosero, aunque estoy seguro de que no es lo que pretendía el editor. Personalmente, creo que es mejor usar nombres puramente descriptivos como corchetes , corchetes y angulares .
Alan Moore

2
¿Es posible reemplazar el separador con una cadena?
Calculemus

37

sed no es compatible con el operador "no codicioso".

Debe utilizar el operador "[]" para excluir "/" de la coincidencia.

sed 's,\(http://[^/]*\)/.*,\1,'

PD: no hay necesidad de barra diagonal inversa "/".


realmente no. Si el delimitador pudiera ser uno de los muchos caracteres posibles (solo una cadena de números), su coincidencia de negación podría volverse cada vez más compleja. eso está bien, pero sin duda sería bueno tener una opción para hacer. * no codicioso
gesell

1
La pregunta fue más general. Estas soluciones funcionan para las URL, pero no (por ejemplo) para mi caso de uso de eliminar los ceros finales. s/([[:digit:]]\.[[1-9]]*)0*/\1/obviamente no funcionaría bien para 1.20300. Sin embargo, dado que la pregunta original era sobre las URL, deberían mencionarse en la respuesta aceptada.
Daniel H

33

Simulación de cuantificador perezoso (no codicioso) en sed

¡Y todos los demás sabores regex!

  1. Encontrar la primera aparición de una expresión:

    • POSIX ERE (usando la -ropción)

      Regex:

      (EXPRESSION).*|.

      Sed:

      sed -r 's/(EXPRESSION).*|./\1/g' # Global `g` modifier should be on

      Ejemplo (encontrar la primera secuencia de dígitos) Demostración en vivo :

      $ sed -r 's/([0-9]+).*|./\1/g' <<< 'foo 12 bar 34'
      12

      ¿Cómo funciona ?

      Esta expresión regular se beneficia de una alternancia |. En cada posición, el motor intenta elegir la coincidencia más larga (este es un estándar POSIX que es seguido por un par de otros motores también), lo que significa que continúa .hasta que se encuentre una coincidencia ([0-9]+).*. Pero el orden también es importante.

      ingrese la descripción de la imagen aquí

      Como se establece el indicador global, el motor intenta continuar haciendo coincidir carácter por carácter hasta el final de la cadena de entrada o nuestro objetivo. Tan pronto como se empareja el primer y único grupo de captura del lado izquierdo de la alternancia, el (EXPRESSION)resto de la línea también se consume de inmediato .*. Ahora mantenemos nuestro valor en el primer grupo de captura.

    • POSIX BRE

      Regex:

      \(\(\(EXPRESSION\).*\)*.\)*

      Sed:

      sed 's/\(\(\(EXPRESSION\).*\)*.\)*/\3/'

      Ejemplo (encontrar la primera secuencia de dígitos):

      $ sed 's/\(\(\([0-9]\{1,\}\).*\)*.\)*/\3/' <<< 'foo 12 bar 34'
      12

      Esta es como la versión ERE pero sin alternar. Eso es todo. En cada posición, el motor intenta hacer coincidir un dígito.

      ingrese la descripción de la imagen aquí

      Si se encuentra, se consumen y capturan otros dígitos siguientes y el resto de la línea se empareja inmediatamente de lo contrario, ya que *significa más o cero se salta sobre el segundo grupo de captura \(\([0-9]\{1,\}\).*\)*y llega a un punto .para que coincida con un solo carácter y este proceso continúa.

  2. Encontrar la primera aparición de una expresión delimitada :

    Este enfoque coincidirá con la primera aparición de una cadena delimitada. Podemos llamarlo un bloque de cuerda.

    sed 's/\(END-DELIMITER-EXPRESSION\).*/\1/; \
         s/\(\(START-DELIMITER-EXPRESSION.*\)*.\)*/\1/g'

    Cadena de entrada:

    foobar start block #1 end barfoo start block #2 end

    -EDE: end

    -SDE: start

    $ sed 's/\(end\).*/\1/; s/\(\(start.*\)*.\)*/\1/g'

    Salida:

    start block #1 end

    First regex \(end\).*coincide y captura el delimitador de primer extremo endy sustituye todos los caracteres coincidentes con caracteres capturados recientemente, que es el delimitador de final. En esta etapa nuestra salida es: foobar start block #1 end.

    ingrese la descripción de la imagen aquí

    Luego, el resultado se pasa a la segunda expresión regular \(\(start.*\)*.\)*que es la misma que la versión POSIX BRE anterior. Coincide con un solo carácter si el delimitador de inicio startno coincide, de lo contrario, coincide y captura el delimitador de inicio y coincide con el resto de caracteres.

    ingrese la descripción de la imagen aquí


Responde directamente tu pregunta

Usando el enfoque # 2 (expresión delimitada) debe seleccionar dos expresiones apropiadas:

  • EDE: [^:/]\/

  • SDE: http:

Uso:

$ sed 's/\([^:/]\/\).*/\1/g; s/\(\(http:.*\)*.\)*/\1/' <<< 'http://www.suepearson.co.uk/product/174/71/3816/'

Salida:

http://www.suepearson.co.uk/

Nota: esto no funcionará con delimitadores idénticos.


3) al sugerir sitios como regex101 para la demostración, agregue una nota de que no siempre es adecuado para herramientas cli debido a la sintaxis y las diferencias de características
Sundeep

1
@ Sundeep Gracias. Convertí todas esas citas en comillas simples. También consideré la regla de partido más larga más a la izquierda que se menciona. Sin embargo, en sedy en todos los demás motores que siguen el mismo orden estándar , importa la igualdad. Entonces echo 'foo 1' | sed -r 's/.|([0-9]+).*/\1/g'no tiene una coincidencia pero echo 'foo 1' | sed -r 's/([0-9]+).*|./\1/g'sí.
revo

@Sundeep también la solución para las expresiones delimitadas no funcionó para delimitadores de inicio y fin idénticos para los cuales agregué una nota.
revo

gran punto sobre lo que sucede cuando diferentes alternancias comienzan desde la misma ubicación y tienen la misma longitud, supongo que seguirán el orden de izquierda a derecha como otros motores ... necesitan buscar si eso se describe en el manual
Sundeep

Sin embargo, aquí hay un caso extraño: stackoverflow.com/questions/59683820/…
Sundeep

20

Solución no codiciosa para más de un personaje.

Este hilo es muy antiguo pero supongo que la gente todavía lo necesita. Digamos que quieres matar todo hasta la primera aparición de HELLO. No puedes decir[^HELLO] ...

Entonces, una buena solución implica dos pasos, suponiendo que puede ahorrar una palabra única que no espera en la entrada, digamos top_sekrit .

En este caso podemos:

s/HELLO/top_sekrit/     #will only replace the very first occurrence
s/.*top_sekrit//        #kill everything till end of the first HELLO

Por supuesto, con una entrada más simple, puede usar una palabra más pequeña o incluso un solo carácter.

HTH!


44
Para hacerlo aún mejor, útil en una situación en la que no puede esperar un carácter no utilizado: 1. reemplace ese carácter especial con una PALABRA realmente no utilizada, 2. reemplace la secuencia final con el carácter especial, 3. realice la búsqueda que termina con un carácter especial, 4 Reemplace el carácter especial, 5. Reemplace la PALABRA especial. Por ejemplo, desea un operador codicioso entre <hello> y </hello>:
Jakub

3
Aquí ejemplo: echo "Buscar: <hello> fir ~ st <br> yes </hello> <hello> sec ~ ond </hello>" | sed -e "s, ~, VERYSPECIAL, g" -e "s, </hello>, ~, g" -e "s,. * Buscar: <hello> ([^ ~] *). *, \ 1 , "-e" s, \ ~, </hello>, "-e" s, MUY ESPECIAL, ~, "
Jakub

2
Estoy de acuerdo. Buena solución Reformularía el comentario para que diga: si no puede confiar en que ~ no se use, reemplace sus ocurrencias actuales primero usando s / ~ / VERYspeciaL / g, luego haga el truco anterior, luego devuelva el original ~ usando s / VERYspeciaL / ~ / g
ishahak

1
Me gusta usar "variables" más raras para este tipo de cosas, así que en lugar de `usarlas <$$>(ya que se $$expande a su ID de proceso en el shell, aunque tendría que usar comillas dobles en lugar de comillas simples, y eso podría romper otras partes de su expresión regular) o, si está disponible Unicode, algo así <∈∋>.
Adam Katz

En algún momento usted tiene que preguntarse por qué no está simplemente usando perlo pythono algún otro idioma en su lugar. perlhace esto de una manera menos frágil en una sola línea ...
ArtOfWarfare 05 de

18

sed - correspondencia no codiciosa por Christoph Sieghart

El truco para obtener una coincidencia no codiciosa en sed es hacer coincidir todos los caracteres, excepto el que termina la coincidencia. Lo sé, es obvio, pero perdí unos minutos preciosos y los scripts de shell deberían ser, después de todo, rápidos y fáciles. Entonces, en caso de que alguien más lo necesite:

Emparejamiento codicioso

% echo "<b>foo</b>bar" | sed 's/<.*>//g'
bar

Emparejamiento no codicioso

% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar

17

Esto se puede hacer usando cortar:

echo "http://www.suepearson.co.uk/product/174/71/3816/" | cut -d'/' -f1-3

9

Otra forma, no usar expresiones regulares, es usar el método de campos / delimitador, por ejemplo

string="http://www.suepearson.co.uk/product/174/71/3816/"
echo $string | awk -F"/" '{print $1,$2,$3}' OFS="/"

5

sed Ciertamente tiene su lugar, pero este no es uno de ellos.

Como Dee ha señalado: solo úsalo cut. Es mucho más simple y mucho más seguro en este caso. Aquí hay un ejemplo donde extraemos varios componentes de la URL usando la sintaxis Bash:

url="http://www.suepearson.co.uk/product/174/71/3816/"

protocol=$(echo "$url" | cut -d':' -f1)
host=$(echo "$url" | cut -d'/' -f3)
urlhost=$(echo "$url" | cut -d'/' -f1-3)
urlpath=$(echo "$url" | cut -d'/' -f4-)

te dio:

protocol = "http"
host = "www.suepearson.co.uk"
urlhost = "http://www.suepearson.co.uk"
urlpath = "product/174/71/3816/"

Como puede ver, este es un enfoque mucho más flexible.

(todo crédito a Dee)



3

sed -E interpreta las expresiones regulares como expresiones regulares extendidas (modernas)

Actualización: -E en MacOS X, -r en GNU sed.


44
No, no lo hace ... Al menos no GNU sed.
Michel de Ruiter

77
En términos más generales, -Ees exclusivo de BSD sedy, por lo tanto, de OS X. Enlaces a páginas de manual. -rtrae expresiones regulares extendidas a GNUsed como se señala en la corrección de @ stephancheg. Tenga cuidado al usar un comando de variabilidad conocida en las distribuciones 'nix. Aprendí eso por las malas.
fny

1
Esta es la respuesta correcta si desea usar sed, y es la más aplicable a la pregunta inicial.
Will Tice

8
La -ropción de GNU sed solo cambia las reglas de escape, de acuerdo con Appendix A Extended regular expressionsel archivo de información y algunas pruebas rápidas; en realidad no agrega un calificador no codicioso ( GNU sed version 4.2.1al menos a partir de)
Eichin

1
GNU se reconoció -Ecomo una opción no documentada por un tiempo, pero en la versión 4.2.2.177 , la documentación se actualizó para reflejar eso, por lo que ahora -Eestá bien para ambos.
Benjamin W.

3

Todavía hay esperanza de resolver esto usando sed puro (GNU). A pesar de que esta no es una solución genérica, en algunos casos puede usar "bucles" para eliminar todas las partes innecesarias de la cadena como esta:

sed -r -e ":loop" -e 's|(http://.+)/.*|\1|' -e "t loop"
  • -r: Usar expresiones regulares extendidas (para + y paréntesis sin escape)
  • ": loop": define una nueva etiqueta llamada "loop"
  • -e: agrega comandos a sed
  • "t loop": Vuelve a la etiqueta "loop" si hubo una sustitución exitosa

El único problema aquí es que también cortará el último carácter separador ('/'), pero si realmente lo necesita, simplemente puede volver a colocarlo después de que finalice el "ciclo", simplemente agregue este comando adicional al final del anterior línea de comando:

-e "s,$,/,"

2

Debido a que declaró específicamente que está tratando de usar sed (en lugar de perl, cut, etc.), intente agrupar. Esto evita que el identificador no codicioso no sea reconocido. El primer grupo es el protocolo (es decir, 'http: //', 'https: //', 'tcp: //', etc.). El segundo grupo es el dominio:

echo "http://www.suon.co.uk/product/1/7/3/" | sed "s | ^ \ (. * // \) \ ([^ /] * \). * $ | \ 1 \ 2 |"

Si no está familiarizado con la agrupación, comience aquí .


1

Me doy cuenta de que esta es una entrada antigua, pero alguien puede encontrarla útil. Como el nombre de dominio completo no puede exceder una longitud total de 253 caracteres, reemplace. * Con. \ {1, 255 \}


1

Así es como hacer una coincidencia no codiciosa de cadenas de caracteres múltiples usando sed. Digamos que desea cambiar cada foo...bara, <foo...bar>por ejemplo, esta entrada:

$ cat file
ABC foo DEF bar GHI foo KLM bar NOP foo QRS bar TUV

debería convertirse en esta salida:

ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV

Para hacer eso, convierte foo y bar en caracteres individuales y luego usa la negación de esos caracteres entre ellos:

$ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/g; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV

En lo anterior:

  1. s/@/@A/g; s/{/@B/g; s/}/@C/gse está convirtiendo {y }a cadenas de marcador de posición que no pueden existir en la entrada para que esos caracteres estén disponibles para convertir fooy bara.
  2. s/foo/{/g; s/bar/}/ges convertir fooy bara {y }respectivamente
  3. s/{[^{}]*}/<&>/gestá realizando la operación que queremos: convertir foo...bara<foo...bar>
  4. s/}/bar/g; s/{/foo/gestá convirtiendo {y de }vuelta a fooy bar.
  5. s/@C/}/g; s/@B/{/g; s/@A/@/g está convirtiendo las cadenas de marcador de posición a sus caracteres originales.

Tenga en cuenta que lo anterior no se basa en que ninguna cadena particular no esté presente en la entrada, ya que fabrica tales cadenas en el primer paso, ni le importa qué ocurrencia de una expresión regular particular que desee hacer coincidir, ya que puede usar {[^{}]*}tantas veces como sea necesario en la expresión para aislar la coincidencia real que desea y / o con el operador de coincidencia numérica seds, por ejemplo, para reemplazar solo la segunda aparición:

$ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/2; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
ABC foo DEF bar GHI <foo KLM bar> NOP foo QRS bar TUV

1

Todavía no he visto esta respuesta, así que así es como puedes hacer esto con vio vim:

vi -c '%s/\(http:\/\/.\{-}\/\).*/\1/ge | wq' file &>/dev/null

Esto ejecuta la vi :%ssustitución globalmente (el final g), se abstiene de generar un error si no se encuentra el patrón ( e), luego guarda los cambios resultantes en el disco y se cierra. Esto &>/dev/nullevita que la GUI parpadee brevemente en la pantalla, lo que puede ser molesto.

Me gusta usar via veces por expresiones regulares muy complicado, debido a que (1) Perl es muertos moribundos, (2) vim tiene una muy motor de expresiones regulares avanzada, y (3) ya estoy íntimamente familiarizado con viexpresiones regulares en mi edición de uso del día a día documentos.


0
echo "/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|'

no te molestes, lo tengo en otro foro :)


44
para que pueda obtener partido codicioso: /home/one/two/three/, si se agrega otro /como /home/one/two/three/four/myfile.txtustedes avidez coincidir fourasí: /home/one/two/three/four, la pregunta es sobre no expansivo
stefanb

0

sed 's|\(http:\/\/www\.[a-z.0-9]*\/\).*|\1| funciona tambien


0

Aquí hay algo que puede hacer con un enfoque de dos pasos y awk:

A=http://www.suepearson.co.uk/product/174/71/3816/  
echo $A|awk '  
{  
  var=gensub(///,"||",3,$0) ;  
  sub(/\|\|.*/,"",var);  
  print var  
}'  

Salida: http://www.suepearson.co.uk

¡Espero que ayude!


0

Otra versión sed:

sed 's|/[:alnum:].*||' file.txt

Coincide /seguido de un carácter alfanumérico (por lo que no es otra barra diagonal), así como el resto de los caracteres hasta el final de la línea. Luego lo reemplaza con nada (es decir, lo elimina).


1
Supongo que debería ser "[[:alnum:]]", no "[:alphanum:]".
oli_arborum
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.