Połącz wiersze według pierwszej kolumny przez awk lub sed

12

Jak mogę użyć awkw następującej sytuacji?

Chcę połączyć wiersze zaczynające się od tej samej kolumny. Jedynie pierwsza kolumna jest utrzymywana po dołączyć (w tym przypadku aaa, www, hhh).

Plik może być rozdzielony spacjami lub tabulatorami.

Przykładowe dane wejściowe:

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

Pożądane wyjście:

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

Tłem tego jest to, że chcę skonfigurować bardzo prostą bazę danych opartą na plikach, w której pierwsza kolumna jest zawsze identyfikatorem encji. Wszystkie wiersze oparte na tej samej kolumnie identyfikatora są konkatenowane.

malutki
źródło
1
skąd uuupochodzi linia (w danych wyjściowych)?
saeedn
Przepraszam moja wina. Zmienię to.
malutki

Odpowiedzi:

8

Aby uzyskać pierwsze kolumny w każdej linii za pomocą awk, możesz wykonać następujące czynności:

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

To są twoje klucze do reszty linii. Możesz więc utworzyć tabelę skrótów, używając pierwszej kolumny jako klucza, a drugiej kolumny linii jako wartości:

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

Aby uzyskać całą resztę wiersza, zaczynając od kolumny 2, musisz zebrać wszystkie kolumny:

< 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
źródło
Cześć, tak, naprawdę potrzebowałem podziału na tabele skrótów. Dziękuję Ci!
malutki
2
@tiny - Zakładałem, że trzeba zachować porządek. Czy tak nie jest (ta odpowiedź tworzy kolejność odpowiadającą mechanizmowi haszowania, a nie twojemu pierwotnemu zamówieniu)?
ire_and_curses
3

Ktoś inny może odpowiedzieć w awk lub sed, ale wersja Python jest prosta i może być dla ciebie pomocna.

#!/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
źródło
Bardzo fajny. Z moim zerowym doświadczeniem w Pythonie udało mi się nawet edytować skrypt, który bierze pierwszy argument jako nazwę pliku wejściowego :)
malutki
2

Jest to bardziej interesujące zastosowanie coreutils, podejrzewam, że nie jest bardzo wydajne przy dużych wejściach, ponieważ wywołuje łączenie dla każdej linii na wejściu.

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

Aby poprawić jego efektywność, pomocne może być oszczędzanie outfilei tmpRAMDISK.

Edytować

Lub bez plików tymczasowych:

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

echo "$out"
Thor
źródło
2

A oto liniowiec 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
źródło