table - what is greedy regex




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

Simulando o 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 tem um ponto . corresponde ao próximo caractere imediato.

      Como o sinalizador global está definido, o mecanismo tenta continuar caractere por caractere até o final da string de entrada 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.

      Se for encontrado, outros dígitos seguintes são consumidos e capturados e o restante da linha é 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 entrada:

    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

    A primeira 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 .

    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.

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/

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 o slash trainling, 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.


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 do 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 entrada, 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 entrada mais simples você poderia usar uma palavra menor, ou talvez até mesmo um único caractere.

HTH!


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!


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, substituir e imprimir s/<pattern>/<replace>/p
  • uso ; separador de comandos de pesquisa em vez de / para facilitar a digitação de modo s;<pattern>;<replace>;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 /
  • primeiro ^ 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 qualquer caractere 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 a partida lembrada 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 incluir 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/

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 <foo...bar> assim, por exemplo, esta entrada:

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

deve se tornar esta saída:

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

Para fazer isso, você converte foo e bar em caracteres individuais e, em seguida, 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 <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> 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 cadeias de caracteres de espaços reservados que não podem existir na entrada, então esses caracteres estão disponíveis para converter foo e bar em.
  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 <foo...bar>
  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 específica estar presente na entrada, pois ela fabrica essas strings na primeira etapa, nem se importa com a ocorrência de qualquer regexp específico que você queira 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 substituir 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 <foo KLM bar> NOP foo QRS bar TUV

Eu percebo que esta é uma entrada 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 \}


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|'

Outra versão sed:

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

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


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 -E interpreta expressões regulares como expressões regulares estendidas (modernas)

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


sed - correspondência não gananciosa de 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 "<b>foo</b>bar" | sed 's/<.*>//g'
bar

Correspondência não gananciosa

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

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


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

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


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






regex-greedy