Substituir dados em posições específicas no arquivo txt usando dados de outro arquivo

5

Eu tenho um arquivo de texto no formato abaixo:

$data This is the experimental data    
good data
This is good file
datafile
1 4324 3673 6.2e+11 7687 67576
2 3565 8768 8760 5780 8778          "This is line '2'"
3 7656 8793 -3e+11 7099 79909
4 8768 8965 8769 9879 0970
5 5878 9879 7.970e-1 9070 0709799
.
.
.
100000 3655 6868 97879 96879 69899
$.endfile

Eu quero substituir os dados da 3ª e 4ª colunas da linha '2' para '100000' com os dados de dois outros arquivos de texto que têm uma coluna de 99999 linhas cada.

Como posso fazer isso usando awk , sed ou qualquer outro comando unix? Note que o delimitador de coluna é o espaço.

Os outros dois arquivos de texto têm 99999 linhas cada e estão no seguinte formato:

12414
12421
36347
3.4e+3
-3.5e22
987983
.
.
.
87698
    
por Nilesh 08.09.2014 / 13:20

5 respostas

1

Como você não solicitou uma solução 100% awk , Eu vou oferecer um híbrido que (a) pode, sem dúvida, ser mais fácil de entender, e (b) não enfatiza os limites de memória de awk :

awk '
    $1 == 2 { secondpart = 1 }
       { if (!secondpart) {
                print > "top"
         } else {
                print $1, $2 > "left"
                print $5, $6, $7, $8, $9 > "right"
         }
       }' a
(cat top; paste -d" " left b c right) > new_a
rm top left right

Ou podemos eliminar um dos arquivos temporários e encurtar o script por um comando:

(awk '
    $1 == 2 { secondpart = 1 }
       { if (!secondpart) {
                print
         } else {
                print $1, $2 > "left"
                print $5, $6, $7, $8, $9 > "right"
         }
       }' a; paste -d" " left b c right) > new_a
rm left right

Isto irá colocar alguns espaços extras nas extremidades das linhas da saída, e perderá dados do arquivo a se qualquer linha tiver mais de nove campos (colunas). Se esses são problemas, eles podem ser corrigidos facilmente.

    
por 08.09.2014 / 18:28
2

Um jeito estranho:

awk '{if(FNR==NR){f2[FNR+1]=$1;} 
      else{
        if(FNR==1){k++;} 
        if(k==1){f3[FNR+1]=$1} 
        else{if($1~/^[0-9]+/ && $1>1){$3=f2[$1];$4=f3[$1];} 
         print}
  }}' file2 file3 file1 

Esta é a mesma coisa escrita como um script comentado para maior clareza:

#!/usr/local/bin/gawk -f

{
    ## NR is the current line number, irrespective of 
    ## which input file is being read. FNR is the line 
    ## number of the current file. It is reset to 1 each 
    ## time a new file is opened. Therefore, FNR will be 
    ## equal to NR only while the 1st file is being read.
    if(FNR==NR){
        ## If this is the 1st file, save its 1st field
        ## in the array f2. The key of the array is the
        ## line number of the current file plus one. This is
        ## because you want to start modifying from row '2' onwards.
        ## Therefore, presumably, you want the 1st row of file2 to
        ## be the value for row '2' of your data file..
        f2[FNR+1]=$1;
    } 
    ## If this is not the 1st file
    else{
        ## If this is the 1st line of the current file
        if(FNR==1){
            ## Increase the value of the variable k by 1.
            k++;
        } 
        ## If k is currently 1, this means that the above has only
        ## been run once so we are currently reading the 1nd file.
        if(k==1){
            ## Save the 1st field of this file (file3 in your example)
            ## in the array f3. The key of the array is the
            ## line number of the current file plus one. 
            f3[FNR+1]=$1
        }
        ## If k is not 1, we are reading the 3rd file. In this case, 
        ## your actual data.
        else{
            ## If the 1st field is a number and is greater than 1.
            ## In other words, if this is one of the lines you want
            ## to change. 
            if($1~/^[0-9]+/ && $1>1){
                ## Set the 3rd field to be the value saved in the array
                ## f2 for the value of $1.  
                $3=f2[$1];
                ## Set the 4th field to be the value saved in the array
                ## f3 for the value of $1. 
                $4=f3[$1];
            } 
            ## Print the current line. Since this is outside the
            ## previous if block, it will print all lines irrespective
            ## of whether they've been modified. 
            print;
        }
    }
}

Um caminho Perl:

perl -lane 'BEGIN{
    open(A,"file2"); while(<A>){chomp; $f2{$.+1}=$_;} 
    open(B,"file3"); while(<B>){chomp; $f3{$.+1}=$_;}} 
    if($F[0]=~/^\d+$/ && $F[0]>1){$F[2]=$f2{$F[0]}; $F[3]=$f3{$F[0]}}
     print "@F"' file1

Explicação

  • -lane : o l removerá automaticamente as novas linhas finais do final de cada linha de entrada (o mesmo que chomp ) e adicionará uma nova linha a cada instrução print . O a dividirá automaticamente cada linha de entrada no espaço em branco na matriz @F , fazendo com que o perl seja executado como awk. O n significa "executar o script fornecido por -e em cada linha do arquivo de entrada.
  • BEGIN{...} : isso é executado antes que o arquivo de entrada seja lido. Nesse caso, estou abrindo cada um dos arquivos extras e salvando o conteúdo deles nos hashes %f2 e %f3 . Isso é basicamente o mesmo que os arrays awk que eu usei acima.
  • if($F[0]=~/^\d+$/ && $F[0]>1){...} : novamente, essa é a mesma lógica do script awk. Ele substituirá os campos pelas entradas correspondentes de cada arquivo.
  • print "@F" : isso imprimirá todos os campos.
por 08.09.2014 / 14:32
1
{ { paste -d\  /dev/fd/[345] | 
    sed 's/ \( [^ ]*\)\(.*\)//'
} 3<<FILE1 4<<FILE2 5<<FILE3
$(<file1 sed '1,/^1/w /dev/fd/2
      /^2/,$!d;s/ [^ ]*//4;s// /3')
FILE1
$(<file2 tr -s \n)
FILE2
$(<file3 tr -s \n)
FILE3
} 2>&1

Na sequência de comandos acima, faço uma boa quantidade de malabarismo. É muito simplesmente feito. file[23] são realmente idênticos - ambos são uma cópia das suas 99,999 linhas de linhas 3/4. Isso deixa file1 - é essencialmente o arquivo exato em seu exemplo acima, mas a linha 5 é duplicada em 6 e 7 para corresponder a file[23] .

Basicamente, cada arquivo só recebe seu próprio descritor de arquivo e seu próprio bit de trabalho preparatório. file[23] quase não prepara - tr apenas comprime todos os caracteres% ewline \n em um - assim as linhas em branco desaparecem.

file1 fica um pouco mais. Primeiro, todas as linhas até e incluindo a primeira linha que começa com 1 são gravadas em stderr . Eles são os próximos excluídos da saída - então eles só saem para >&2 . Próximo sed seleciona cols 3/4 e os substitui por um único espaço - o que significa que onde eles estavam agora há dois caracteres de espaço consecutivos.

paste reúne todos os descritores de arquivo e coloca todos juntos, separados por espaços. Então sed tenta trocar a primeira seqüência de caracteres não espaciais imediatamente após dois caracteres espaciais com tudo depois disso.

Por último, os descritores de arquivo para stderr e stdout são unidos em stdout . O resultado é assim:

OUTPUT

$data This is the experimental data

good data

This is good file

datafile

1 4324 3673 6.2e+11 7687 67576
2 3565 8768 12414 12414 8778
3 7656 8793 12421 12421 79909
4 8768 8965 36347 36347 0970
5 5878 9879 3.4e+3 3.4e+3 0709799
6 5878 9879 -3.5e22 -3.5e22 0709799
7 5878 9879 987983 987983 0709799
. . .
. . .
. . .
100000 3655 6868 87698 87698 69899
$.endfile 
    
por 08.09.2014 / 21:13
0

aqui está o que eu criei.

seus dados estão em a.txt, a terceira coluna está em b.txt (eu coloquei o nome do dia da semana para maior clareza, isso funcionará bem com o número).

mybox $ cat b.txt
day monday tuesday wednesday thursday friday saturday
mybox $ cat a.txt
1 4324 3673 6.2e+11 7687 67576
2 3565 8768 8760 5780 8778
3 7656 8793 -3e+11 7099 79909
4 8768 8965 8769 9879 0970
5 5878 9879 7.970e-1 9070 0709799
100000 3655 6868 97879 96879 69899
mybox $ cat ul.awk
FILENAME == "b.txt" { for (i=2;i<=NF;i++) value_one[i-1]=$i ; next ; }
 {printf "%s %s %s %s %s %s\n",$1,$2,value_one[FNR],$4,$5,$6}

mybox $ awk -f ul.awk b.txt a.txt
1 4324 monday 6.2e+11 7687 67576
2 3565 tuesday 8760 5780 8778
3 7656 wednesday -3e+11 7099 79909
4 8768 thursday 8769 9879 0970
5 5878 friday 7.970e-1 9070 0709799
100000 3655 saturday 97879 96879 69899

no entanto, não tenho certeza se o awk consegue lidar com 99999 colunas .

Isso é o que você está procurando? (além de mesclar apenas um arquivo)

edite 1 coluna simples b.txt (a propósito, elimine qualquer problema no awk).

mybox $ cat a.txt
1 4324 3673 6.2e+11 7687 67576
2 3565 8768 8760 5780 8778
3 7656 8793 -3e+11 7099 79909
4 8768 8965 8769 9879 0970
5 5878 9879 7.970e-1 9070 0709799
100000 3655 6868 97879 96879 69899
mybox $ cat b.txt
monday
tuesday
wednesday
thursday
friday
saturday

conteúdo de ul.awk

FILENAME == "b.txt" { value[FNR]=$i ; }
FILENAME != "b.txt" { printf "%s %s %s %s %s %s\n",$1,$2,value[FNR],$4,$5,$6}

mybox $ awk -f ul.awk b.txt a.txt
1 4324 monday  6.2e+11 7687 67576
2 3565 tuesday  8760 5780 8778
3 7656 wednesday  -3e+11 7099 79909
4 8768 thursday  8769 9879 0970
5 5878 friday  7.970e-1 9070 0709799
100000 3655 saturday 97879 96879 69899

está se aproximando?

    
por 08.09.2014 / 13:50
0

Outra maneira sem arrays, é uma bagunça, então vou tentar limpar depois

awk 'function get(file,L) {x=1
        while ( (getline < file) > 0) {if(NR==x)y=$0;x++}
        close(file)
        return y
        }
     ARGV[1]==FILENAME{d=$0;a=get(ARGV[2],$0);b=get(ARGV[3],$0);$0=d;$2=a;$3=b;print
     }' file file1 file2
    
por 08.09.2014 / 16:55