Cole vários arquivos baseados na primeira coluna em um único arquivo

2

Eu tenho vários arquivos que precisam ser mesclados com base na primeira coluna de cada arquivo

Arquivo1:

foo  12  
jhdfeg 25  
kjfdgkl 37

Arquivo 2:

foo 23  
jhdfeg 45

Arquivo 3:

foo 35  
djhf 37  

A saída deve ser assim

        file1 file2  file3  
foo     12    23     35  
jhdfeg  25    45     0  
kjfdgkl 37    0      0  
djhf    0     0      37
    
por Bio_Ram 01.05.2017 / 18:17

4 respostas

1
perl -F'\s+' -lane '

   $. == 1 and @ARGC = ($ARGV, @ARGV);       # initialize the @ARGC array

   exists $h{$F[0]} or $h[keys %h] = $F[0];  # needed to remember order

   $h{$F[0]}->[@ARGC-@ARGV-1] = $F[1];       # populate hash

   END {
      $, = "\t";           # set the OFS to TAB

      print q//, @ARGC;    # print the first line with filenames

      for my $key (@h) {   # print remaninig lines with data
         print $key,
            map { $h{$key}->[$_] // 0 } 0 .. $#ARGC;
      }
   }

' file1 file2 file3 # ... you can give as many files here

Saída

        file1   file2   file3
foo     12      23      35
jhdfeg  25      45      0
kjfdgkl 37      0       0
djhf    0       0       37
    
por 02.05.2017 / 08:36
2
abordagem

awk :

joiner.awk script:

#!/bin/awk -f
BEGIN { 
    f1=ARGV[1]; f2=ARGV[2]; f3=ARGV[3]     # the 1st, 2nd and 3rd file names respectively
    printf("%10s\t%s\t%s\t%s\n", "", f1, f2, f3)   # printing header
}
{ a[$1][FILENAME]=$2 }    # accumulating values
END {
    for (i in a) {
        printf("%-10s\t%d\t%d\t%d\n", i, a[i][f1], a[i][f2], a[i][f3]) 
    }
}

Uso:

awk -f joiner.awk file1 file2 file3

A saída:

          file1 fil2 file3
kjfdgkl     37  0   0
foo         12  23  35
djhf        0   0   37
jhdfeg      25  45  0
    
por 02.05.2017 / 00:25
1

Se você tem 3 arquivos como em seu exemplo, você poderia fazer isso com alguns join magic. Primeiro, escreva os nomes delimitados por tabulações dos arquivos para o arquivo de saída:

for i in File*; do printf "\t%s" "$i" >> RES; done

Adicione uma linha vazia para os resultados reais:

printf '\n' >> RES

Use join em File1 e File2 e redirecione a saída para um arquivo temporário:

join  -a1 -a2  -e0 <(sort File1) -o 0 1.2 2.2 <(sort File2) > TEMP_FILE

E agora use-o novamente com a saída do comando acima e File3 (você também pode usar um pipe ( | ) aqui):

join  -a1 -a2  -e0 <(sort TEMP_FILE) -o 0 1.2 1.3 2.2 <(sort File3) >> RES

E substitua espaços em branco por tabulações em RES :

tr ' ' '\t' < RES > FINAL_RES

Seus resultados estão em FINAL_RES :

$ cat FINAL_RES
        File1   File2   File3
foo     12      23      35
jhdfeg  25      45      0
kjfdgkl 37      0       0
    
por 01.05.2017 / 21:52
1

E aqui uma abordagem mais geral, independente do número de arquivos com sed :

sed '1{x;s/$/_/;x;}
  /foo/{x;s/_/ 0_/g;x;}
  G;s/^\([a-z]*\)  *\([0-9]*\).*\n\(.*_\)\([^_]*\)0//
  s/^\([a-z]*\) *\([0-9]*\).*\n\([^_]*\)0_\(.*\)/__/
  $! {h;d;}
  s/[^_]*_//
  y/_/\n/' file*

Isso depende de cada arquivo que começa com a linha foo , como no seu exemplo.

Dado que você tem conhecimento básico sobre como sed funciona, espaço de padrão e espaço, aqui vem a explicação:

A idéia principal é construir toda a tabela de saída no espaço de armazenamento. Em cada linha, o espaço de espera contém a tabela nesse ponto junto com uma linha de modelo necessária para novas linhas. Usamos _ como separador de linha durante o processamento. E agora, passo a passo:

1{x;s/$/_/;x;}

Isso inicializa o espaço de espera com um único _ como o início da nossa linha de modelos.

/foo/{x;s/_/ 0_/g;x;}

/foo/ endereça linhas que contêm foo , o que indica o início de um novo arquivo. Nesse caso, os comandos em {} são executados: cada linha no espaço de manutenção (linhas reais da tabela e a linha do modelo) obtém 0 anexado. Se, posteriormente, encontrarmos a palavra-chave dessa linha, o 0 será substituído pelo número correto; se a palavra-chave não ocorrer, o 0 permanece.

G;s/^\([a-z]*\) *\([0-9]*\).*\n\(.*_\)\([^_]*\)0//

'G' acrescenta o espaço de espera ao espaço do padrão. O comando s tem quatro \(\) seções: A primeira contém a palavra-chave, a segunda o valor, a terceira após a nova linha (portanto, é a tabela anexada do espaço de espera) até a segunda ocorrência da palavra-chave %código%). O quarto contém tudo nessa linha, excluindo o% final. Portanto, encontramos uma linha já existente com essa palavra-chave e substituímos a 0 . Largamos tudo na nova linha e apenas mantemos a tabela atualizada.

s/^\([a-z]*\) *\([0-9]*\).*\n\([^_]*\)0_\(.*\)/__/

Outra correspondência, incluindo uma nova linha de linha 0 , para sabermos que não encontramos a palavra-chave na tabela (caso contrário, a linha nova teria sido removida antes. Dessa vez, adicionamos uma nova linha ao final. composto da palavra-chave, da linha do modelo e do valor. E esse é o truque da linha do modelo: adicionamos uma coluna \n a ela para cada novo arquivo, por isso, se removermos uma 0 , teremos 0 coluna para cada arquivo em que essa palavra-chave não existia.

$! {h;d;}

Se esta não foi a última linha, mova a tabela modificada de volta para o espaço de espera ( 0 ) e comece de novo ( h ).

s/[^_]*_//

Para a última linha, isso remove a linha do modelo.

y/_/\n/

E isso substitui d por nova linha. Além disso, você também pode substituir espaços por guias, se desejar.

Editar

Se a suposição estiver errada de que cada arquivo começa com a linha _ , precisamos de um método diferente para informar foo quando um novo arquivo for iniciado, como adicionar uma linha extra para cada arquivo iniciar e transmitir tudo para sed :

for file in file*; do
  echo Start of $file
  cat $file
done | sed '1{x;s/$/__/;x;}
  /Start of/{G;s/_/ 0_/g;s/Start of \(.*\)\n\([^_]*\)_\([^_]*\) 0/_ /;x;d;}
  G;l;s/^\([a-z]*\)  *\([0-9]*\).*\n\(.*_\)\([^_]*\)0//
  l;s/^\([a-z]*\) *\([0-9]*\).*\n\([^_]*\)0_\(.*\)/ 0__/
  $! {h;d;}
  s/[^_]*_//
  y/_/\n/'

Esta versão também gera a linha principal da tabela para todos os nomes de arquivos como cabeçalhos de coluna.

    
por 02.05.2017 / 06:10