web-dev-qa-db-ja.com

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

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:

一部の人々は、なぜそれがbashでそれを行うのが面倒だと思ったのですか? Perl。理由は、このマシンでこのPerlを実行する必要がなかったためです。これは、私が慣れ親しんでいるツールのほとんどを備えていないカスタムビルドのLinuxマシンでした。そして、それは興味深い問題だったと思います。

質問を非難しないでください。気に入らない場合は無視してください。 :-)

210
Zizzencs
sort ip_addresses | uniq -c

これは最初にカウントを印刷しますが、それ以外は正確に必要なものでなければなりません。

374
Joachim Sauer

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

cat ip_addresses | sort -n | uniq -c

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

PS

Sortコマンドを省略すると、uniqは連続する同一行のみを参照するため、正しい結果が得られません。

45

既存のフィールドのグループに基づいて複数のフィールドを合計するには、以下の例を使用します(要件に応じて$ 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
19
Anonymous

正規の解決策は、別の回答者が言及したものです。

sort | uniq -c

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

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

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

このコマンドは、必要な出力を提供します

9
zjor

大量のコードを使用してbashのハッシュをシミュレートして線形動作を取得するか、 二次 超線形バージョン。

これらのバージョンの中で、 saua のソリューションが最適です(そして最も簡単です):

sort -n ip_addresses.txt | uniq -c

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

4
Vinko Vrsalovic

ソリューション(mysqlのようなグループ化)

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

結果

3249  googleplus
4211 linkedin
5212 xing
7928 facebook
4
kairouan2020

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

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

投稿ごとのグループ こちら

3
Jadu Saikia

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

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
PolyThinker

純粋 bash (フォークなし!)

bash functionを使用する方法があります。フォークがないので、この方法は非常に迅速です!...

...ipアドレスの束smallのまま!

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ビットの符号なし整数値に変換され、arrayのインデックスとして使用されます。これは単純な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アドレスですが、sort'n count10 '000アドレス。

1
F. Hauri

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

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

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

0
nicerobot

他のほとんどのソリューションでは重複がカウントされます。キーと値のペアを本当にグループ化する必要がある場合は、これを試してください:

これが私のデータ例です:

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
0
Aron Curzon

Bashで何かを探しているのは理解していますが、誰か他の人がPythonで何かを探しているかもしれない場合は、これを検討してください。

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

セット内の値はデフォルトで一意であり、Pythonはこの点でかなり優れているため、ここで何かを獲得するかもしれません。私はコードをテストしていませんので、バグがあるかもしれませんが、これはあなたをそこに導くかもしれません。また、発生回数をカウントする場合は、セットの代わりに辞書を使用すると簡単に実装できます。

編集:私はお粗末な読者なので、私は間違って答えた。これは、発生をカウントする辞書を含むスニペットです。

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のリストを保持し、値として発生した回数を保持します。

0
wzzrd