Substitui o padrão de cada vez por uma string diferente (extraída do arquivo externo)

4

Eu tenho uma entrada:

a
b TOCHANGE
c
d 
e TOCHANGE

em que preciso alterar os padrões "TOCHANGE" usando um arquivo externo:

line1
line2
...

para obter a seguinte saída:

a
b line1    
c
d
e line2

Eu tentei o seguinte comando:

while read k ; do sed -i "s/TOCHANGE/$k/g" input ; done < externalfile

mas eu tenho:

a
b line1    
c
d
e line1
    
por E.C 24.04.2018 / 15:47

4 respostas

5

com perl :

perl -pi -e 's{TOCHANGE}{chomp ($repl = <STDIN>); $repl}ge' input <externalfile

Com awk , supondo que TOCHANGE não ocorra em externalfile (ou, mais geralmente, que as substituições não gerem novas ocorrências de TOCHANGE , o que também pode acontecer, por exemplo, em uma entrada que contenha TOTOCHANGE FROMTOCHANGE e externalfile contém CHANGE e WHATEVER ):

POSIXLY_CORRECT=1 PAT=TOCHANGE awk '
  {
    while ($0 ~ ENVIRON["PAT"]) {
      getline repl < "externalfile"
      gsub(/[&\]/, "\\&", repl)
      sub(ENVIRON["PAT"], repl)
    }
    print
  }' < input > input.new

( POSIXLY_CORRECT=1 é necessário para o GNU awk , onde sem o qual não funcionaria corretamente para as cadeias de substituição que contenham caracteres de barra invertida).

Observe que $PAT acima é considerado uma expressão regular estendida. Você pode precisar escapar dos operadores ERE se quiser que eles sejam tratados literalmente (como PAT='TO\.CHANGE' para substituir TO.CHANGE strings).

    
por 24.04.2018 / 16:01
2

Isso pode ser feito com o GNU sed como determinado:

sed -e '/TOCHANGE/R file_2' input.txt |
sed -e '/TOCHANGE/N;s/TOCHANGE\(.*\)\n\(.*\)//' 

Na primeira passagem, sed colocará uma linha de arquivo_2 abaixo da linha TOCHANGE para todas as linhas em input.txt

Na próxima passagem, a linha que compreende TOCHANGE será unida com a seguinte linha e uma substituição s /// obterá a saída pretendida.

Com Perl pode ser realizado como:

perl -pe 's|TOCHANGE|<STDIN> =~ s/\n//r|e' input.txt < file_2
    
por 26.04.2018 / 08:41
1

Com esse awk

awk -v old='TOCHANGE' '
NR==FNR{a[NR]=$0;next}
$2==old{$2=a[++i]}
1' changefile infile > outfile
    
por 24.04.2018 / 17:43
1

Acople soluções complicadas com o uso específico dos recursos awk .

Primeira variante

Se o padrão "TOCHANGE" ocorrer apenas de uma vez em cada linha. O usual awk será suficiente.

awk '{
    if(NF == 2) {
        getline OFS < "file_2"
        $1 = $1
    }    
    print
}' FS='TOCHANGE' input.txt

Segunda variante

Se o padrão "TOCHANGE" puder ocorrer várias vezes em cada linha. O gawk necessário.

gawk '{
    RS="\n"
    if(RT)
        getline ORS < "file_2"
    else
        ORS=""

    print

    RS="TOCHANGE"
}' RS='TOCHANGE' input.txt

Teste

input.txt

a
b TOCHANGE
c
d 
e TOCHANGE
f
g TOCHANGE

file_2

line1
line2
line3
line4

Resultado

a
b line1
c
d 
e line2
f
g line3
    
por 25.04.2018 / 12:20