variable - learn bash




從bash模擬“group by”的最佳方法是什麼? (10)

純粹的bash (沒有叉子!)

有一種方法,使用bash函數 。 這種方式非常快,因為沒有叉子!...

...雖然一堆的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個地址,但當我嘗試對10'000個地址進行排序時,大約需要1秒。

假設你有一個包含IP地址的文件,每行有一個地址:

10.0.10.1
10.0.10.1
10.0.10.3
10.0.10.2
10.0.10.1

您需要一個shell腳本來計算每個IP地址出現在文件中的次數。 對於之前的輸入,您需要以下輸出:

10.0.10.1 3
10.0.10.2 1
10.0.10.3 1

一種方法是:

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

然而,它遠沒有效率。

你將如何更有效地使用bash解決這個問題?

(有一點需要補充:我知道它可以通過perl或awk解決,我對bash有更好的解決方案感興趣,而不是那些語言。)

附加信息:

假設源文件是5GB,運行算法的機器有4GB。 所以排序不是一個有效的解決方案,也不是多次讀取文件。

我喜歡類似散列表的解決方案 - 任何人都可以提供對該解決方案的改進?

其他信息#2:

有些人問,為什麼我會在bash中用比如perl更簡單的方式來做這件事。 原因是在機器上我不得不這樣做,Perl不適合我。 這是一個定制的linux機器,沒有大部分我習慣的工具。 我認為這是一個有趣的問題。

所以,請不要責怪這個問題,如果你不喜歡,就忽略它。 :-)


大多數其他解決方案都是重複的。 如果您確實需要對鍵值對進行分組,請嘗試以下操作:

以下是我的示例數據:

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

快速和骯髒的方法如下:

cat ip_addresses | sort -n | uniq -c

如果您需要在bash中使用這些值,則可以將整個命令分配給一個bash變量,然後遍歷結果。

PS

如果省略了排序命令,您將不會得到正確的結果,因為uniq僅查看連續的相同行。


您可能可以將文件系統本身用作哈希表。 偽代碼如下:

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

最後,您只需遍歷所有文件並在文件中打印文件名和數字。 或者,您可以不用保留一個計數,而是每次在文件中添加空格或換行符,最後只需查看文件大小(以字節為單位)。


我知道你正在尋找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作為密鑰的列表以及它們作為值發生的次數。


我覺得awk關聯數組在這種情況下也很方便

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

一個小組通過郵寄here



規範的解決方案是另一位受訪者提到的解決方案:

sort | uniq -c

它比用Perl或awk編寫的代碼更短,更簡潔。

你寫道你不想使用排序,因為數據的大小大於機器的主內存大小。 不要低估Unix排序命令的執行質量。 Sort用於處理128k(即131,072字節)內存(PDP-11)機器上的大量數據(認為原始AT&T的計費數據)。 當排序遇到比預設限制更多的數據(經常調整接近機器主存儲器的大小)時,它將它在主存儲器中讀取的數據分類並將其寫入臨時文件中。 然後它會用下一批數據重複該操作。 最後,它對這些中間文件執行合併排序。 這允許排序處理比機器主存儲器多數倍的數據。


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

這個命令會給你想要的輸出


sort ip_addresses | uniq -c

這將首先打印計數,但除此之外,它應該正是你想要的。





scripting