Concatene linhas pela primeira coluna por awk ou sed

12

Como posso usar awkna seguinte situação?

Quero concatenar linhas que começam com a mesma coluna. Apenas a primeira coluna é mantida após a juntar-se (neste caso aaa, www, hhh).

O arquivo pode ser separado por espaço ou tabulação.

Exemplo de entrada:

aaa bbb ccc ddd NULL NULL NULL
aaa NULL NULL NULL NULL NULL NULL
aaa bbb ccc NULL NULL NULL NULL
www yyy hhh NULL NULL NULL NULL
hhh 111 333 yyy ooo hyy uuuioooy
hhh 111 333 yyy ooo hyy NULL

Saída desejada:

aaa bbb ccc ddd NULL NULL NULL NULL NULL NULL NULL NULL NULL bbb ccc NULL NULL NULL NULL
www yyy hhh NULL NULL NULL NULL
hhh 111 333 yyy ooo hyy uuuioooy 111 333 yyy ooo hyy NULL

O pano de fundo disso é que eu quero configurar um banco de dados baseado em arquivo muito simples, em que a primeira coluna seja sempre o identificador da entidade. Todas as linhas baseadas na mesma coluna identificadora são concatenadas.

minúsculo
fonte
1
de onde veio a uuulinha (na saída)?
Saeedn
Desculpe, minha culpa. Eu vou editar.
Minúsculo

Respostas:

8

Para obter as primeiras colunas em cada linha usando o awk, você pode fazer o seguinte:

< testfile awk '{print $1}'
aaa
aaa
aaa
www
hhh
hhh

Essas são suas chaves para o restante das linhas. Portanto, você pode criar uma tabela de hash, usando a primeira coluna como chave e a segunda coluna da linha como valor:

< testfile awk '{table[$1]=table[$1] $2;} END {for (key in table) print key " => " table[key];}'
www => yyy
aaa => bbbNULLbbb
hhh => 111111

Para obter o restante da linha, começando na coluna 2, você precisa coletar todas as colunas:

< testfile awk '{line="";for (i = 2; i <= NF; i++) line = line $i " "; table[$1]=table[$1] line;} END {for (key in table) print key " => " table[key];}'
www => yyy hhh NULL NULL NULL NULL 
aaa => bbb ccc ddd NULL NULL NULL NULL NULL NULL NULL NULL NULL bbb ccc    NULL NULL NULL NULL 
hhh => 111 333 yyy ooo hyy uuuioooy 111 333 yyy ooo hyy NULL 
binfalse
fonte
Oi, sim, ele realmente precisava de decomposição em tabelas de hash. Obrigado!
Minúsculo
2
@ minúsculo - eu estava assumindo que a encomenda precisava ser preservada. Não é esse o caso (esta resposta produz pedidos correspondentes ao mecanismo de hash, não o seu pedido original)?
ire_and_curses
3

Outra pessoa pode responder em awk ou sed, mas uma versão em Python é direta e pode ser útil para você.

#!/usr/bin/env python

input_file = 'input.dat'
in_fh      = open(input_file, 'r')

input_order = []
seen        = {}
for line in in_fh:    
    # Remove the newline character...
    line = line[:-1]

    # Separate the first column from the rest of the line...
    key_col, sep, rest_of_line = line.partition(" ")
    rest_of_line = sep + rest_of_line  

    # If we've seen this key already, concatenate the line...
    if key_col in seen:
        seen[key_col] += rest_of_line
    # ...otherwise, record the ordering, and store the new info
    else:
        input_order.append(key_col)
        seen[key_col] = rest_of_line

in_fh.close()

# Dump the ordered output to stdout
for unique_col in input_order:
    print unique_col + seen[unique_col]
ire_and_curses
fonte
Muito legal. Com minha experiência de zero python eu ainda conseguiu editar script que é preciso primeiro argumento como nome de arquivo de entrada :)
minúscula
2

Esta é uma aplicação mais interessante do coreutils, suspeito que não seja muito eficiente com entrada grande, pois invoca junção para cada linha da entrada.

touch outfile
while read; do
  join -a1 -a2 outfile <(echo $REPLY) > tmp
  mv tmp outfile
done < infile

Para melhorar sua eficiência, salvar outfilee tmpusar um ramdisk pode ajudar.

Editar

Ou sem arquivos temporários:

out=""
while read; do
  out=$(join -a1 -a2 <(echo -n "$out") <(echo -n "$REPLY"))
done < infile

echo "$out"
Thor
fonte
2

E aqui está uma linha PERL:

$ perl -e 'my %h; while(<>){chomp; @a=split(/\s+/); $k=shift(@a); $h{$k}.=join(" ", @a) . " "; } map{$h{$_}=~s/\s*$//; print "$_ $h{$_}\n}keys(%hash);' infile
terdon
fonte