Correspondência de regex não ganancioso (relutante) em sed?

Eu estou tentando usar sed para limpar linhas de URLs para extrair apenas o domínio ..

Então, de:

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

Eu quero:

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

(com ou sem a barra de treino, não importa)

Eu tentei:

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

e (escapando do quantificador não ganancioso)

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

mas eu não consigo fazer o quantificador não ganancioso funcionar, então ele sempre acaba combinando com a string inteira.

Nem o regex básico nem estendido do Posix / GNU reconhece o quantificador não-guloso; você precisa de um regex mais tarde. Felizmente, regex Perl para este contexto é muito fácil de obter:

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

Tente [^/]* vez de .*? :

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

Com o sed, eu geralmente implemento pesquisa não-gulosa, procurando por qualquer coisa, exceto o separador até o separador:

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

Saída:

 http://www.suon.co.uk 

isto é:

  • não imprima -n
  • pesquisar, combinar padrão, replace e imprimir s///p
  • uso ; separador de comandos de pesquisa em vez de / para facilitar a digitação de modo s;;;p
  • lembre-se de correspondência entre parênteses \(\) , mais tarde acessível com \1 , \2
  • correspondência http://
  • seguido por qualquer coisa entre colchetes [] , [ab/] significaria a ou b ou /
  • first ^ in [] significa not , então seguido por qualquer coisa exceto a coisa no []
  • então [^/] significa qualquer coisa exceto / caractere
  • * é repetir o grupo anterior, então [^/]* significa caracteres, exceto / .
  • até agora sed -n 's;\(http://[^/]*\) significa pesquisar e lembrar http:// seguido por quaisquer caracteres exceto / e lembrar o que você encontrou
  • queremos pesquisar até o final do domínio, então pare no próximo / então adicione outro / no final: sed -n 's;\(http://[^/]*\)/' mas queremos combinar o resto da linha após o domínio so add .*
  • agora o jogo lembrado no grupo 1 ( \1 ) é o domínio, então substitua a linha correspondente por itens salvos no grupo \1 e imprima: sed -n 's;\(http://[^/]*\)/.*;\1;p'

Se você quiser include a barra invertida após o domínio também, adicione mais uma barra invertida no grupo para lembrar:

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

saída:

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

O sed não suporta o operador “não ganancioso”.

Você precisa usar o operador “[]” para excluir “/” da correspondência.

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

PS não há necessidade de barra invertida “/”.

Solução não-gananciosa para mais de um único caractere

Este tópico é muito antigo, mas eu suponho que as pessoas ainda precisam dele. Vamos dizer que você quer matar tudo até a primeira ocorrência de HELLO . Você não pode dizer [^HELLO]

Portanto, uma boa solução envolve duas etapas, supondo que você possa poupar uma palavra única que não está esperando na input, digamos, top_sekrit .

Neste caso, podemos:

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

Claro, com uma input mais simples, você poderia usar uma palavra menor, ou talvez até mesmo um único caractere.

HTH!

Simulando quantificador preguiçoso (não-ganancioso) em sed

E todos os outros sabores de regex!

  1. Encontrando a primeira ocorrência de uma expressão:

    • POSIX ERE (usando a opção -r )

      Regex:

       (EXPRESSION).*|. 

      Sed:

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

      Exemplo (encontrar a primeira sequência de dígitos) Demonstração ao vivo :

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

      Como isso funciona ?

      Este regex beneficia de uma alternância | . Em cada posição, o motor procurará o primeiro lado da alternação (nosso alvo) e, se não for correspondido ao segundo lado da alternação, que possui um ponto . corresponde ao próximo caractere imediato.

      insira a descrição da imagem aqui

      Como o sinalizador global está definido, o mecanismo tenta continuar caractere por caractere até o final da string de input ou nosso destino. Assim que o primeiro e único grupo de captura do lado esquerdo da alternação for correspondido (EXPRESSION) restante da linha será imediatamente consumido .* . Nós agora mantemos nosso valor no primeiro grupo de captura.

    • POSIX BRE

      Regex:

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

      Sed:

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

      Exemplo (encontrar a primeira sequência de dígitos):

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

      Este é como a versão ERE, mas sem nenhuma alteração envolvida. Isso é tudo. Em cada posição única, o mecanismo tenta corresponder a um dígito.

      insira a descrição da imagem aqui

      Se for encontrado, outros dígitos seguintes serão consumidos e capturados e o restante da linha será correspondido imediatamente caso contrário, * significa mais ou zero e pula sobre o segundo grupo de captura \(\([0-9]\{1,\}\).*\)* e chega a um ponto . para corresponder a um único caractere e esse processo continua.

  2. Encontrando a primeira ocorrência de uma expressão delimitada :

    Essa abordagem corresponderá à primeira ocorrência de uma string delimitada. Podemos chamá-lo de um bloco de string.

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

    Cadeia de input:

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

    -EDE: end

    -SDE: start

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

    Saída:

     start block #1 end 

    O primeiro regex \(end\).* Faz a correspondência e captura a extremidade do primeiro delimitador e substitui toda a correspondência por caracteres capturados recentes, que é o delimitador final. Neste estágio nossa saída é: foobar start block #1 end .

    insira a descrição da imagem aqui

    Então o resultado é passado para o segundo regex \(\(start.*\)*.\)* Que é o mesmo que a versão POSIX BRE acima. Ele corresponde a um único caractere se o início do delimitador start não for correspondido, caso contrário ele corresponderá e capturará o delimitador inicial e corresponderá ao restante dos caracteres.

    insira a descrição da imagem aqui


Respondendo diretamente a sua pergunta

Usando a abordagem # 2 (expressão delimitada), você deve selecionar duas expressões apropriadas:

  • EDE: [^:/]\/

  • SDE: http:

Uso:

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

Saída:

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

Isso pode ser feito usando o corte:

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

sed – correspondência não gananciosa por Christoph Sieghart

O truque para obter correspondência não-gananciosa no sed é combinar todos os caracteres, exceto o que termina o jogo. Eu sei, um acéfalo, mas eu desperdicei preciosos minutos e scripts de shell devem ser, afinal, rápidos e fáceis. Então, no caso de alguém precisar disso:

Correspondência gananciosa

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

Correspondência não gananciosa

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

outra forma, não usando regex, é usar o método fields / delimiter eg

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

sed certamente tem o seu lugar, mas isso não é um deles!

Como Dee apontou: basta usar o cut . É muito mais simples e muito mais seguro neste caso. Aqui está um exemplo em que extraímos vários componentes da URL usando a syntax 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-) 

da-te:

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

Como você pode ver, essa abordagem é muito mais flexível.

(todo o crédito para Dee)

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

sed -E interpreta expressões regulares como expressões regulares estendidas (modernas)

Atualização: -E no MacOS X, -r no GNU sed.

Ainda há esperança para resolver isso usando puro (GNU) sed. Apesar disso não ser uma solução genérica, em alguns casos você pode usar “loops” para eliminar todas as partes desnecessárias da string da seguinte forma:

 sed -r -e ":loop" -e 's|(http://.+)/.*|\1|' -e "t loop" 
  • -r: Use regex estendido (para parênteses + e sem escape)
  • “: loop”: define um novo label chamado “loop”
  • -e: adiciona comandos ao sed
  • “t loop”: Salta de volta para o label “loop” se houver uma substituição bem-sucedida

O único problema aqui é que ele também cortará o último caractere separador (‘/’), mas se você realmente precisar dele, você pode simplesmente colocá-lo de volta após o término do “loop”, apenas inclua este comando adicional no final do último. linha de comando:

 -e "s,$,/," 

Porque você declarou especificamente que está tentando usar sed (em vez de perl, cut, etc.), tente agrupar. Isso contorna o identificador não-ganancioso potencialmente não sendo reconhecido. O primeiro grupo é o protocolo (por exemplo, ‘http: //’, ‘https: //’, ‘tcp: //’, etc). O segundo grupo é o domínio:

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

Se você não estiver familiarizado com o agrupamento, comece aqui .

Eu percebo que esta é uma input antiga, mas alguém pode achar útil. Como o nome de domínio completo não pode exceder um comprimento total de 253 caracteres, substitua. * Por. \ {1, 255 \}

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

não se incomode, eu entendi em outro fórum 🙂

sed 's|\(http:\/\/www\.[az.0-9]*\/\).*|\1| funciona também

Outra versão sed:

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

Ele corresponde / seguido por um caractere alfanumérico (portanto, não outra barra invertida), assim como o restante dos caracteres até o final da linha. Depois, substitui-a por nada (isto é, apaga-a).

Aqui está algo que você pode fazer com uma abordagem em duas etapas e o awk:

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

Saída: http://www.suepearson.co.uk

Espero que ajude!

Esta é a forma robusta de fazer correspondência não-gulosa de cadeias de caracteres múltiplos usando sed. Vamos dizer que você quer mudar cada foo...bar para assim, por exemplo, esta input:

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

deve se tornar esta saída:

 ABC  GHI  NOP  TUV 

Para fazer isso, você converte foo e bar em caracteres individuais e depois usa a negação desses caracteres entre eles:

 $ 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  GHI  NOP  TUV 

Acima:

  1. s/@/@A/g; s/{/@B/g; s/}/@C/g s/@/@A/g; s/{/@B/g; s/}/@C/g está convertendo { e } para strings de espaço reservado que não podem existir na input, então esses caracteres estão disponíveis para converter foo e bar para.
  2. s/foo/{/g; s/bar/}/g s/foo/{/g; s/bar/}/g está convertendo foo e bar para { e } respectivamente
  3. s/{[^{}]*}/<&>/g está realizando o op que queremos – convertendo foo...bar em
  4. s/}/bar/g; s/{/foo/g s/}/bar/g; s/{/foo/g está convertendo { e } volta para foo e bar .
  5. s/@C/}/g; s/@B/{/g; s/@A/@/g s/@C/}/g; s/@B/{/g; s/@A/@/g está convertendo as strings do alocador de espaço de volta para seus caracteres originais.

Observe que o acima não depende de nenhuma string em particular estar presente na input, pois ela fabrica essas strings na primeira etapa, nem se importa com a ocorrência de qualquer regexp específico que você deseja corresponder, pois você pode usar {[^{}]*} quantas vezes forem necessárias na expressão para isolar a correspondência real desejada e / ou com o operador de correspondência numérica de seds, por exemplo, para replace apenas a segunda ocorrência:

 $ 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  NOP foo QRS bar TUV 
Intereting Posts