Pobieranie liczby unikatowych wartości w kolumnie w bash

98

Mam pliki rozdzielane tabulatorami z kilkoma kolumnami. Chcę policzyć częstotliwość występowania różnych wartości w kolumnie dla wszystkich plików w folderze i posortować je w kolejności malejącej (najpierw od największej liczby). Jak mógłbym to osiągnąć w środowisku wiersza poleceń Linuksa?

Może używać dowolnego popularnego języka linii poleceń, takiego jak awk, perl, python itp.

sfactor
źródło

Odpowiedzi:

155

Aby zobaczyć liczbę częstotliwości w drugiej kolumnie (na przykład):

awk -F '\t' '{print $2}' * | sort | uniq -c | sort -nr

plikA.txt

z    z    a
a    b    c
w    d    e

plikB.txt

t    r    e
z    d    a
a    g    c

plikC.txt

z    r    a
v    d    c
a    m    c

Wynik:

  3 d
  2 r
  1 z
  1 m
  1 g
  1 b
Wstrzymano do odwołania.
źródło
69

Oto sposób na zrobienie tego w powłoce:

FIELD=2
cut -f $FIELD * | sort| uniq -c |sort -nr

W tym bash jest świetny.

Thedward
źródło
23
Coś takiego… ar ar ar! :)
John Rix
3
Kinda sorta unique thingy. : P (przy okazji używaj -d,do oddzielania pól przecinkami lub innymi ogranicznikami).
cprn
4
Użyłem cut -f 1 -d ' '. Dziękuję bardzo. :)
Alfonso Nishikawa,
8

Witryna GNU proponuje ten fajny skrypt awk, który wypisuje zarówno słowa, jak i ich częstotliwość.

Możliwe zmiany:

  • Możesz przejść przez potok sort -nr(i odwrócić wordi freq[word]), aby zobaczyć wynik w porządku malejącym.
  • Jeśli chcesz mieć konkretną kolumnę, możesz pominąć pętlę for i po prostu napisać freq[3]++- zamień 3 na numer kolumny.

Tutaj idzie:

 # wordfreq.awk --- print list of word frequencies

 {
     $0 = tolower($0)    # remove case distinctions
     # remove punctuation
     gsub(/[^[:alnum:]_[:blank:]]/, "", $0)
     for (i = 1; i <= NF; i++)
         freq[$i]++
 }

 END {
     for (word in freq)
         printf "%s\t%d\n", word, freq[word]
 }
Adam Matan
źródło
2
Świetny przykładowy skrypt. Pokazuje tak wiele możliwości awk.
David Mann,
Ten skrypt był dla mnie pomocny w określeniu, na które wiersze w skoroszycie programu Excel naprawdę musiałem zwrócić uwagę :) (skopiowałem zawartość Excela do pliku tekstowego, użyj awk i, voila !, mogę utworzyć plik wzorca dla grep -n) .
Jubbles
6

Perl

Ten kod oblicza wystąpienia wszystkich kolumn i wyświetla posortowany raport dla każdej z nich:

# columnvalues.pl
while (<>) {
    @Fields = split /\s+/;
    for $i ( 0 .. $#Fields ) {
        $result[$i]{$Fields[$i]}++
    };
}
for $j ( 0 .. $#result ) {
    print "column $j:\n";
    @values = keys %{$result[$j]};
    @sorted = sort { $result[$j]{$b} <=> $result[$j]{$a}  ||  $a cmp $b } @values;
    for $k ( @sorted ) {
        print " $k $result[$j]{$k}\n"
    }
}

Zapisz tekst jako columnvalues.pl
Uruchom go jako: perl columnvalues.pl files*

Wyjaśnienie

W pętli while na najwyższym poziomie:
* Pętla nad każdym wierszem połączonych plików wejściowych
* Podziel wiersz na tablicę @Fields
* Dla każdej kolumny zwiększ wynikową strukturę danych tablicy skrótów

Na najwyższym poziomie pętli for:
* Pętla nad tablicą wyników
* Wydrukuj numer kolumny
* Pobierz wartości użyte w tej kolumnie
* Sortuj wartości według liczby wystąpień
* Drugorzędne sortowanie na podstawie wartości (na przykład b vs g vs m vs z)
* Iteruj przez skrót wyniku, używając posortowanej listy
* Wydrukuj wartość i liczbę każdego wystąpienia

Wyniki oparte na przykładowych plikach wejściowych dostarczonych przez @Dennis

column 0:
 a 3
 z 3
 t 1
 v 1
 w 1
column 1:
 d 3
 r 2
 b 1
 g 1
 m 1
 z 1
column 2:
 c 4
 a 3
 e 2

.csv wejście

Jeśli pliki wejściowe to .csv, zmień /\s+/na/,/

Maskowanie

W brzydkim konkursie Perl jest szczególnie dobrze wyposażony.
Ta jedna linijka robi to samo:

perl -lane 'for $i (0..$#F){$g[$i]{$F[$i]}++};END{for $j (0..$#g){print "$j:";for $k (sort{$g[$j]{$b}<=>$g[$j]{$a}||$a cmp $b} keys %{$g[$j]}){print " $k $g[$j]{$k}"}}}' files*
Chris Koknat
źródło
2

Rubin (1,9+)

#!/usr/bin/env ruby
Dir["*"].each do |file|
    h=Hash.new(0)
    open(file).each do |row|
        row.chomp.split("\t").each do |w|
            h[ w ] += 1
        end
    end
    h.sort{|a,b| b[1]<=>a[1] }.each{|x,y| print "#{x}:#{y}\n" }
end
kurumi
źródło
5
Jest to bardzo interesujące, zarówno dlatego, że go użyłem i zadziałało, jak i dlatego, że jestem po prostu zdumiony tym, jak brzydki jest rubin ... Myślałem, że perl jest zły!
ryansstack
W obronie Rubiego można to naprawdę uporządkować. Na przykład, używając each_with_objectmiędzy innymi. Krótko mówiąc, jest to nieco rażąco napisane.
Rambatino