Como combinar dois arquivos combinando dois parâmetros com awk

Sep 15 2020

Eu tenho um arquivo A.txt(sep = \t):

Cycle   Well    Value   Target
1   A1  5.07368111264623    EC
1   A1  3.06982862746599    FT
1   A1  2.46545646544623    EC

E um segundo arquivo B.txt(sep = \t, a primeira coluna está vazia ):

    Well    Fluor   Target  Content Sample
    A1  Cy5 EC  Unkn-01 2060563935
    A1  Cy5 FT  Unkn-09 2156515156

Quero adicionar a coluna Contentde B.txtem A.txtse ambos Well e Targetforem os mesmos dados dos dois arquivos e enviar o resultado para C.txt(sep = \t):

Cycle   Well    Value   Target  Content
1   A1  5.07368111264623    EC  Unkn-01
1   A1  3.06982862746599    FT  Unkn-09
1   A1  2.46545646544623    EC  Unkn-01

Eu tento coisas como:

awk -F"\t" 'FNR==NR{if (a[$2]) {a[$2]=a[$2] "\t" $7} else {a[$2]=$7}} NR>FNR{split($0,f,"\t"); if (a[f[4]]) $0=$0 "\t" a[f[4]]; print}'

Mas não funcionou. Alguma ideia de como fazer isso?

Precisão:

  • No primeiro arquivo usado como modelo (A.txt), várias linhas com o mesmo Poço e Alvo estão presentes.
  • Em B.txt, há apenas uma linha com a mesma combinação Poço / alvo.
  • Não é possível não ter um padrão correspondente no arquivo A do arquivo B.

Respostas

2 Bodo Sep 15 2020 at 19:31

Primeira solução usando GNU awk ou POSIX awk

Edit: Como Ed Morton escreveu em seu comentário, a resposta original estava errada sobre o que é suportado awkapenas pelo GNU . (O texto na documentação GNU vs. a documentação POSIX é um pouco confuso.)

O que a awkdocumentação GNU chama de Arrays multidimensionais é compatível com POSIX awk. Vejohttps://pubs.opengroup.org/onlinepubs/000095399/utilities/awk.htmle pesquise por "multidimensional" ou SUBSEP. Essas matrizes são, na verdade, unidimensionais.

GNU awktambém suporta Arrays of Arrays que são verdadeiros arrays multidimensionais.

Esta versão do comando requer GNU awk:

awk -F"\t" 'NR == FNR { a[$2][$4] = $5; next } { print $0, a[$2][$4] }' B.txt A.txt > C.txt

A variante compatível com POSIX (*), que deve funcionar com qualquer awké

awk -F"\t" 'NR == FNR { a[$2,$4] = $5; next } { print $0, a[$2,$4] }' B.txt A.txt > C.txt

Ambos imprimem

Cycle   Well    Value   Target 
1   A1  5.07368111264623    EC Unkn-01
1   A1  3.06982862746599    FT Unkn-09
1   A1  2.46545646544623    EC Unkn-01

Os dados do arquivo B.txtsão salvos no array aporque, de acordo com a pergunta, a chave Well / Target é única neste arquivo. Em seguida, esses dados são anexados aos dados do arquivo A.txt.

O separador de campo deve ser especificado explicitamente. Caso contrário, awkiria ignorar colunas / valores vazios.

Esta solução usa números de coluna fixos para identificar as colunas a serem correspondidas ou impressas.

Editar: A seguinte solução de solução que combina explicitamente as expressões de índice com \to separador não tem nenhuma vantagem em comparação com a solução compatível com POSIX (*) mostrada acima.

awk -F"\t" 'NR == FNR { a[$2 "\t" $4] = $5; next } { print $0, a[$2 "\t" $4] }' B.txt A.txt > C.txt

Isso é equivalente a definir SUBSEP = "\t"e usar a sintaxe .a[$2, $4]


Segunda solução usando q

A ferramenta q pode ser usada para realizar consultas de banco de dados em arquivos CSV.

Vejo http://harelba.github.io/q/ ou https://github.com/harelba/q

Esta solução tem um problema com o título da coluna vazia em B.txt. Como solução alternativa, adicionei um título Emptyà linha de cabeçalho deste arquivo.

Então, eu uso estes arquivos:

A.txt

Cycle   Well    Value   Target
1   A1  5.07368111264623    EC
1   A1  3.06982862746599    FT
1   A1  2.46545646544623    EC

B.txt

Empty   Well    Fluor   Target  Content Sample
    A1  Cy5 EC  Unkn-01 2060563935
    A1  Cy5 FT  Unkn-09 2156515156

O comando

q -H -t "select a.Cycle,a.Well,a.Value,a.Target,b.Content from A.txt as a inner join B.txt as b on a.Well=b.Well and a.Target=b.Target"

estampas

1   A1  5.07368111264623    EC  Unkn-01
1   A1  3.06982862746599    FT  Unkn-09
1   A1  2.46545646544623    EC  Unkn-01

Para imprimir o cabeçalho, você pode adicionar um comando printfou echo.

printf "Cycle\tWell\tValue\tTarget\tContent\n" > C.txt
q -H -t "select a.Cycle,a.Well,a.Value,a.Target,b.Content from A.txt as a inner join B.txt as b on a.Well=b.Well and a.Target=b.Target" >> C.txt

Para automatizar a modificação do arquivo, B.txtvocê pode usar

printf "Empty" > B1.txt
cat B.txt >> B1.txt
printf "Cycle\tWell\tValue\tTarget\tContent\n" > C.txt
q -H -t "select a.Cycle,a.Well,a.Value,a.Target,b.Content from A.txt as a inner join B1.txt as b on a.Well=b.Well and a.Target=b.Target" >> C.txt

Esta solução usou colunas nomeadas da linha do cabeçalho para identificar as colunas a serem correspondidas ou impressas.

2 AdminBee Sep 15 2020 at 19:31

Supondo que você tenha GNU awkpara matrizes bidimensionais, o seguinte programa fará a tarefa:

awk -F'\t' 'NR==FNR&&FNR>1{map[$2][$4]=$5}\ NR>FNR{if (FNR==1) {$5="Content"} else {$5=map[$2][$4]}} NR>FNR' B.txt A.txt > C.txt

Primeiro, esse processo B.txtcriará um mapeamento de valores de "Conteúdo" para uma combinação específica de "Poço" e "Alvo". Ao processar A.txtposteriormente (indicado por FNR, o contador de linha por arquivo agora sendo menor do que NRo contador de linha global), o programa procura a combinação específica de "Poço" e "Alvo" na linha atual e substitui o " Conteúdo "valor do mapa criado anteriormente. Ele imprime a saída e depois processa o segundo arquivo (a NR>FNRcondição "perdida" ).