gawk junte dois TSV por colunas (a'la sql join)

2

Como ingressar em arquivos tsv, exemplos:

a.tsv

c   7   r   z
d   6   s   w
f   1   f   f
b   8   p   y
a   9   q   x

b.tsv

a   q   a
c   r   ccc
b   p   bb
0   0   0
d   s   dddd

Aqui eu gostaria de "associá-los" por colunas, where a$1,a$3==b$1,b$2 e exibição restante (a $ 2, a $ 4, b $ 3):

6   w   dddd
9   x   a
8   y   bb
7   z   ccc

A pergunta é: como você faria isso com o gawk?

A ordem das linhas não mede (na saída. Na entrada, a ordem das linhas não é definida e pode ser diferente nas linhas a.tsv e b.tsv - como no banco de dados relacional, elas têm sem ordem).

Nota de exclusividade : Originalmente, assumi "a exclusividade de key={a$1,a$3} . Como glenn jackman notado - não pode ser assumido a partir da declaração original do problema, pois permite linhas não únicas de acordo com qualquer tecla - obrigado glenn .

    
por Grzegorz Wierzowiecki 24.10.2011 / 14:44

4 respostas

6

Parece que o comando join só pode ingressar em um campo [ 1 , 2 ], então:

awk '
    BEGIN {FS=OFS="\t"}
    NR==FNR {a[$1 FS $3] = $2 FS $4; next}
    $1 FS $2 in a {print a[$1 FS $2], $3}
' a.tsv b.tsv

Atualização devido ao comentário: como a chave fornecida não é única, aqui está uma técnica para criar várias entradas de "a.tsv"

awk '
    BEGIN {FS=OFS="\t"}
    NR==FNR {
        key = $1 FS $3
        if (key in a)
            a[key] = a[key] "\n" $2 FS $4
        else
            a[key] = $2 FS $4
        next
    }
    $1 FS $2 in a {
        split(a[$1 FS $2], ary, /\n/)
        for (idx in ary)
            print ary[idx], $3
    }
' a.tsv b.tsv
    
por 24.10.2011 / 15:41
3

Eu dividi a tarefa em dois programas diferentes:

  1. Use join (1) para juntar os dois arquivos

  2. awk (1) ou cut (1) para remover colunas indesejadas

por 24.10.2011 / 14:49
2

Eu não sei muito bem o awk , mas ele foi projetado especificamente para manipular campos em arquivos de texto, então presumo que possa fazer o trabalho muito bem, mas porque você pareceu (?) expressar interesse em join (nos comentários do ktf ), aqui está uma solução usando ferramentas unix padrão: join e cut e paste e sort - muitos "ands", mas funciona e pode servir como um exemplo do porquê awk é melhor:) ... Eu o apresentei principalmente para o fator de comparação de métodos.

join -t $'\t' -o 1.2 1.3  2.2  \
 <(paste <(paste <(cut -f1 a.tsv) \
                 <(cut -f3 a.tsv) \
                 | tr '\t' '
join -t $'\t' -o 1.2 1.3  2.2  \
 <(paste <(paste <(cut -f1 a.tsv) \
                 <(cut -f3 a.tsv) \
                 | tr '\t' '%pre%' ) \
         <(cut -f2 a.tsv) \
         <(cut -f4 a.tsv) \
         | sort ) \
 <(paste <(paste <(cut -f1 b.tsv) \
                 <(cut -f2 b.tsv) \
                 | tr '\t' '%pre%' ) \
         <(cut -f3 b.tsv) \
         | sort ) 
' ) \ <(cut -f2 a.tsv) \ <(cut -f4 a.tsv) \ | sort ) \ <(paste <(paste <(cut -f1 b.tsv) \ <(cut -f2 b.tsv) \ | tr '\t' '%pre%' ) \ <(cut -f3 b.tsv) \ | sort )
    
por 24.10.2011 / 20:52
1

Finalmente consegui fazer isso. Então, compartilho minha solução:

awk '
BEGIN {
    FS=OFS="\t"
    while ((getline < "a.tsv") > 0){
        a2[$1,$3] = $2; a4[$1,$3] = $4
    }
}
($1,$2) in a2 { print a2[$1,$2] FS a4[$1,$2] FS $3 }' < b.tsv

produz:

9   x   a
7   z   ccc
8   y   bb
6   w   dddd

Esta solução:

  • não assume a ordem das linhas de entrada
  • funciona quando algumas linhas não têm correspondência em outro arquivo
  • suponha que as linhas em a.tsv sejam exclusivas de acordo com key = {a$1,a$3}

Para os interessados em right join , basta excluir a instrução if( ($1,$2) in a2) . Para aqueles interessados em left join , apenas faça a versão "right join" e troque a.tsv por b.tsv (e altere o código de acordo).

Nota de exclusividade : Como glenn jackman notou que as linhas de a.tsv podem não ser únicas de acordo com key={a$1,a$3} , você pode querer verificar a solução dele.

    
por 25.10.2011 / 10:06

Tags