Duplicar e substituir um padrão em um arquivo de texto

2

Vamos considerar um arquivo de texto de entrada como este:

some text …
% BEGIN
blabla
foo bar
blabla
blabla
% END
some text …

e um arquivo foobar.txt como este:

2 3
8 9 
1 2

qual é a maneira mais simples de usar sed (talvez awk ?) para obter este arquivo de texto de saída:

some text …
% BEGIN
blabla
2 3
blabla
blabla
% END
% BEGIN
blabla
8 9
blabla
blabla
% END
% BEGIN
blabla
1 2
blabla
blabla
% END
some text …
    
por Jean-Pierre 13.05.2017 / 22:38

6 respostas

0

Solução bash + sed complexa:

foobar_replacer.sh script:

#!/bin/bash
head -n1 "$2"  # print the first line

while read -r line
do
    sed '1d;$d;{s/^foo bar$/'"$line"'/g}' "$2"        
done < "$1"

tail -n1 "$2" # print the last line

Uso :

bash foobar_replacer.sh foobar.txt input.txt

A saída:

some text …
% BEGIN
blabla
2 3
blabla
blabla
% END
% BEGIN
blabla
8 9
blabla
blabla
% END
% BEGIN
blabla
1 2
blabla
blabla
% END
some text …

sed detalhes do comando:

1d;$d; - exclua a primeira e a última linha de input.txt

s/^foo bar$/'"$line"'/g - substitua a linha que contém foo bar pelo próximo item $line de foobar.txt

    
por 13.05.2017 / 23:22
2

Aqui está uma maneira incrível de fazer isso, usando getline :

awk '
  /% BEGIN/ {
    s = 1;
  }

  s == 1 {
    b = b == "" ? $0 : b ORS $0
  }

  /% END/ {
    while ((getline repl < "foobar.txt") > 0) {
      tmp = b;
      sub(/foo bar/, repl, tmp);
      print tmp;
    }
    b = "";
    s = 0;
    next;
  }

  s == 0 {
    print;
  }' input

Com o GNU awk, você pode fazer a substituição sem um temporário - usando gensub :

gawk '
  /% BEGIN/ {
    s = 1;
  }

  s == 1 {
    b = b == "" ? $0 : b ORS $0
  }

  /% END/ {
    while ((getline repl < "foobar.txt") > 0) {
      print gensub(/foo bar/, repl, 1, b);
    }
    b = "";
    s = 0;
    next;
  }

  s == 0 {
    print;
  }' input

Teste:

$ gawk '
>   /% BEGIN/ {s = 1;}
>   s == 1 {b = b == "" ? $0 : b ORS $0}
>   /% END/ {while ((getline repl < "foobar.txt") > 0) {print gensub(/foo bar/, repl, 1, b);} s = 0; next;}
>   s == 0 {print}' input
some text …
% BEGIN
blabla
2 3
blabla
blabla
% END
% BEGIN
blabla
8 9 
blabla
blabla
% END
% BEGIN
blabla
1 2
blabla
blabla
% END
some text …
    
por 14.05.2017 / 01:38
1
perl -nMFatal=open -e '$l = $_;
   @ARGV and open my $fh, "<", $ARGV[0];
   print +(/^%\hBEGIN/ ? $a=0 : $a++) == 1 ? $l : $_ while <$fh>;
' foobar.txt input.txt
Trabalhando
  • Para cada linha lida do arquivo foobar.txt, open a lexical filehandle $fh no arquivo input.txt. A razão pela qual tem que ser léxico é porque ele se fecha quando a próxima linha de entrada do foobar.txt é lida.
  • Inicializamos o contador $a quando vemos a linha % BEGIN em input.txt. E 1 linha depois disso, substituímos a linha em input.txt pela linha de foobar.txt.
  • A ordem dos argumentos é: foobar.txt e, em seguida, input.txt.
  • Incluímos o pragma Fatal.pm, que manipula erros ao abrir arquivos automaticamente.
Resultados
some text --
% BEGIN
blabla
2 3
blabla
blabla
% END
some text --
some text --
% BEGIN
blabla
8 9
blabla
blabla
% END
some text --
some text --
% BEGIN
blabla
1 2
blabla
blabla
% END
some text --
    
por 14.05.2017 / 14:59
0

Tente isto:

while read line; do awk -v f="$line" '{gsub(/foo bar/, f)} 1' input; done <foobar.txt

Isso lê linha por linha de foobar.txt . Para cada line em foobar.txt , o arquivo input é lido e o line de foobar.txt é substituído em cada ocorrência de foo bar .

Como funciona

  • while read line; do

    Isso inicia um while -loop que lê linhas do foobar.txt.

  • awk -v f="$line" '{gsub(/foo bar/, f)} 1' input

    Isso lê o arquivo input e substitui $line em todos os lugares em que foo bar ocorre.

    Mais detalhadamente:

    • -v f="$line"

      Isso cria uma variável awk f cujo valor é o conteúdo da variável line .

    • gsub(/foo bar/, f)

      Para cada linha que o awk lê, ele procura ocorrências do regex foo bar e substitui o valor de f

    • 1

      Esta é a abreviatura do awk para imprimir a linha.

    A razão para usar o awk aqui, ao invés de sed, é que o awk tem um melhor manuseio para capturar o valor das variáveis do shell.

  • done <foobar.txt

    Isso sinaliza o fim do while -loop e diz ao loop para usar o arquivo foobar.txt como sua entrada padrão.

Versão multilinha

Para quem gosta de seus comandos espalhados por várias linhas:

while read line
do
    awk -v f="$line" '{gsub(/foo bar/, f)} 1' input
done <foobar.txt
    
por 13.05.2017 / 22:56
0

bash script com sed usando. Uso: ./search_and_replace.sh < input.txt , resultado será no novo arquivo output.txt

#!/bin/bash

begin_str="% BEGIN"
end_str="% END"
pattern="foo bar"
write_to_var_flag=0
output_file=output.txt
foobar_file=foobar.txt
begin_to_end_block_var=""

# clean output file if it exist, else create it
> "$output_file"

function read_foobar_file () {
    while read -r line; do
        echo -ne "$begin_to_end_block_var" | sed "s/$pattern/$line/" >> "$output_file"
    done < "$foobar_file"
}

while read -r line; do
    if  [ "$line" == "$begin_str" ]; then
        write_to_var_flag=1
    fi

    if (( $write_to_var_flag )); then
        begin_to_end_block_var+="$line\n"
    else
        echo "$line" >> "$output_file"
    fi

    if [ "$line" == "$end_str" ]; then
        read_foobar_file 
        write_to_var_flag=0
    fi
done
    
por 14.05.2017 / 12:55
0
sed -e '
   1{
      :loop
         N
      /\n\.$/!bloop
      s///;h
      N;s/.*\n//
   }

   G
   y/\n_/_\n/
   s/^\([^_]*\)_\(.*_% BEGIN_[^_]*_\)[^_]*//
   y/\n_/_\n/
' input.txt foobar.txt

Trabalhando

  • Neste método, a ordem dos argumentos é: input.txt e foobar.txt
  • Como POSIX sed não tem ideia de quando um arquivo termina & o próximo começa, ou precisamos adicionar um e-distintivo, digamos, . , OR com base no tipo de dados nos dois arquivos para ajudar a informar em qual arquivo estamos ". No nosso caso, optei por siga o primeiro método.
  • Primeiro, armazenamos o arquivo input.txt no espaço de armazenamento, o todo dele.
  • Em seguida, para cada linha lida do arquivo foobar.txt, anexamos o espaço de espera a ela e, em seguida, substituímos a segunda linha após a linha % BEGIN no espaço de padrão com a primeira linha. Observação: temos o que é um multiline pattern space , que é ...\n...\n...\n...
por 15.05.2017 / 06:22