Substitua a linha M no arquivo1 pela linha N no arquivo2

3

Demos M=3 e N=4 . Desejo substituir a linha M em file1 pela linha N in file2 . Eu posso substituir por uma string usando sed :

sed -i '3s/.*/stringToReplace/' file1

E posso usar awk para obter a linha N de file2

awk 'NR==4' "file2"

Como posso combinar esses dois? Se eu tentar

sed -i '3s/.*/{awk 'NR==4' "file2"}/' file1

então eu substituo a linha M por literalmente {awk 'NR==4' "file2"} .

    
por Tom 06.10.2017 / 11:19

4 respostas

1

Abordagem curta sed :

Exemplo de arquivo f1 :

f1 line1
f1 line2
f1 line3
f1 line4
f1 line5

Exemplo de arquivo f2 :

ID1,value12,value13
ID1,value22,value23
ID1,value32,value33
ID2,/value42/,~value43~
ID3,value52,value53

O trabalho:

sed '3 s/.*/'"$(sed -n '4{ s/\//\\//g;p;}' f2)"'/;' f1

A saída:

f1 line1
f1 line2
ID2,/value42/,~value43~
f1 line4
f1 line5
    
por 06.10.2017 / 11:44
3

Existem várias maneiras de fazer isso corretamente para manipular entradas arbitrárias.
Com o GNU sed e um sistema que suporta /dev/stdin :

sed -n "${n}{p;q;}" file2 | sed -e "$m{r/dev/stdin" -e 'd;p;}' file1

ou, um pouco mais curto 1 :

sed $n'!d;q' file2 | sed -e $m'{r /dev/stdin' -e 'd;p;}' file1

Com qualquer sed e um shell que suporte a substituição do processo

sed '1h;1d;'$((m+1))'x' <(sed ${n}'!d;q' file2) file1

que também pode ser escrito como

sed ${n}'!d;q' file2 | sed '1h;1d;'$((m+1))'x' - file1

Basicamente, uma invocação de sed extrai a linha n de file2 , que é então lida pelo outro sed como primeiro operando: salva no buffer de retenção, apaga e lê o conteúdo dofile1 2º operando, a saber m+1 , trocando buffers quando na linha sed (da entrada combinada).

Com qualquer -f que suporte a leitura de um arquivo de script via stdin de sed , é possível executar:

sed ${n}'!d;i\
'${m}'c\
s/\/&&/g
q' file2 | sed -f - file1

aqui, o primeiro n transforma a linha file2 de sed em um arquivo de script como

${m}c\
line_n_content_here_with_any_backslash_escaped

que é então usado pelo segundo file1 para processar m (ou seja, substituir linha a\ pelo seguinte texto ... ). Todas as barras invertidas presentes no texto original (juntamente com as novas linhas incorporadas - mas há apenas uma linha) precisam ser ignoradas porque ao usar qualquer i\ , c\ ou sed para adicionar texto

<backslash> characters in text shall be removed, and the following character shall be treated literally.

Com qualquer s , você pode usar o sempre popular comando sed ubstitute certificando-se de que a string seja interpolada em head de substituição escapa todos os caracteres reservados - neste caso em particular, é apenas uma linha, por exemplo,

line=$(sed ${m}'!d;s|[\/&]|\&|g;q' file2)

então substitua:

sed ${m}'s/.*/'"$line"'/' file1

Com enormes arquivos de entrada, você pode executar:

{ head -n $((m-1)); { head -n $((n-1)) >/dev/null; head -n 1; } <file2; head -n 1 >/dev/null; cat; } <file1

que faz algo assim:

print (m-1) lines from file1
discard (n-1) lines from file2
print n-th line from file2
discard m-th line from file1
print the remaining lines from file1

embora alguns sed s sejam burros e não sejam compatíveis com os padrões isso não funciona em todas as configurações ... mas onde isso acontece, isso supera awk , ! e os gostos em termos de velocidade.

1: com alguns shells você pode precisar desativar a expansão do histórico para que $n funcione ...
também, $m e %code% realmente não precisam ser citados aqui, já que devem ser inteiros positivos, embora não causem dano

    
por 06.10.2017 / 11:33
2

Tente isto:

$ cat f1
foo
bar
xyz
baz
temp
good
$ cat f2
1
2
3
4
5
6

$ awk -v m=3 -v n=4 'NR==FNR{if(FNR==n) s=$0; next} FNR==m{$0=s} 1' f2 f1
foo
bar
4
baz
temp
good
  • NR==FNR será verdadeiro somente quando o primeiro arquivo estiver sendo processado
  • if(FNR==n) s=$0 se for enésimo linha, salve em variável
  • next para que o restante do código não seja executado, desde que o primeiro arquivo seja processado
  • FNR==m{$0=s} se for mth linha para o segundo argumento do arquivo, substitua-o
  • 1 imprima registro de entrada, incluindo quaisquer modificações
  • Observe a ordem dos argumentos de entrada de arquivo

Pode usar if(FNR==n){s=$0;nextfile} para evitar linhas de processamento após a linha n

De manual do GNU awk - obrigado @iruvar

NOTE: For many years, nextfile was a common extension. In September 2012, it was accepted for inclusion into the POSIX standard. See the Austin Group website.

    
por 06.10.2017 / 11:33
0

Não misture o awk com sed. Você pode usar uma solução completa de awk

arquivo1:

 file1 1
 file1 2
 file1 3
 file1 4

arquivo2:

 file2 1
 file2 2
 file2 3
 file2 4


 awk -v m=3 -v n=4 'NR == FNR { filea[FNR]=$0 } FNR != NR { fileb[FNR]=$0 } END { for (i=1;i<=FNR;i++) { if ( i == m ) { print fileb[n] } else { print filea[i] } } } ' file1 file2

Detalhado:

 NR == FNR { 
            filea[FNR]=$0 
           }
 FNR != NR { 
            fileb[FNR]=$0 
           }
 END { 
       for (i=1;i<=FNR;i++) { 
                               if ( i == m ) { 
                                         print fileb[n] 
                                             } 
                               else { 
                                          print filea[i] 
                                    } 
                            } 
      } 

Comparamos NR e FNR para determinar os registros em file1 e file2 (quando NR = FNR, sabemos que estamos no primeiro arquivo) Configuramos array filea com os registros em file1 e fileb com os registros em file2. Em seguida, percorremos todos os registros na matriz filea, imprimindo o conteúdo apenas quando o parâmetro passado m não é igual a 3. Se for, em vez disso, imprimimos o índice do arquivo de matriz determinado pelo parâmetro passado n

    
por 06.10.2017 / 11:41