Unindo entradas baseadas na coluna usando awk / join

3

Eu tenho dois arquivos que são delimitados por pipe e podem ter correspondências de coluna 1 + coluna2 em ambos, ou um arquivo pode ter a entrada enquanto o outro não. Suponha que minha chave de partida eu estou saindo de igual a $ 1 "-" $ 2 usando um pipe '|' como o FS.

arquivo1

1111|AAA|foo|50
1111|BBB|foo|30
2222|BBB|foo|10

arquivo2

1111|AAA|bar|10
1111|CCC|bar|20
3333|AAA|bar|40

A saída desejada seria a seguinte para a primeira entrada (estou trabalhando)

1111|AAA|50|10

Para o segundo arquivo de entrada1 (Se não houver correspondência de coluna1 + coluna2 em ambos os arquivos, substitua a entrada que está faltando para foo como 0. E o contrário)

1111|BBB|30|0

E para uma chave de entrada (coluna1 + coluna2) no arquivo2, mas não no arquivo1 (Esta é a entrada 3 do arquivo 2 da saída esperada)

3333|AAA|0|40

Portanto, o formato geral de saída desejado está listando TODAS as chaves exclusivas que são representadas por coluna1 + coluna2 em AMBOS os arquivos. Com as entradas da terceira coluna sendo esses valores da coluna 1 do arquivo 1 (ou 0 se o valor não existir no arquivo1) e da quarta coluna na saída como aqueles valores na coluna 4 do arquivo 2 (ou 0 se o valor não existir no arquivo2 ).

Eu fiz muita pesquisa e tentei muitas coisas, mas tenho valores que não saem se o par column1 + column2 existir no arquivo2, mas não no arquivo1, usando o seguinte:

join -t"|" -e0 -a1 -a2 -o 1.2,1.3,1.5,2.5 <(<file1 awk -F"|" '{print $1"-"$2"|"$0}' | sort -k1,1) <(<file2 awk -F"|" '{print $1"-"$2"|"$0}' | sort -k1,1)

O caso acima me dá a saída esperada se houver uma correspondência coluna1 + coluna2 no arquivo1, mas não no arquivo2, e acrescentar um 0 para a correspondência não existente ... Como posso fazer isso funcionar para TODOS os cenários?

O comando acima fará alguma substituição de processo adicionando uma chave na coluna 1 em ambos os arquivos, que é column1 + column2, e depois unirá com base nessa nova chave. -e0 adicionará um 0 se essa chave existir no arquivo1, mas não no arquivo2. Como posso obtê-lo para cobrir o caso de: Nova chave (column1-column2) existe no arquivo 2 mas NÃO no arquivo 1?

    
por EDubman 06.03.2018 / 23:30

3 respostas

3

Com sua abordagem, você precisa usar join duas vezes (ou altere sua abordagem para fazer isso com um único join invocação ):

  • imprima as linhas comuns e as linhas não parciais de file1 com join -t'|' -e0 -a1 -o 1.2,1.3,1.5,2.5 <(<file1 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1) <(<file2 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1)
  • imprima as linhas não parciais de file2 com join -t'|' -e0 -v2 -o 2.2,2.3,1.5,2.5 <(<file1 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1) <(<file2 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1)

Você pode fazer o mesmo com uma única invocação de awk , armazenando $4 em duas matrizes indexadas por, e. $1|$2 e, em seguida, no bloco END iterando sobre os índices de cada matriz, comparando-os e imprimindo de acordo:

awk -F'|' 'NR==FNR{z[$1"|"$2]=$4;next}{x[$1"|"$2]=$4}
END{for (j in x){if (!(j in z)){print j, "0", x[j]}};
for (i in z){if (i in x){print i, z[i], x[i]} else {print i, z[i], "0"}}
}' OFS="|"  file1 file2
    
por 07.03.2018 / 00:24
1

O seguinte substitui o primeiro | nos dois arquivos por @ (use um caractere que não ocorra em outro lugar no arquivo), executa o join e, em seguida, altera o @ de volta para o original | . Dessa forma, criamos um novo campo de junção | -delimited que consiste nas colunas 1 e 2 dos arquivos originais.

join -t'|' -e0 -a1 -a2 -o0,1.3,2.3 \
    <( sed 's/|/@/' file1 | sort )  \
    <( sed 's/|/@/' file2 | sort ) |
tr '@' '|'

Na especificação do campo de saída ( -o ), um zero representa o campo de junção e a coluna 3 em qualquer arquivo é, na verdade, a coluna 4 dos dados originais.

Para os arquivos de entrada fornecidos, isso gera

1111|AAA|50|10
1111|BBB|30|0
1111|CCC|0|20
2222|BBB|10|0
3333|AAA|0|40
    
por 07.03.2018 / 07:40
1

Outra abordagem awk :

awk -F'|' 'NR==FNR{f1[$1FS$2]=$NF;next} {f2[$1FS$2]=$NF} 
    END{for (x in f1){print x,f1[x],f2[x]?f2[x]:0; delete f2[x]};
        for (y in f2) print y, 0, f2[y]
}' file[12] OFS='|'

Explicação:

  • NR==FNR{f1[$1FS$2]=$NF;next} , isso será executado apenas para o arquivo1 e com a combinação de teclas $1FS$2 armazenará o último valor da coluna $NF na matriz chamada f1 ( FS substituirá | como awk F eder S ).
  • {f2[$1FS$2]=$NF} , o mesmo que acima, mas isso será executado apenas para o arquivo2
  • for (x in f1){print x,f1[x],f2[x]?f2[x]:0; delete f2[x]} , loop na matriz f1 e chave de impressão ( x ), seu valor em file1 f1[x] e se houver a mesma chave file1 em file2, imprima também, senão print 0 (condição ternária usada f2[x]?f2[x]:0 ), depois disso, também estamos excluindo o registro da mesma chave do arquivo2 com delete f2[x] .
  • for (y in f2) print y, 0, f2[y] , agora array f2 tem registros que existem apenas no arquivo2, então estamos imprimindo a chave ( y ), 0 porque não existe no arquivo1 e seu valor no arquivo2 f2[y] . / li>
por 07.03.2018 / 07:18