Cách tốt nhất để mô phỏng nhóm trên mạng bởi bash từ bash?


231

Giả sử bạn có một tệp chứa địa chỉ IP, một địa chỉ trong mỗi dòng:

10.0.10.1
10.0.10.1
10.0.10.3
10.0.10.2
10.0.10.1

Bạn cần một tập lệnh shell tính cho mỗi địa chỉ IP bao nhiêu lần nó xuất hiện trong tệp. Đối với đầu vào trước, bạn cần đầu ra sau:

10.0.10.1 3
10.0.10.2 1
10.0.10.3 1

Một cách để làm điều này là:

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

Tuy nhiên, nó thực sự là xa hiệu quả.

Làm thế nào bạn sẽ giải quyết vấn đề này hiệu quả hơn bằng cách sử dụng bash?

(Một điều cần nói thêm: Tôi biết nó có thể được giải quyết từ perl hoặc awk, tôi quan tâm đến một giải pháp tốt hơn trong bash, không phải bằng các ngôn ngữ đó.)

THÔNG TIN BỔ SUNG:

Giả sử rằng tệp nguồn là 5 GB và máy chạy thuật toán có 4GB. Vì vậy, sắp xếp không phải là một giải pháp hiệu quả, cũng không đọc tệp nhiều hơn một lần.

Tôi thích giải pháp giống như hashtable - bất kỳ ai cũng có thể cung cấp các cải tiến cho giải pháp đó?

THÔNG TIN BỔ SUNG # 2:

Một số người hỏi tại sao tôi lại bận tâm làm điều đó trong bash khi nó dễ dàng hơn, ví dụ như perl. Lý do là trên máy tôi phải làm điều này không có sẵn cho tôi. Nó là một máy linux được xây dựng tùy chỉnh mà không có hầu hết các công cụ tôi đã sử dụng. Và tôi nghĩ đó là một vấn đề thú vị.

Vì vậy, xin vui lòng, đừng đổ lỗi cho câu hỏi, chỉ cần bỏ qua nó nếu bạn không thích nó. :-)


Tôi nghĩ bash là công cụ sai cho công việc. Perl có lẽ sẽ là một giải pháp tốt hơn.
Francois Wolmarans

Câu trả lời:


412
sort ip_addresses | uniq -c

Điều này sẽ in số đếm đầu tiên, nhưng khác hơn là nó phải chính xác những gì bạn muốn.


71
mà sau đó bạn có thể chuyển sang "sort -nr" để sắp xếp theo thứ tự giảm dần, từ số cao nhất đến số thấp nhất. tức làsort ip_addresses | uniq -c | sort -nr
Brad

15
sort ip_addresses | uniq -c | sort -nr | awk '{ print $2, $1 }'để có được địa chỉ IP trong cột đầu tiên và đếm trong giây.
Raghu Dodda

thêm một điều chỉnh cho phần sắp xếp:sort -nr -k1,1
Andrzej Martyna

50

Phương pháp nhanh và bẩn như sau:

cat ip_addresses | sort -n | uniq -c

Nếu bạn cần sử dụng các giá trị trong bash, bạn có thể gán toàn bộ lệnh cho biến bash và sau đó lặp qua các kết quả.

PS

Nếu lệnh sort bị bỏ qua, bạn sẽ không nhận được kết quả chính xác vì uniq chỉ nhìn vào các dòng giống nhau liên tiếp.


Đó là hiệu quả rất khôn ngoan, bạn vẫn có hành vi bậc hai
Vinko Vrsalovic

Ý nghĩa bậc hai O (n ^ 2) ?? Điều đó phụ thuộc vào thuật toán sắp xếp chắc chắn, không có khả năng sử dụng loại bogo-sort như vậy.
paxdiablo

Chà, trong trường hợp tốt nhất, đó là O (n log (n)), tệ hơn hai lần chuyền (đó là những gì bạn nhận được với việc thực hiện dựa trên hàm băm tầm thường). Tôi nên nói "siêu tuyến" thay vì bậc hai.
Vinko Vrsalovic

Và nó vẫn nằm trong cùng một ràng buộc rằng những gì OP yêu cầu để cải thiện hiệu quả một cách khôn ngoan ...
Vinko Vrsalovic

11
uuoc, việc sử dụng mèo vô dụng

22

để tổng hợp nhiều trường, dựa trên một nhóm các trường hiện có, hãy sử dụng ví dụ dưới đây: (thay thế $ 1, $ 2, $ 3, $ 4 theo yêu cầu của bạn)

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 vì nó cho biết phải làm gì khi không chỉ cần số đếm
dùng829755

1
+1 vì sortuniqdễ nhất để thực hiện đếm, nhưng không giúp đỡ khi bạn cần tính toán / tính tổng các giá trị trường. Cú pháp mảng của awk rất mạnh và là chìa khóa để nhóm ở đây. Cảm ơn!
odony

1
một điều nữa, xem ra printchức năng của awk dường như hạ thấp số nguyên 64 bit xuống còn 32 bit, vì vậy đối với các giá trị int vượt quá 2 ^ 31 bạn có thể muốn sử dụng printfvới %.0fđịnh dạng thay vì printở đó
odony

1
Những người tìm kiếm "nhóm theo" bằng cách nối chuỗi thay vì cộng số sẽ thay thế arr[$1,$2]+=$3+$4bằng ví dụ: 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: Array [$ 1] = (Array [$ 1] $ 2) `thành công.
Stéphane Gourichon

20

Giải pháp kinh điển là giải pháp được đề cập bởi người trả lời khác:

sort | uniq -c

Nó ngắn hơn và súc tích hơn những gì có thể viết bằng Perl hoặc awk.

Bạn viết rằng bạn không muốn sử dụng sắp xếp, vì kích thước của dữ liệu lớn hơn kích thước bộ nhớ chính của máy. Đừng đánh giá thấp chất lượng thực hiện của lệnh sắp xếp Unix. Sắp xếp được sử dụng để xử lý khối lượng dữ liệu rất lớn (nghĩ rằng dữ liệu thanh toán ban đầu của AT & T) trên các máy có bộ nhớ 128k (tức là 131.072 byte) (PDP-11). Khi sắp xếp gặp nhiều dữ liệu hơn giới hạn đặt trước (thường được điều chỉnh gần với kích thước của bộ nhớ chính của máy), nó sẽ sắp xếp dữ liệu mà nó đã đọc trong bộ nhớ chính và ghi vào một tệp tạm thời. Sau đó, nó lặp lại hành động với các khối dữ liệu tiếp theo. Cuối cùng, nó thực hiện sắp xếp hợp nhất trên các tệp trung gian đó. Điều này cho phép sắp xếp để làm việc trên dữ liệu lớn hơn nhiều lần so với bộ nhớ chính của máy.


Chà, nó vẫn còn tệ hơn cả số băm, phải không? Bạn có biết thuật toán sắp xếp nào sắp xếp sử dụng nếu dữ liệu vừa với bộ nhớ không? Nó có khác nhau trong trường hợp dữ liệu số (tùy chọn -n) không?
Vinko Vrsalovic

Nó phụ thuộc vào cách sắp xếp (1). Cả GNU sort (được sử dụng trên các bản phân phối Linux) và sắp xếp BSD đều có chiều dài lớn để sử dụng thuật toán phù hợp nhất.
Diomidis Spinellis

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

lệnh này sẽ cung cấp cho bạn đầu ra mong muốn


4

Có vẻ như bạn phải sử dụng một lượng lớn mã để mô phỏng băm trong bash để có hành vi tuyến tính hoặc tuân theo các phiên bản siêu tuyến bậc hai .

Trong số các phiên bản đó, giải pháp của saua là tốt nhất (và đơn giản nhất):

sort -n ip_addresses.txt | uniq -c

Tôi tìm thấy http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-11/0118.html . Nhưng nó xấu như địa ngục ...


Tôi đồng ý. Đây là giải pháp tốt nhất cho đến nay và các giải pháp tương tự có thể có trong perl và awk. Bất cứ ai cũng có thể cung cấp một thực hiện sạch hơn trong bash?
Zizzencs

Không phải là tôi biết. Bạn có thể nhận được các triển khai tốt hơn trong các ngôn ngữ hỗ trợ băm, nơi bạn thực hiện cho $ ip (@ips) {$ hash {$ ip} = $ hash {$ ip} + 1; } và sau đó chỉ cần in các khóa và giá trị.
Vinko Vrsalovic

4

Giải pháp (nhóm theo như mysql)

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

Kết quả

3249  googleplus
4211 linkedin
5212 xing
7928 facebook

3

Bạn có thể có thể sử dụng chính hệ thống tệp như một bảng băm. Mã giả như sau:

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

Cuối cùng, tất cả những gì bạn cần làm là duyệt qua tất cả các tệp và in tên tệp và số trong đó. Ngoài ra, thay vì giữ số đếm, bạn có thể nối thêm khoảng trắng hoặc dòng mới mỗi lần vào tệp và cuối cùng chỉ cần xem kích thước tệp theo byte.


3

Tôi cảm thấy mảng kết hợp awk cũng có ích trong trường hợp này

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

Một nhóm bằng cách đăng ở đây


Yepp, giải pháp awk tuyệt vời, nhưng awk chỉ là không thể có trên máy tôi đang làm điều này.
Zizzencs

1

Hầu hết các giải pháp khác tính trùng lặp. Nếu bạn thực sự cần nhóm các cặp giá trị khóa, hãy thử điều này:

Đây là dữ liệu ví dụ của tôi:

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

Điều này sẽ in các cặp giá trị khóa được nhóm bởi tổng kiểm tra 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

Nguyên chất (không có ngã ba!)

Có một cách, sử dụng một chức năng . Cách này rất nhanh vì không có ngã ba! ...

... Trong khi một loạt các địa chỉ IP vẫn nhỏ !

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
}

Lưu ý: Địa chỉ IP được chuyển đổi thành 32 bit giá trị nguyên không dấu, được sử dụng làm chỉ mục cho mảng . Điều này sử dụng mảng bash đơn giản , không phải mảng kết hợp (wich đắt hơn)!

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

Trên máy chủ của tôi, làm như vậy nhanh hơn rất nhiều so với sử dụng dĩa, tối đa khoảng 1000 địa chỉ, nhưng mất khoảng 1 toàn bộ giây khi tôi cố gắng sắp xếp đếm 10.000 địa chỉ.


0

Tôi đã làm nó như thế này:

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

nhưng uniq có thể làm việc cho bạn.


Như tôi đã nói trong bài viết gốc perl không phải là một lựa chọn. Tôi biết nó rất dễ dàng trong perl, không có vấn đề gì với điều đó :-)
Zizzencs

0

Tôi hiểu rằng bạn đang tìm kiếm thứ gì đó ở Bash, nhưng trong trường hợp người khác có thể đang tìm kiếm thứ gì đó trong Python, bạn có thể muốn xem xét điều này:

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

Vì các giá trị trong tập hợp là duy nhất theo mặc định và Python khá tốt trong công cụ này, bạn có thể giành được thứ gì đó ở đây. Tôi chưa kiểm tra mã, vì vậy nó có thể bị lỗi, nhưng điều này có thể đưa bạn đến đó. Và nếu bạn muốn đếm số lần xuất hiện, sử dụng một lệnh thay vì tập hợp là dễ thực hiện.

Chỉnh sửa: Tôi là một độc giả tệ hại, vì vậy tôi đã trả lời sai. Đây là một đoạn trích với một lệnh sẽ đếm số lần xuất hiện.

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

Từ điển mydict hiện giữ một danh sách các IP duy nhất làm khóa và số lần chúng xảy ra như các giá trị của chúng.


cái này không tính gì cả. bạn cần một lệnh giữ điểm.

Doh. Xin lỗi đọc câu hỏi, xin lỗi. Ban đầu tôi có một chút gì đó về việc sử dụng một lệnh để lưu trữ số lần mỗi địa chỉ IP xảy ra, nhưng đã xóa nó, bởi vì, tôi đã không đọc câu hỏi rất tốt. * cố gắng thức dậy đúng cách
wzzrd

2
Có một itertools.groupby()kết hợp với sorted()thực hiện chính xác những gì OP yêu cầu.
jfs

Đó là một giải pháp tuyệt vời trong python, không có sẵn cho việc này :-)
Zizzencs

-8

Sắp xếp có thể được bỏ qua nếu thứ tự không đáng kể

uniq -c <source_file>

hoặc là

echo "$list" | uniq -c

nếu danh sách nguồn là một biến


1
Để làm rõ hơn, từ trang man uniq: Lưu ý: 'uniq' không phát hiện các dòng lặp lại trừ khi chúng liền kề nhau. Bạn có thể muốn sắp xếp đầu vào trước hoặc sử dụng 'sort -u' mà không có 'uniq'.
bộ chuyển
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.