Como aninhar correspondências globais com sed?

2

Se eu fizer:

sed 's/match/replace/g'

Eu sei que sed irá substituir substituir para cada ocorrência de correspondência em uma linha. Mas e se ...?

echo "match <please dont match this?>" |
sed 's/match/replace/g'

... ou ...

echo "never match unless <the match is somehow delimited?>" |
sed 's/match/replace/g'

Eu sei que posso usar t est ou b ranch loops para reciclar as correspondências isoladamente, mas como posso pular seções de uma linha em um contexto de correspondência s///g lobal?

    
por mikeserv 30.10.2014 / 20:02

1 resposta

2

A coisa sobre sed é que é ganancioso . Ele irá engolir o máximo possível para cada caso. Isso pode ser usado para sua vantagem em um contexto de substituição s///g lobal. Se você \( group \) * zero ou mais corresponde a uma string, sed irá g lobalmente os primeiros em todos os casos. E assim, se você puder delimitar de forma confiável um caso / match this / | ignorar este | , poderá fazer algo assim:

sed 's/\([^<>]*<\)*\(match  *\)*\(remove  *\)*//g
     s/.\{,45\}[^ ]*/&\
/g;  s/\(\n\) *//g
' <<INPUT
Never remove any match unless <the match \
you want to remove is somehow delimited.> \
And you can remove any match <per your match \
delimiter as many times as your match occurs \
within the match delimiters.>
INPUT

OUTPUT

Never remove any match unless <the you want to
is somehow delimited.> And you can remove any
match <per your delimiter as many times as your
occurs within the delimiters.>

A entrada existe uma única linha porque o shell escapa das novas linhas no documento here nas barras invertidas. sed divide em 45 char (dar ou receber) limites e imprime. Ainda assim, como você pode ver, todas as ocorrências de correspondência ou removidas fora de um limite < ... > permanecem, enquanto todas as ocorrências dentro são removidos da saída.

Esta é uma função da ganância de sed , pois se aplica a uma correspondência que ocorre com * zero-ou-mais vezes. É essa mesma ganância que torna as substituições impossíveis de fazer da mesma maneira, embora isso requeira apenas um passo extra ou dois para negar.

Para ter uma ideia clara de como isso funciona, podemos realizar uma substituição - que, a propósito, não costuma ser muito útil se aplicada diretamente, como quero mostrar:

printf '%s %s\n' '<321Nu0-9mber123>' \
                 'String321strinG' \
                 '<321Nu0-9mber123>' \
                 'String321strinG' |
sed 's/\(<[^<>]*>\)*[0-9]*/!/g'

OUTPUT

<321Nu0-9mber123>! !S!t!r!i!n!g!s!t!r!i!n!G!
<321Nu0-9mber123>! !S!t!r!i!n!g!s!t!r!i!n!G!

Portanto, quando sed corresponde à linha em um padrão global, ela tenta corresponder esse padrão tantas vezes quanto puder, mantendo a cobiça característica. Um efeito colateral da ganância quando um padrão para ocorrências zero-ou-mais é especificado e não corresponde a uma seção da linha é que ainda corresponde - corresponde ao null-string entre os bytes na parte da linha que ele não conseguiu corresponder.

Acima, você pode ver que a string < ... > não é afetada, enquanto os dígitos que estavam dentro de String ... não apenas desapareceram, mas também que sed inseriu um estrondo para cada personagem. Isso reflete a correspondência de sed para a string nula a cada vez. É por esse motivo que essa técnica é útil para g lobally delimitar uma substituição de correspondência em vez de fazer uma.

E aqui está como isso pode funcionar:

printf '%s\t%s\n' '<321Nu0-9mber123>' \
                'String321strinG' \
                '<321Nu0-9mber123>' \
                'String321strinG' |
sed 's/[0-9]/&\n/g;s/\(<[^<>]*>\)*\n*//g;y/\n/0/'

OUTPUT

<302010Nu00-90mber102030>       String321strinG
<302010Nu00-90mber102030>       String321strinG

Isso acrescenta um zero a cada dígito que ocorre em < e > - que é um caso bastante simples - mas, na verdade, você pode usar o caractere \n ewline dessa maneira para realizar substituições globais para qualquer correspondência. O princípio básico é:

  1. Do sed 's/match/&\n/g'
  2. Então, sed 's/\(match group\)*\n*//g'
  3. Por último, faça sed 's/match\n/replace/g'

É verdade que esses exemplos demonstram apenas exemplos de lista simples - < sempre precede > . Os ninhos também precisam de consideração. Eles são mais difíceis - às vezes muito mais difíceis - mas, bem ...

sed 's/\([{}]\)\([^{}]*[{}]*\)*/\n<&>/g
' <<\INPUT
{{{1!}{2!}{3!}}}outside!{{{4!}}{{5!}}}
INPUT

OUTPUT

<{{{1!}{2!}{>3!
<}}}>outside!
<{{{4!}}{{>5!
<}}}>

Ele serializa grupos em novas linhas. Ele funciona alternando o delimitador que corresponde a cada grupo de correspondências enquanto empilha o maior número de caracteres do mesmo tipo que pode duas vezes seguidas (pelo menos duas vezes) e como ventos de efeito colateral Comparando abre para fecha. Dito isto, por uma questão de simplicidade, o restante pressupõe que qualquer leitor utilizará meios semelhantes para preparar informações e que os ninhos não são um problema.

Essencialmente, a ideia operativa de tudo isso é precedência de correspondência. O primeiro exemplo trabalhou ao tentar corresponder qualquer grupo de caracteres não delimitadores imediatamente antes de um delimitador de abertura antes de tentar corresponder as cadeias de remoção. É lógico que, se o primeiro grupo corresponder, quando a substituição for concluída, todo o grupo correspondido só poderá ser substituído por si próprio - e é isso que pode dificultar as substituições. As remoções são mais simples porque quando você as combina, simplesmente as deixa fora da substituição e tudo está bem.

Além disso, sed avalia determinados tipos de padrões mais do que outros. É importante entender que quando você faz isso, qualquer padrão especificado definitivamente sempre carregará mais peso do que um caso * zero-ou-mais . Então, quando você usa estes para padrões globais, use somente * ou não os use - ou você pode acabar sem pular nenhum grupo.

E é assim que você faz isso com sed .

    
por 30.10.2014 / 20:02