Substituindo texto de uma lista de substituições. Adicionado complicação: barras invertidas

4

Eu tenho um arquivo A que contém pares de strings, um por linha:

\old1 \new1
\old2 \new2
.....

Eu gostaria de fazer uma iteração sobre o arquivo A, e para cada linha executar a substituição (por exemplo, "\ old1 - > \ new1") globalmente em algum arquivo B. Não tive problemas para fazê-lo funcionar sem barras invertidas usando sed ou perl -pi -e usando algo como o seguinte:

while read -r line
do
 set -- $line
 sed -i -e s/$1/$2/g target
done < replacements

No entanto, não consigo descobrir como sed ou perl tratam as barras invertidas literalmente nas cadeias de substituição. Existe uma solução limpa para isso?

    
por Leo Alekseyev 16.04.2011 / 11:59

5 respostas

4

Você precisará escapar de todos os caracteres especiais em expressões regulares, não apenas barras invertidas, mas também [.*^$ e s delimitador (para sed). Em Perl, use a função quotemeta .

Um outro problema com sua tentativa é que quando você executa set -- $line , o shell executa sua própria expansão: ele executa globbing além da divisão de palavras, portanto, se sua linha contiver a* b* e houver arquivos chamados a1 e a2 no diretório atual, então você substituirá a1 por a2 . Você precisa desativar o globbing com set -f nessa abordagem.

Aqui está uma solução que manipula a lista de substituição diretamente em uma lista de argumentos do sed. Ele pressupõe que não há nenhum caractere de espaço nos textos de origem e de substituição, mas qualquer outra coisa que não seja um espaço e uma nova linha deve ser tratada corretamente. A primeira substituição adiciona \ antes dos caracteres que precisam ser protegidos, e a segunda substituição transforma cada linha de foo bar em -e s/foo/bar/g . Atenção, não testado.

set -f
sed_args=$(<replacement sed -e 's~[/.*[\^$]~\&~g' \
                            -e 's~^\([^ ]*\)  *\([^ ]*\).*~-e s///g~')
sed -i $sed_args target

Em Perl, você terá menos problemas com aspas, se você apenas permitir que Perl leia o arquivo de substituição diretamente. Mais uma vez, não testado.

perl -i -pe 'BEGIN {
   open R, "<replacement" or die;
   while (<R>) {
       chomp;
       ($from, $to, @ignored) = split / +/;
       $s{$from} = $to;
   }
   close R;
   $regexp = join("|", map {quotemeta} keys %s);
}
s/($regexp)/$s{$1}/ego'
    
por 16.04.2011 / 14:38
2

Esta é uma tentativa de escapar da barra invertida usando expansão de parâmetro com substituição de padrão.

$ set -- \foo \bar
$ echo $1
\foo
$ echo ${1/\/\\}
\foo
$ echo "This is \foo to me"
This is \foo to me
$ echo "This is \foo to me" | sed s/${1/\/\\}/${2/\/\\}/
This is \bar to me
$ 
    
por 16.04.2011 / 14:03
2

Para casos simples, existem soluções simples, portanto, se você tiver palavras limpas, simples e básicas, sem.? * * {} () [] \ / e talvez mais sed-stuff, você pode transferir o lista de pares para um sed-command-file com sed:

sed -re 's,(^\| \|$),/,g;s/^/s/;s/$/g/' pairs.txt > pairs.sed
sed -f pairs.sed input > output
    
por 17.04.2011 / 03:45
0

Você pode precisar pré-processar sua lista de substituições para escapar de qualquer coisa como as barras que terão significados especiais quando colocadas em um regex. Primeiro, escape-os e use-os para iterar.

Dependendo da função que você está usando para fazer a substituição, às vezes há sinalizadores que você pode adicionar para tratar seqüências de caracteres literalmente. Se você mostrar sua solução parcial, talvez possamos sugerir a maneira correta de terminá-la.

    
por 16.04.2011 / 12:13
0

Isso faz a mesma suposição sobre espaços como a resposta de @Gilles, mas evita o loop while...read . A primeira barra invertida escapa de qualquer ocorrência de metacaracteres BRE de sed , depois imprime seu número de linha atual e substitui globalmente cada par de caracteres não espaciais que pode encontrar em uma instrução de substituição sed . Em seguida, um segundo sed transforma a primeira saída de sed em algo como:

[linenum]{
s///g;s///g;
}

... então o terceiro sed pode ler seu script no stdin e operar diretamente no arquivo2 sem mexer em um loop de shell.

<file1 \
sed 's|[]\*/^$.[]|\&|g;=
     s|\([^ ]*\) \([^ ]*\)|s///g;|g' |
sed 'N;s/\(\n\)\(.*\)/{}/'       |
sed -f - file2
    
por 18.02.2015 / 13:33