Mesclar dois arquivos e mais a segunda coluna usando AWK

0

Eu gostaria de mesclar dois arquivos e mais a segunda coluna de dois arquivos.

arquivo1.

001    A
002    B
003    C
004    D

arquivo2.

002    D
003    D
005    E
006    F

deve ser mesclado no arquivo 3, conforme abaixo.

001    A
002    BD
003    CD
004    D
005    E
006    F

Eu corri o seguinte comando, mas a saída não estava correta

$ awk 'FNR==NR{a[$1]=$2; next}{print $1, a[$1]$2}' file1 file2
002    BD
003    CD
005    E
006    F

Por favor, ajude como eu posso mesclar usar awk ou sed.

    
por jimmy.zhang 10.12.2016 / 21:48

2 respostas

2

Junção Externa Esquerda / Direita vs. Junção Externa Completa

O problema com o comando que você está usando é que requer que todas as entradas no arquivo2 estejam no arquivo1. A seção de impressão da instrução está avaliando apenas as entradas que estão no arquivo2.

FNR e NR são frequentemente usados dessa maneira para JOIN com base em uma semelhança - escolhendo todos os registros de um conjunto de dados e apenas os registros relacionados do outro conjunto de dados. O que você implementou é especificamente um 'RIGHT OUTER JOIN', já que ele irá inserir todas as entradas do lado 'direito' - file2 e membros correspondentes do lado esquerdo, file1.

Em vez disso, você deseja conduzir um 'FULL OUTER JOIN'. Todos os registros em ambos os arquivos com uma mesclagem de registros com base na coluna 1.

FNR==NR significa que o número de registros 'FILE' ( FNR ) é igual ao número total de registros ( NR ). NR será incrementado para cada linha de qualquer arquivo processado, enquanto FNR será redefinido para 0 ao iniciar um novo arquivo. Portanto, FNR==NR só é verdadeiro ao importar 2+ arquivos durante a leitura no primeiro arquivo. Quando o awk muda para o próximo arquivo, o FNR irá resetar para 0, enquanto o NR continuará a crescer.

Para ilustrar isso, inseri uma instrução print para fornecer o estado dessas variáveis conforme o awk processa a entrada:

$> awk 'FNR==NR{a[$1]=$2;printf("File: %s, NR: %s, FNR: %s, $1: %s, $2: %s, a[$1]: %s\n",FILENAME,NR,FNR,$1,$2,a[$1]); next}  {printf("File: %s, NR: %s, FNR: %s, $1: %s, $2: %s, a[$1]$2: %s\n",FILENAME,NR,FNR,$1,$2,a[$1]$2); }' file1 file2

File: file1, NR: 1, FNR: 1, $1: 001, $2: A, a[$1]: A
File: file1, NR: 2, FNR: 2, $1: 002, $2: B, a[$1]: B
File: file1, NR: 3, FNR: 3, $1: 003, $2: C, a[$1]: C
File: file1, NR: 4, FNR: 4, $1: 004, $2: D, a[$1]: D
File: file2, NR: 5, FNR: 1, $1: 002, $2: D, a[$1]$2: BD
File: file2, NR: 6, FNR: 2, $1: 003, $2: D, a[$1]$2: CD
File: file2, NR: 7, FNR: 3, $1: 005, $2: E, a[$1]$2: E
File: file2, NR: 8, FNR: 4, $1: 006, $2: F, a[$1]$2: F

Solução

Para corrigi-lo, tudo o que você precisa fazer é continuar adicionando entradas ao array enquanto processa o arquivo2, e somente envia os resultados após processar todos os arquivos de entrada.

Portanto, nesse caso, não nos importamos com NR ou FNR .

  • Para cada linha de texto de todos os arquivos de entrada, use seu valor de coluna um $1 como um índice em uma matriz a[$1]

  • Atribua o valor da coluna 2, $2 , à matriz nesse índice, mas acrescente o valor para não sobrescrever um valor que já exista: a[$1]=a[$1]$2

  • Aguarde até que todos os registros / linhas tenham sido processados antes de imprimir o array:

    for (i in a) { printf("%s\t%s\n", i, a[i]) }

A única desvantagem é que o awk usa matrizes associativas que usam índices baseados em strings, não integer (é por isso que isso funciona), mas um efeito colateral disso é que a ordem das entradas na matriz pode não ser como pretendido; neste caso, a impressão do conteúdo não está em ordem numérica (por índice), de modo que a saída da pipeta para classificar é necessária:

$> awk '{ a[$1]=a[$1]$2; next } END { for (i in a) { printf("%s\t%s\n", i, a[i]) } }' file1 file2 | sort -n 

001     A
002     BD
003     CD
004     D
005     E
006     F

Abordagem alternativa

Você também pode usar o comando join para fazer isso, mas eu não sei como combiná-los - eles permanecem delimitados por espaço, então um estágio adicional de processamento é necessário:

$> join  -o 0,1.2,2.2 -a1 -a2 file1 file2 | awk '{printf("%s\t%s%s\n", $1, $2, $3)}'
001     A
002     BD
003     CD
004     D
005     E
006     F

TODO

Isso não faz nada para rejeitar entradas duplicadas - que podem ou não ser desejadas. Atualmente, se você tiver registros duplicados em arquivos de entrada separados, eles serão combinados:

file1: 001 A
e file2: 001 A
 resultará em um registro de saída de 001 AA

    
por 11.12.2016 / 21:53
0

Agora que vi um esforço de pesquisa sua, apresento minha solução:

awk '{ z[$1]=z[$1]$2 } END { for (i in z) print i, z[i] }' file1  file2

A saída é:

002 BD
003 CD
004 D
005 E
006 F 
001 A

Se você quiser fazer uma classificação numérica nos valores da primeira coluna, pode passar a saída do comando anterior para sort :

awk '{ z[$1]=z[$1]$2 } END { for (i in z) print i, z[i] }' file1  file2 \
| sort -n -k1
    
por 11.12.2016 / 21:33

Tags