bashから「グループ化」をシミュレートする最良の方法は?


231

IPアドレスを含むファイルがあり、各行に1つのアドレスがあるとします。

10.0.10.1
10.0.10.1
10.0.10.3
10.0.10.2
10.0.10.1

各IPアドレスがファイルに出現する回数をカウントするシェルスクリプトが必要です。上記の入力には、次の出力が必要です。

10.0.10.1 3
10.0.10.2 1
10.0.10.3 1

これを行う1つの方法は次のとおりです。

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

しかし、それは実際には効率的ではありません。

どのようにbashを使用してこの問題をより効率的に解決しますか?

(1つ追加する必要があります。私はそれがperlまたはawkから解決できることを知っています。これらの言語ではなく、bashのより良いソリューションに興味があります。)

追加情報:

ソースファイルが5GBで、アルゴリズムを実行しているマシンが4GBであるとします。したがって、並べ替えは効率的なソリューションではなく、ファイルを複数回読み取ることもありません。

私はハッシュテーブルのようなソリューションが好きでした-誰でもそのソリューションに改善を提供できますか?

追加情報#2:

たとえばperlのほうがずっと簡単なのに、なぜbashを使うのが面倒なのかと尋ねる人もいました。その理由は、私がこのperlを実行しなければならなかったマシンでは、私は利用できなかったからです。これは、私が慣れているほとんどのツールを持たないカスタムビルドのLinuxマシンでした。そして、それは興味深い問題だったと思います。

だから、質問のせいにせず、気に入らなければ無視してください。:-)


私はbashがその仕事には不適切なツールだと思います。Perlはおそらくより良い解決策でしょう。
フランソワ・ウォルマラン2008

回答:


412
sort ip_addresses | uniq -c

これは最初にカウントを出力しますが、それ以外は正確にあなたが望むものでなければなりません。


71
これを「sort -nr」にパイプして、最大数から最小数まで降順でソートできます。iesort ip_addresses | uniq -c | sort -nr
Brad Parks、

15
そしてsort ip_addresses | uniq -c | sort -nr | awk '{ print $2, $1 }'、最初の列でIPアドレスを取得し、2番目の列でカウントします。
Raghu Dodda 2016

ソート部分のもう1つの調整:sort -nr -k1,1
Andrzej Martyna '10

50

迅速かつダーティな方法は次のとおりです。

cat ip_addresses | sort -n | uniq -c

bashの値を使用する必要がある場合は、コマンド全体をbash変数に割り当ててから、結果をループ処理できます。

PS

sortコマンドを省略した場合、uniqは連続する同一の行のみを調べるため、正しい結果が得られません。


効率的には非常に似ていますが、2次の振る舞いがあります
Vinko Vrsalovic '19

二次の意味O(n ^ 2)?? それは確かにソートアルゴリズムに依存します、そのようなbogo-sortを使用することはまずありません。
paxdiablo 2008

まあ、最良のケースでは、O(n log(n))になります。これは、2つのパスよりも劣ります(これは、簡単なハッシュベースの実装で得られるものです)。二次式ではなく「超線形」と言ったほうがいい。
Vinko Vrsalovic 2008

そして、OPが効率を改善するために求めているのは、同じ境界内にあります...
Vinko Vrsalovic 2008

11
uuoc、猫の無用な使用

22

既存のフィールドのグループに基づいて複数のフィールドを合計するには、以下の例を使用します(要件に応じて、$ 1、$ 2、$ 3、$ 4を置き換えます)

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[$1,$2]+=$3+$4 }END {for (i in arr) print i,arr[i]}' file

US|A|3000
US|B|3000
US|C|3000
UK|1|9000

2
+1は、カウントが必要なだけでなく、何をすべきかを示しているため
user829755 '26

1
+1 sortuniqカウントは最も簡単ですが、フィールド値を計算/合計する必要がある場合は役に立ちません。awkの配列構文は非常に強力であり、ここでグループ化するための鍵となります。ありがとう!
odony 2016

1
もう一つ、AWKのことに気を付けるprintように2 ^ 31を超えるint型の値のために使用することがあり、機能は32ビットに64ビット整数をダウンスケールするようだprintf%.0f代わりの形式print
odony

1
数字を追加する代わりに文字列を連結して「グループ化」を探している人はarr[$1,$2]+=$3+$4、たとえばarr[$1,$2]=(arr[$1,$2] $3 "," $4). I needed this to provide a grouped-by-package list of files (two columns only) and used: arr [$ 1] =(arr [$ 1] $ 2) `に置き換えれば成功します。
ステフェイン・グーリッホン

20

正規のソリューションは、別の回答者が言及したものです。

sort | uniq -c

Perlやawkで記述できるものよりも短くて簡潔です。

データのサイズがマシンのメインメモリサイズよりも大きいため、並べ替えを使用したくないと書き込みます。Unix sortコマンドの実装品質を過小評価しないでください。ソートは、128k(131,072バイト)のメモリ(PDP-11)を搭載したマシンで非常に大容量のデータ(元のAT&Tの請求データを考える)を処理するために使用されました。ソートは、事前設定された制限(多くの場合、マシンのメインメモリのサイズに近くなるように調整)を超えるデータを検出すると、メインメモリで読み取ったデータをソートし、一時ファイルに書き込みます。次に、データの次のチャンクでアクションを繰り返します。最後に、これらの中間ファイルに対してマージソートを実行します。これにより、並べ替えは、マシンのメインメモリよりも何倍も大きいデータを処理できます。


ええと、それはまだハッシュカウントよりも悪いですよね?データがメモリに収まる場合、どのソートアルゴリズムがソートを使用するか知っていますか?数値データの場合(-nオプション)は異なりますか?
Vinko Vrsalovic 2008

sort(1)の実装方法によって異なります。GNUソート(Linuxディストリビューションで使用)とBSDソートはどちらも、最も適切なアルゴリズムを使用するために非常に長くなります。
Diomidis Spinellis

9
cat ip_addresses | sort | uniq -c | sort -nr | awk '{print $2 " " $1}'

このコマンドはあなたに望ましい出力を与えるでしょう


4

線形動作を得るために大量のコードを使用してbashのハッシュをシミュレートするか、2次超線形バージョンに固執する必要があるようです。

これらのバージョンの中で、sauaのソリューションが最良(かつ最も単純)です。

sort -n ip_addresses.txt | uniq -c

http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-11/0118.htmlを見つけました。しかし、それは地獄のように醜いです...


同意する。これはこれまでで最高のソリューションであり、perlとawkでも同様のソリューションが可能です。誰かがbashでよりクリーンな実装を提供できますか?
Zizzencs 2008

私が知っていることではありません。$ ip(@ips){$ hash {$ ip} = $ hash {$ ip} + 1;の場合、ハッシュをサポートする言語でより良い実装を得ることができます。次に、キーと値を出力します。
Vinko Vrsalovic 2008

4

解決策(mysqlのようなグループ)

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

結果

3249  googleplus
4211 linkedin
5212 xing
7928 facebook

3

おそらくファイルシステム自体をハッシュテーブルとして使用できます。次のような擬似コード:

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

最後に、必要なのは、すべてのファイルを走査して、ファイル名と番号を出力することだけです。または、カウントを保持する代わりに、ファイルに毎回スペースまたは改行を追加して、最終的にファイルサイズをバイト単位で確認することもできます。


3

この場合、awk連想配列も便利だと思います

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

ここでの投稿によるグループ


Yepp、素晴らしいawkソリューションですが、awkは私がこれを実行していたマシンでは使用できませんでした。
Zizzencs 2008

1

他のソリューションのほとんどは重複を数えます。キーと値のペアをグループ化する必要がある場合は、次のことを試してください。

これが私のサンプルデータです:

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

これにより、md5チェックサムでグループ化されたキーと値のペアが出力されます。

cat table.txt | awk '{print $1}' | sort | uniq  | xargs -i grep {} table.txt
30c68b02161e15435ff52e34f4fe4ab8 b.txt
30c68b02161e15435ff52e34f4fe4ab8 c.txt
fe4ab8e15432161f452e345ff30c68b0 a.txt
fe4ab8e15432161f452e345ff30c68b0 d.txt
fe4ab8e15432161f452e345ff30c68b0 e.txt

1

ピュア (フォークなし!)

方法があります。 機能。フォークがないので、この方法は非常に迅速です!...

...の束間のIPアドレスは、滞在する小さな

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
}

注:IPアドレスは、配列のインデックスとして使用される32ビットの符号なし整数値に変換されます。これは、連想配列ではなく、単純なbash配列を使用します(より高価です)。

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

私のホストでは、これを行うと、フォークを使用するよりもはるかに速く、最大で約1,000のアドレスを取得できますが、ソートしてn'000のアドレスをカウントしようとすると、約1秒かかります。


0

私はそれをこのようにしたでしょう:

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

しかしuniqはあなたのために働くかもしれません。


元の投稿で述べたように、perlはオプションではありません。私はそれがperlで簡単であることを知っています、
それで

0

Bashで何かを探しているとのことですが、他の誰かがPythonで何かを探している可能性がある場合は、これを検討することをお勧めします。

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

セット内の値はデフォルトで一意であり、Pythonはこの点で非常に優れているため、ここで何か勝つ可能性があります。私はコードをテストしていないので、バグがあるかもしれませんが、これはあなたをそこに連れて行くかもしれません。また、発生をカウントしたい場合は、セットの代わりにdictを使用するのが簡単です。

編集:私はひどい読者なので、間違って答えました。これは、発生をカウントするdictを含むスニペットです。

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

辞書mydictは、一意のIPのリストをキーとして保持し、それらが発生した回数を値として保持します。


これは何も数えません。あなたはスコアを保つ辞書が必要です。

どー。質問をよく読みません。申し訳ありません。私はもともと、dictを使用して各IPアドレスの発生回数を保存することについて少し考えていましたが、質問をよく読んでいなかったため、削除しました。*正しくウェイクアップしようとします
wzzrd 2008

2
ありitertools.groupby()と組み合わせたsorted()OPが尋ねまさにありませんが。
jfs 2008

これはPythonでは素晴らしいソリューションですが、これには利用できませんでした:-)
Zizzencs

-8

順序が重要でない場合は、並べ替えを省略できます

uniq -c <source_file>

または

echo "$list" | uniq -c

ソースリストが変数の場合


1
さらに明確にするために、uniqのmanページから:注: 'uniq'は、隣接する行でない限り、繰り返される行を検出しません。最初に入力を並べ替えるか、「uniq」なしで「sort -u」を使用することができます。
コンバータ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.