Procura e substitui strings que não são substrings de outras strings

5

Eu tenho uma lista de substitutos assim:

search_and -> replace
big_boy -> bb
little_boy -> lb
good_dog -> gd
...

Eu preciso fazer substituições para o acima, mas ao mesmo tempo, evite sequências de caracteres correspondentes que são mais longas como estas:

big_boys
good_little_boy

Eu tentei isso:

sed -i -r "s/$(\W){search}(\W)/${replacement}/g"

Mas o acima não funciona quando a string ("good_dog" neste caso) ocorre no final de uma linha da seguinte forma:

Mary had a 'little_boy', good_little_boy, $big_boy, big_boys and good_dog

Mary had a 'lb', good_little_boy, $bb, big_boys and good_dog

E duvido que o acima funcione quando a string ocorre no início da linha também. Existe uma boa maneira de pesquisar e substituir?

    
por Question Overflow 12.07.2014 / 10:40

3 respostas

4

Se você estiver usando o GNU sed (o que você acha que -i sugere), há um " limite de palavras "escape \b :

sed -i "s/\b$SEARCH\b/$REPLACE/g"

\b corresponde exatamente a um limite de palavra: o caractere de um lado é um caractere de "palavra" e o caractere do outro não. É uma correspondência de largura zero, portanto, não é necessário usar subgrupos de captura para manter o valor com e . Há também \B , que é exatamente o oposto.

Se você não estiver usando o GNU sed, poderá usar a alternância com o início e o fim da linha em seus subpadrões de captura: (\W|^) . Isso corresponderá a um caractere não pertencente à palavra ou ao início de uma linha, e (\W|$) corresponderá a um caractere não pertencente à palavra ou ao final de uma linha. Nesse caso, você ainda usa e como estava. Alguns não-GNU sed s suportam \b de qualquer maneira, pelo menos em um modo estendido, então vale a pena dar essa chance independentemente.

    
por 12.07.2014 / 10:48
3

Se você quiser mais portáteis, use \< e \> :

sed -i "s/\<$SEARCH\>/$REPLACE/g" file

\< e \> funcionam em gsed, ssed, sed15, sed16, sedmod.

\b e \B funcionam somente em gsed.

Em Mac OSX , você deve usar esta sintaxe:

sed -i '' -e "/[[:<:]]$SEARCH[[:>:]]/$REPLACE/g" file
    
por 12.07.2014 / 11:23
3

Você também pode usar o perl, que deve suportar \b em todas as plataformas. Supondo que sua lista de substituições esteja no formato que você mostra (separado por -> ), você poderia fazer:

perl -F"->" -ane 'chomp;$rep{$F[0]}=${$F[1]}; 
                  END{open(A,"file"); 
                    while(<A>){
                        s/\b$_\b/$rep{$_}/g for keys(%rep); 
                        print
                    }
                  }' replacements

Explicação

  • O -a faz o perl rodar como awk, dividindo campos automaticamente na matriz @F então $F[0] é o primeiro campo, $F[1] o segundo etc. O -F define o separador do campo de entrada , assim como -F no awk. O -n significa "leia o arquivo de entrada, linha por linha e aplique o script dado por -e a cada linha".

  • chomp : remove novas linhas ( \n ) do final da linha.

  • $rep{$F[0]}=${$F[1]}; : preenche o hash %rep fazendo com que o padrão seja substituído (o primeiro campo, $F[0] ) a chave e a substituição ( $F[1] ) o valor. * END{} : isso é executado depois que o arquivo de entrada ( replacements ) foi lido.
  • open(A,"file") : abra o arquivo file para leitura com o manipulador de arquivo A .
  • while (<A>) : leia o arquivo linha por linha.
  • s/// for keys(%rep) : iterará todas as chaves do hash %rep , salvando cada chave como a variável especial $_ . O s/// é o operador de substituição e está fazendo a mesma substituição conforme explicado na resposta de Michael .

Você também pode ler o arquivo e usar sed conforme mostrado nas outras respostas:

$ sed 's/->/\t/' replacements | 
    while IFS=$'\t' read from to; do sed -i "s/\b$from\b/$to/g" file; done
    
por 12.07.2014 / 14:46