Substitua strings em um arquivo baseado em uma lista de strings e uma lista de substituições correspondentes

5

Estou tentando substituir strings em file A :

Hello Peter, how is your dad? where is mom? 

onde as strings a serem substituídas estão em file B :

Peter
dad
mom

e suas substituições correspondentes estão em file C :

John
wife
grandpa

Resultado esperado:

Hello John, how is your wife? where is grandpa?

Posso editar file A , substituindo o valor em file B usando o valor da linha correspondente em file C ?

O que eu fiz até agora:

 cat 1.txt | sed -e "s/$(sed 's:/:\/:g' 2.txt)/$(sed 's:/:\/:g' 3.txt)/" > 4.txt

funciona se houver apenas uma linha em file B & file C , se houver mais de uma linha, não funcionará.

    
por Robert Choy 20.03.2016 / 17:52

5 respostas

-1

A solução que criei não é muito curta, mas é simples o suficiente para ser muito legível. a menos que sua tarefa fosse fazer a coisa toda com sed ...?

 #!/usr/bin/bash

 cp A.txt D.txt

 x=1
 length=$(wc -l B.txt | sed 's/\ .*//g')

 until [ $x -eq $length ]; do

    Bx=$(awk "NR==$x" B.txt)
    Cx=$(awk "NR==$x" C.txt)

    sed -i "s/$Bx/$Cx/g" D.txt

    x=$(($x+1))

 done

 rm -f ./sed*

observe que este script cria uma tonelada de arquivos inúteis se B.txt for maior que C.txt e talvez vice-versa (não foi testado até agora)

    
por 20.03.2016 / 18:29
9

A maneira mais fácil de fazer isso com sed é processar essas duas listas e transformá-las em um arquivo de script , por exemplo.

s/line1-from-fileB/line1-from-fileC/g
s/line2-from-fileB/line2-from-fileC/g
....................................
s/lineN-from-fileB/lineN-from-fileC/g

que sed será executado, editando fileA . A maneira correta é processar o LHS / RHS primeiro e escapar de quaisquer caracteres especiais que possam aparecer nessas linhas, depois juntar o LHS e RHS adicionando o s , os delimitadores / e g (por exemplo, com paste ) e canalizar o resultado para sed :

paste -ds///g /dev/null /dev/null \
<(sed 's|[[\.*^$/]|\&|g' fileB) <(sed 's|[\&/]|\&|g' fileC) \
/dev/null /dev/null | sed -f - fileA

Portanto, existe: um paste e três sed s que processará cada arquivo apenas uma vez, independentemente do número de linhas.
Isto assume que o seu shell suporta a substituição de processos e que o seu sed pode ler um script-file do stdin . Além disso, ele não é editado no local (deixei de fora a opção -i , pois não há suporte para todos os sed s)

    
por 20.03.2016 / 19:51
2

Se você deseja que as substituições sejam feitas independentemente umas das outras, por exemplo:

foo -> bar
bar -> foo

Aplicado em

foobar

Para resultar em:

barfoo

ao contrário de foofoo como uma tradução ingênua de s/foo/bar/g; s/bar/foo/g faria, você poderia:

perl -pe '
  BEGIN{
    open STRINGS, "<", shift@ARGV or die"STRINGS: $!";
    open REPLACEMENTS, "<", shift@ARGV or die "REPLACEMENTS: $!";
    while (defined($a=<STRINGS>) and defined($b=<REPLACEMENTS>)) {
      chomp ($a, $b);
      push @repl, $b;
      push @re, "$a(?{\$repl=\$repl[" . $i++. "]})"
    }
    eval q($re = qr{) . join("|", @re) . "}";
  }
  s/$re/$repl/g' strings.txt replacements.txt fileA 

Isso é perl regexps esperado em patterns.txt . Como os regexps perl podem executar código arbitrário, é importante que eles sejam higienizados. Se você quiser substituir somente as strings fixas, altere para:

perl -pe '
  BEGIN{
    open PATTERNS, "<", shift@ARGV or die"PATTERNS: $!";
    open REPLACEMENTS, "<", shift@ARGV or die "REPLACEMENTS: $!";
    for ($i = 0; defined($a=<PATTERNS>) and defined($b=<REPLACEMENTS>); $i++) {
      chomp ($a, $b);
      push @string, $a;
      push @repl, $b;
      push @re, "\Q\$string[$i]\E(?{\$repl=\$repl[$i]})"
    }
    eval q($re = qr{) . join("|", @re) . "}";
  }
  s/$re/$repl/g' patterns.txt replacements.txt fileA 
    
por 31.08.2017 / 17:14
1

No exemplo simples, você mostra onde cada uma das palavras de destino aparece apenas uma vez no arquivo, você poderia simplesmente fazer:

$ paste fileB fileC | while read a b; do sed -i "s/$a/$b/" fileA; done
$ cat fileA
Hello John, how is your wife? where is grandpa? 

O comando paste imprimirá os dados dos dois arquivos combinados:

$ paste fileB fileC
Peter   John
dad wife
mom grandpa

Passamos isso por meio de um simples loop while read , que itera todas as linhas, salvando o valor de fileB as $a e de fileC as $b . Em seguida, o comando sed substituirá a primeira ocorrência de $a por $b . Isso é repetido três vezes.

Essa abordagem é válida se você souber que suas palavras-alvo aparecem apenas uma vez no arquivo (elas precisam, caso contrário, você precisará fornecer mais detalhes que possamos usar para identificar qual ocorrência deve ser substituída) e se os arquivos são pequenos, como o que você mostrou. Para arquivos maiores, isso levará muito tempo e será muito ineficiente, já que precisará ser executado uma vez para cada par de palavras.

Então, se você tiver arquivos maiores, talvez queira algo assim:

paste fileB fileC | 
    perl -lane '$words{$F[0]}=$F[1]} 
        END{open(A,"fileA"); while(<A>){s/$_/$words{$_}/ for keys %words; print}'
    
por 20.03.2016 / 18:54
-2

Isso pode ajudar seu problema a ser resolvido. (Consulte: )

O Source.txt tem duas linhas a seguir:

OldString
NewString

Antes da execução do comando, o Target.txt tem as seguintes linhas:

OldString ==> NewString
This is Target File containing OldString now.
OldString is to be replaced.
NewString won't get impacted.

Uso:

awk -v lookupStr='awk 'NR==1' Source.txt' -v replacementStr='awk 'NR==2' Source.txt' 'NR==2 && (idx=index($0,lookupStr)) { $0=substr($0,1,idx-1) replacementStr substr($0,idx+length(lookupStr)) } 1' Target.txt > temp.txt && mv temp.txt Target.txt

Execução do comando de postagem O destino.txt tem a seguinte linha:

OldString ==> NewString
This is Target File containing NewString now.
OldString is to be replaced.
NewString won't get impacted.

Aqui eu defini duas variáveis lookupStr e replacementStr. ambos são atribuídos à linha # 1 e à linha # 2 de Source.txt, respectivamente. Então, na linha Sencond de Target.txt, estou substituindo o conteúdo de $ 0 pelo primeiro caractere até o índice de lookupStr (ou seja, "OldString"), acrescentando o replacementStr (ou seja, "NewString") e concatenando o restante dos caracteres. No final, a saída está sendo gravada em um temp.txt e a mesma é renomeada para Target.txt

Se você precisar fazer esse exercício de substituição em um arquivo inteiro, basta remover a condição NR == 2 & & do comando acima.

    
por 15.05.2016 / 02:14