La meilleure façon de simuler "group by" de bash?

supposons que vous ayez un fichier qui contient des adresses IP, une adresse dans chaque ligne:

10.0.10.1
10.0.10.1
10.0.10.3
10.0.10.2
10.0.10.1

vous avez besoin d'un script shell qui compte pour chaque adresse IP combien de fois il apparaît dans le fichier. Pour l'entrée précédente vous avez besoin de la sortie suivante:

10.0.10.1 3
10.0.10.2 1
10.0.10.3 1

Une façon de le faire est:

cat ip_addresses |uniq |while read ip
do
    echo -n $ip" "
    grep -c $ip ip_addresses
done

mais il est vraiment loin d'être efficace.

comment résoudre ce problème utiliser plus efficacement bash?

(une chose à ajouter: je sais qu'il peut être résolu de perl ou awk, je suis intéressé par une meilleure solution en bash, pas dans ces langues.)

INFORMATIONS SUPPLÉMENTAIRES:

supposons que le fichier source est de 5 Go et que la machine qui exécute l'algorithme a 4 Go. Si le tri n'est pas une solution efficace, ni de la lecture du fichier plusieurs fois.

j'ai aimé la table de hachage comme solution - n'importe qui peuvent apporter des améliorations à cette solution?

INFORMATION SUPPLÉMENTAIRE #2:

certaines personnes ont demandé pourquoi je me donnerais la peine de le faire à bash quand il est beaucoup plus facile dans perl par exemple. La raison est que sur la machine, je devais faire ce perl n'était pas disponible pour moi. C'était une machine linux construite sur mesure sans la plupart des outils auxquels je suis habitué. Et je pense que c'était un problème intéressant.

alors s'il vous plaît, ne blâmez pas la question, ignorez-la si vous ne le faites pas plaire. :- )

188
demandé sur Luke Girvin 2008-12-19 15:13:58

14 réponses

sort ip_addresses | uniq -c

cela affichera le compte en premier, mais à part cela, il devrait être exactement ce que vous voulez.

330
répondu Joachim Sauer 2008-12-19 12:22:35

la méthode rapide et sale est la suivante:

cat ip_addresses | sort -n | uniq -c

si vous avez besoin d'utiliser les valeurs de bash, vous pouvez affecter l'ensemble de la commande à une variable de bash et ensuite boucler les résultats.

PS

si la commande sort est omise, vous n'obtiendrez pas les bons résultats car uniq ne regarde que des lignes identiques successives.

41
répondu Francois Wolmarans 2011-10-31 16:43:33

la solution canonique est celle mentionnée par un autre répondant:

sort | uniq -c

il est plus court et plus concis que ce qui peut être écrit en Perl ou en awk.

vous écrivez que vous ne voulez pas utiliser sort, parce que la taille des données est plus grande que la taille de la mémoire principale de la machine. Ne sous-estimez pas la qualité d'implémentation de la commande unix sort. Le tri a été utilisé pour traiter de très grands volumes de données (pensez à la facturation originale D'AT & T données) sur des machines avec 128k (soit 131.072 octets) de mémoire (PDP-11). Quand sort rencontre plus de données qu'une limite prédéfinie (souvent accordée près de la taille de la mémoire principale de la machine) il trie les données qu'il a lues dans la mémoire principale et les écrit dans un fichier temporaire. Il répète ensuite l'action avec les prochains morceaux de données. Enfin, il effectue un tri de fusion sur ces fichiers intermédiaires. Cela permet à sort de travailler sur des données beaucoup plus grandes que la mémoire principale de la machine.

19
répondu Diomidis Spinellis 2008-12-20 16:02:55

pour résumer plusieurs champs, en se basant sur un groupe de champs existants, utiliser l'exemple ci-dessous:$1, $2, $3, $4 selon vos besoins)

cat file

US|A|1000|2000
US|B|1000|2000
US|C|1000|2000
UK|1|1000|2000
UK|1|1000|2000
UK|1|1000|2000

awk 'BEGIN { FS=OFS=SUBSEP="|"}{arr[,]+=+ }END {for (i in arr) print i,arr[i]}' file

US|A|3000
US|B|3000
US|C|3000
UK|1|9000
13
répondu Anonymous 2014-03-31 09:51:28
cat ip_addresses | sort | uniq -c | sort -nr | awk '{print  " " }'

cette commande vous donnerait la sortie désirée

7
répondu zjor 2014-07-25 22:28:45

il semble que vous devez soit utiliser une grande quantité de code pour simuler des hachures dans bash pour obtenir le comportement linéaire ou s'en tenir à la quadratique les versions superlinéaires.

Parmi ces versions, saua 's est la meilleure solution (et la plus simple):

sort -n ip_addresses.txt | uniq -c

j'ai trouvé http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-11/0118.html . Mais c'est moche comme l'enfer...

4
répondu Vinko Vrsalovic 2017-05-23 12:34:37
"151910920, Vous pouvez utiliser le système de fichiers lui-même comme une table de hachage. Pseudo-code comme suit:

for every entry in the ip address file; do
  let addr denote the ip address;

  if file "addr" does not exist; then
    create file "addr";
    write a number "0" in the file;
  else 
    read the number from "addr";
    increase the number by 1 and write it back;
  fi
done

à la fin, tout ce que vous avez à faire est de parcourir tous les fichiers et d'imprimer les noms de fichiers et les numéros qu'ils contiennent. Alternativement, au lieu de garder un compte, vous pourriez ajouter un espace ou une nouvelle ligne à chaque fois au fichier, et à la fin il suffit de regarder la taille du fichier en octets.

3
répondu PolyThinker 2008-12-20 15:30:34

Solution (group by like mysql)

grep -ioh "facebook\|xing\|linkedin\|googleplus" access-log.txt | sort | uniq -c | sort -n

résultat

3249  googleplus
4211 linkedin
5212 xing
7928 facebook
2
répondu kairouan2020 2014-02-14 09:08:38

je me sens awk tableau associatif est également très pratique dans ce cas

$ awk '{count[]++}END{for(j in count) print j,count[j]}' ips.txt

Un groupe par la poste ici

2
répondu SriniV 2014-03-31 09:51:06

je comprends que vous recherchez quelque chose à Bash, mais au cas où quelqu'un d'autre pourrait être à la recherche de quelque chose en Python, vous pourriez vouloir considérer ceci:

mySet = set()
for line in open("ip_address_file.txt"):
     line = line.rstrip()
     mySet.add(line)

comme valeurs dans l'ensemble sont uniques par défaut et Python est assez bon à ce truc, vous pourriez gagner quelque chose ici. Je n'ai pas testé le code, il est peut-être sur écoute, mais ça pourrait t'y mener. Et si vous voulez compter les occurrences, utiliser un dict au lieu d'un ensemble est facile à mettre en œuvre.

Edit: Je lis mal, alors j'ai mal répondu. Voici un extrait avec une dict qui compterait les occurences.

mydict = {}
for line in open("ip_address_file.txt"):
    line = line.rstrip()
    if line in mydict:
        mydict[line] += 1
    else:
        mydict[line] = 1

le dictionnaire mydict contient maintenant une liste d'IP uniques en tant que clés et le nombre de fois où elles sont apparues en tant que leurs valeurs.

1
répondu wzzrd 2008-12-20 17:17:17

Pure (pas de fourche!)

il y a un moyen, en utilisant un fonction . Ce moyen est très rapide car il n'y a pas de fourchette!...

... Alors que le groupe de adresses ip rester petit !

countIp () { 
    local -a _ips=(); local _a
    while IFS=. read -a _a ;do
        ((_ips[_a<<24|${_a[1]}<<16|${_a[2]}<<8|${_a[3]}]++))
    done
    for _a in ${!_ips[@]} ;do
        printf "%.16s %4d\n" \
          $(($_a>>24)).$(($_a>>16&255)).$(($_a>>8&255)).$(($_a&255)) ${_ips[_a]}
    done
}

Note: les adresses IP sont converties en une valeur entière non signée de 32bits, utilisée comme index pour tableau . Cette utilisation simple bash tableaux , pas tableau associatif (qui est plus cher)!

time countIp < ip_addresses 
10.0.10.1    3
10.0.10.2    1
10.0.10.3    1
real    0m0.001s
user    0m0.004s
sys     0m0.000s

time sort ip_addresses | uniq -c
      3 10.0.10.1
      1 10.0.10.2
      1 10.0.10.3
real    0m0.010s
user    0m0.000s
sys     0m0.000s

sur mon hôte, le faire est beaucoup plus rapide que d'utiliser des fourches, jusqu'à environ 1'000 adresses, mais prendre environ 1 seconde entière quand je vais essayer de tri'n count 10'000 adresses.

1
répondu F. Hauri 2018-02-18 12:38:08

Je l'aurais fait comme ça:

perl -e 'while (<>) {chop; $h{$_}++;} for $k (keys %h) {print "$k $h{$k}\n";}' ip_addresses

mais uniq pourrait travailler pour vous.

0
répondu nicerobot 2008-12-20 00:30:51

la plupart des autres solutions comptent les doublons. Si vous avez vraiment besoin de grouper des paires de valeurs clés, essayez ceci:

Voici mon exemple de données:

find . | xargs md5sum
fe4ab8e15432161f452e345ff30c68b0 a.txt
30c68b02161e15435ff52e34f4fe4ab8 b.txt
30c68b02161e15435ff52e34f4fe4ab8 c.txt
fe4ab8e15432161f452e345ff30c68b0 d.txt
fe4ab8e15432161f452e345ff30c68b0 e.txt

cette option permet d'afficher les paires de valeurs clés groupées par le total de contrôle md5.

cat table.txt | awk '{print }' | sort | uniq  | xargs -i grep {} table.txt
30c68b02161e15435ff52e34f4fe4ab8 b.txt
30c68b02161e15435ff52e34f4fe4ab8 c.txt
fe4ab8e15432161f452e345ff30c68b0 a.txt
fe4ab8e15432161f452e345ff30c68b0 d.txt
fe4ab8e15432161f452e345ff30c68b0 e.txt
0
répondu Aron Curzon 2015-11-11 21:02:38

Tri peut être omis si l'ordre n'est pas significatif

uniq -c <source_file>

ou

echo "$list" | uniq -c

si la liste des sources est une variable

-7
répondu Sudden Def 2008-12-19 12:28:01