Số lượng lớn các mẫu từ tệp khổng lồ


18

Tôi có một tệp phát triển khoảng 200.000 dòng mỗi ngày và tất cả được tạo thành với các khối gồm ba dòng như sau:

1358726575123       # key
    Joseph Muller   # name
    carpenter       # job
9973834728345
    Andres Smith
    student
7836472098652
    Mariah Anthony
    dentist

Bây giờ, tôi có một tệp khác mà tôi trích xuất khoảng 10.000 mẫu chính, chẳng hạn như 1358726575123. Sau đó, tôi chạy một forvòng lặp với các mẫu này và phải kiểm tra chúng với tệp đầu tiên. Nếu tệp không chứa mẫu đó, tôi lưu mẫu trong tệp thứ ba để xử lý thêm:

for number in $(grep -o '[0-9]\{12\}' file2); do  # finds about 10.000 keys
     if ! grep -q ^$number$ file1; then           # file1 is a huge file
         printf "$number\n" >>file3               # we'll process file3 later
     fi
done

Mã ví dụ thu được một tệp khổng lồ 10.000 lần và tôi chạy vòng lặp này khoảng một lần một phút, trong cả ngày .

Vì tệp khổng lồ tiếp tục phát triển, tôi có thể làm gì để làm cho tất cả điều này nhanh hơn và tiết kiệm một số CPU? Tôi tự hỏi liệu sắp xếp tệp bằng cách nào đó bằng khóa của nó (nếu vậy, làm thế nào?) Hoặc sử dụng db thay vì văn bản thuần túy sẽ giúp ...


Câu trả lời:


11

Câu trả lời này dựa trên awkcâu trả lời được đăng bởi potong ..
Nó nhanh gấp đôi so với commphương thức (trên hệ thống của tôi), với cùng 6 triệu dòng trong tệp chính và 10 nghìn khóa ... (hiện đã được cập nhật để sử dụng FNR, Khu bảo tồn

Mặc dù awknhanh hơn hệ thống hiện tại của bạn và sẽ cung cấp cho bạn và (các) máy tính của bạn một chút không gian thở, hãy lưu ý rằng khi xử lý dữ liệu mạnh như bạn đã mô tả, bạn sẽ nhận được kết quả tổng thể tốt nhất bằng cách chuyển sang cơ sở dữ liệu chuyên dụng; ví dụ. SQlite, MySQL ...


awk '{ if (/^[^0-9]/) { next }              # Skip lines which do not hold key values
       if (FNR==NR) { main[$0]=1 }          # Process keys from file "mainfile"
       else if (main[$0]==0) { keys[$0]=1 } # Process keys from file "keys"
     } END { for(key in keys) print key }' \
       "mainfile" "keys" >"keys.not-in-main"

# For 6 million lines in "mainfile" and 10 thousand keys in "keys"

# The awk  method
# time:
#   real    0m14.495s
#   user    0m14.457s
#   sys     0m0.044s

# The comm  method
# time:
#   real    0m27.976s
#   user    0m28.046s
#   sys     0m0.104s


Điều này nhanh, nhưng tôi không hiểu nhiều về awk: tên tệp sẽ trông như thế nào? Tôi đã thử file1 -> mainfilefile2 -> keysvới gawk và mawk, và nó xuất ra các phím sai.
Teresa e Junior

file1 có khóa, tên và công việc.
Teresa e Junior

'mainfile' là tệp lớn (có khóa, tên và công việc). Tôi vừa gọi nó là "mainfile" vì tôi cứ bị lẫn lộn với tập tin nào (file1 so với file2) .. 'khóa' chỉ chứa 10 nghìn, hoặc nhiều khóa, .. Đối với tình huống của bạn KHÔNG chuyển hướng bất kỳ . .. chỉ sử dụng tệp1 EOF tệp2 Chúng là tên của các tệp của bạn .. "EOF" là tập lệnh 1 dòng theo kịch bản để chỉ ra phần cuối của tệp đầu tiên (tệp dữ liệu chính) và bắt đầu tệp thứ hai (tệp dữ liệu chính) và bắt đầu tệp thứ hai ( các phím). awkcho phép bạn đọc trong một loạt các tệp .. Trong trường hợp này, chuỗi đó có 3 tệp trong đó. Đầu ra chuyển đếnstdout
Peter.O

Kịch bản này sẽ in bất kỳ phím mà là trong hiện tại mainfile, nó cũng sẽ in bất kỳ phím từ keystập tin đó là KHÔNG trong mainfile... Đó có thể là những gì đang xảy ra ... (Tôi sẽ xem xét một chút sâu hơn vào nó ...
Peter.O

Cảm ơn bạn, @ Peter.O! Vì các tệp được bảo mật, tôi đang cố gắng tạo các tệp mẫu $RANDOMđể tải lên.
Teresa e Junior

16

Vấn đề, tất nhiên, là bạn chạy grep trên tệp lớn 10.000 lần. Bạn chỉ nên đọc cả hai tập tin một lần. Nếu bạn muốn ở bên ngoài các ngôn ngữ kịch bản, bạn có thể làm theo cách này:

  1. Trích xuất tất cả các số từ tệp 1 và sắp xếp chúng
  2. Trích xuất tất cả các số từ tệp 2 và sắp xếp chúng
  3. Chạy commtrên danh sách đã sắp xếp để nhận những gì chỉ có trong danh sách thứ hai

Một cái gì đó như thế này:

$ grep -o '^[0-9]\{12\}$' file1 | sort -u -o file1.sorted
$ grep -o  '[0-9]\{12\}'  file2 | sort -u -o file2.sorted
$ comm -13 file1.sorted file2.sorted > file3

Xem man comm.

Nếu bạn có thể cắt bớt tệp lớn mỗi ngày (như tệp nhật ký), bạn có thể giữ bộ đệm của các số được sắp xếp và không cần phải phân tích toàn bộ tệp mỗi lần.


1
Khéo léo! 2 giây (trên các ổ đĩa không đặc biệt nhanh) với 200.000 mục nhập ngẫu nhiên trong tệp chính (ví dụ: 600.000 dòng) và 143.000 khóa ngẫu nhiên (đó chỉ là cách dữ liệu thử nghiệm của tôi kết thúc) ... đã thử nghiệm và nó hoạt động (nhưng bạn biết rằng: ) ... Tôi tự hỏi về {12}.. OP đã sử dụng 12, nhưng các khóa ví dụ dài 13 ...
Peter.O

2
Chỉ cần một lưu ý nhỏ, bạn có thể làm điều đó mà không phải xử lý các tệp tạm thời bằng cách sử dụng <(grep...sort)tên tệp.
Kevin

Cảm ơn bạn, nhưng grepping và sắp xếp các tệp mất nhiều thời gian hơn vòng lặp trước của tôi (+ 2 phút.).
Teresa e Junior

@Teresa e Junior. Làm thế nào lớn là tập tin chính của bạn? ... Bạn đã đề cập rằng nó tăng lên 200.000 dòng mỗi ngày, nhưng không lớn đến mức nào ... Để giảm lượng dữ liệu bạn cần xử lý, bạn có thể đọc chỉ 200.000 dòng hiện tại bằng cách ghi chú số dòng cuối cùng được xử lý (ngày hôm qua) và chỉ sử dụng tail -n +$linenumđể xuất dữ liệu mới nhất. Bằng cách đó, bạn sẽ chỉ xử lý khoảng 200.000 dòng mỗi ngày .. Tôi vừa kiểm tra nó với 6 triệu dòng trong tệp chính và 10 nghìn khóa ... thời gian : thực 0m0.016s, người dùng 0m0.008s, sys 0m0.008s
Peter.O

Tôi thực sự khá bối rối / tò mò về cách bạn có thể grep tệp chính của mình 10.000 lần và thấy nó nhanh hơn so với phương pháp này mà chỉ greps nó một lần (và một lần cho nhỏ hơn nhiều file1 ) ... Ngay cả khi sắp xếp của bạn mất nhiều thời gian hơn tôi kiểm tra, tôi chỉ không thể hiểu được ý tưởng rằng đọc một tệp lớn mà nhiều lần không vượt quá một loại (thời gian)
Peter.O

8

Có, chắc chắn sử dụng một cơ sở dữ liệu. Chúng được làm chính xác cho các nhiệm vụ như thế này.


Cảm ơn! Tôi không có nhiều kinh nghiệm với cơ sở dữ liệu. Cơ sở dữ liệu nào bạn đề nghị? Tôi đã cài đặt MySQL và lệnh sqlite3.
Teresa e Junior

1
Cả hai đều tốt cho việc này, sqlite đơn giản hơn vì về cơ bản nó chỉ là một tệp và API SQL để truy cập nó. Với MySQL, bạn cần thiết lập một máy chủ MySQL để sử dụng nó. Mặc dù điều đó cũng không khó lắm, nhưng sqlite có thể là tốt nhất để bắt đầu.
Mika Fischer

3

Điều này có thể làm việc cho bạn:

 awk '/^[0-9]/{a[$0]++}END{for(x in a)if(a[x]==1)print x}' file{1,2} >file3

CHỈNH SỬA:

Tập lệnh được sửa đổi để cho phép trùng lặp và khóa không xác định trong cả hai tệp, vẫn tạo khóa từ tệp đầu tiên không có trong tệp thứ hai:

 awk '/^[0-9]/{if(FNR==NR){a[$0]=1;next};if($0 in a){a[$0]=2}}END{for(x in a)if(a[x]==1)print x}' file{1,2} >file3

Điều này sẽ bỏ lỡ các khóa mới xuất hiện nhiều hơn một lần trong tệp chính (và đối với vấn đề đó, xảy ra nhiều hơn một lần trong tệp khóa) Dường như yêu cầu tăng số lượng mảng của tệp chính không được vượt quá 1, hoặc một số cách giải quyết tương đương (+1 vì nó khá gần với nhãn hiệu)
Peter.O

1
Tôi đã thử với gawk và mawk, và nó xuất ra các phím sai ...
Teresa e Junior

@ Peter.OI giả sử tệp chính có các khóa duy nhất và tệp 2 là tập hợp con của tệp chính.
potong

@potong Cái thứ hai hoạt động tốt và rất nhanh! Cảm ơn bạn!
Teresa e Junior

@Teresa e Junior Bạn có chắc chắn nó đang làm việc một cách chính xác chưa? .. Sử dụng dữ liệu thử nghiệm mà bạn cung cấp , mà nên đầu ra 5000 phím, khi tôi chạy nó, nó tạo ra 136.703 phím, cũng giống như tôi đã cho đến khi cuối cùng tôi đã hiểu những gì yêu cầu của bạn là ... @potong Tất nhiên rồi! FNR == NR (Tôi chưa bao giờ sử dụng nó trước đây :)
Peter.O

2

Với nhiều dữ liệu đó, bạn thực sự nên chuyển sang cơ sở dữ liệu. Trong khi đó, một điều bạn phải làm để có được bất cứ nơi nào gần hiệu suất tốt là không tìm kiếmfile1 riêng cho từng khóa. Chạy một lần duy nhất grepđể giải nén tất cả các khóa không loại trừ cùng một lúc. Vì điều đó grepcũng trả về các dòng không chứa khóa, hãy lọc chúng đi.

grep -o '[0-9]\{12\}' file2 |
grep -Fxv -f - file1 |
grep -vx '[0-9]\{12\}' >file3

( -Fxcó nghĩa là tìm kiếm toàn bộ dòng, theo nghĩa đen.-f - có nghĩa là đọc danh sách các mẫu từ đầu vào tiêu chuẩn.)


Trừ khi tôi nhầm, điều này không giải quyết được vấn đề lưu trữ khóa không có trong tệp lớn, nó sẽ lưu trữ các khóa có trong đó.
Kevin

@Kevin chính xác, và điều này đã buộc tôi phải sử dụng vòng lặp.
Teresa e Junior

@TeresaeJunior: thêm -v( -Fxv) có thể xử lý việc đó.
Tạm dừng cho đến khi có thông báo mới.

@DennisWilliamson Điều đó sẽ chọn tất cả các dòng trong tệp lớn không khớp với bất kỳ tệp nào trong tệp chính, bao gồm tên, công việc, v.v.
Kevin

@Kevin Cảm ơn, tôi đã đọc sai câu hỏi. Tôi đã thêm một bộ lọc cho các dòng không chính, mặc dù hiện tại tôi thích sử dụngcomm .
Gilles 'SO- ngừng trở nên xấu xa'

2

Cho phép tôi củng cố những gì người khác đã nói, "Đưa bạn đến cơ sở dữ liệu!"

mã nhị phân MySQL có sẵn miễn phí cho hầu hết các nền tảng.

Tại sao không phải là SQLite? Nó dựa trên bộ nhớ, tải một tệp phẳng khi bạn khởi động nó, sau đó đóng nó khi bạn hoàn thành. Điều này có nghĩa là nếu máy tính của bạn gặp sự cố hoặc quá trình SQLite biến mất, thì tất cả dữ liệu cũng vậy.

Vấn đề của bạn trông giống như một vài dòng SQL và sẽ chạy trong một phần nghìn giây!

Sau khi cài đặt MySQL (mà tôi khuyên dùng qua các lựa chọn khác), tôi đã bỏ ra $ 40 cho cuốn sách dạy nấu ăn SQL của O'Reilly của Anthony Molinaro, có rất nhiều mẫu vấn đề, bắt đầu bằng SELECT * FROM tablecác truy vấn đơn giản và trải qua tổng hợp và nhiều phép nối.


Có, tôi sẽ bắt đầu di chuyển dữ liệu của mình sang SQL sau vài ngày, cảm ơn bạn! Các kịch bản awk đã giúp tôi rất nhiều cho đến khi tôi hoàn thành nó!
Teresa e Junior

1

Tôi không chắc đây có phải là đầu ra chính xác mà bạn đang tìm kiếm hay không, nhưng có lẽ cách dễ nhất là:

grep -o '[0-9]\{12\}' file2 | sed 's/.*/^&$/' > /tmp/numpatterns.grep
grep -vf /tmp/numpatterns.grep file1 > file3
rm -f /tmp/numpatterns.grep

Bạn cũng có thể sử dụng:

sed -ne '/.*\([0-9]\{12\}.*/^\1$/p' file2 > /tmp/numpatterns.grep
grep -vf /tmp/numpatterns.grep file1 > file3
rm -f /tmp/numpatterns.grep

Mỗi cái này tạo ra một tệp mẫu tạm thời được sử dụng để thu thập các số từ tệp lớn ( file1).


Tôi tin rằng điều này cũng tìm thấy những con số trong tập tin lớn, không phải những con số không.
Kevin

Đúng, tôi đã không thấy '!' trong OP. Chỉ cần sử dụng grep -vfthay vì grep -f.
Arcege

2
Không có @arcege, grep -vf sẽ không hiển thị các khóa không khớp, nó sẽ hiển thị mọi thứ bao gồm cả tên và công việc.
Teresa e Junior

1

Tôi hoàn toàn đồng ý với việc bạn nhận được một cơ sở dữ liệu (MySQL khá dễ sử dụng). Cho đến khi bạn có được điều đó, tôi thích commgiải pháp của Angus , nhưng rất nhiều người đang cố gắng grepvà hiểu sai mà tôi nghĩ rằng tôi đã chỉ ra (hoặc ít nhất một) cách chính xác để làm điều đó grep.

grep -o '[0-9]\{12\}' keyfile | grep -v -f <(grep -o '^[0-9]\{12\}' bigfile) 

Đầu tiên grepnhận được các phím. Cái thứ ba grep(trong <(...)) lấy tất cả các khóa được sử dụng trong tệp lớn và <(...)chuyển nó giống như một tệp làm đối số -ftrong grep thứ hai. Điều đó làm cho cái thứ hai grepsử dụng nó như một danh sách các dòng phù hợp. Sau đó, nó sử dụng điều này để khớp với đầu vào của nó (danh sách các khóa) từ đường ống (đầu tiên grep) và in bất kỳ khóa nào được trích xuất từ ​​tệp khóa và không ( -v) tệp lớn.

Tất nhiên bạn có thể làm điều này với các tệp tạm thời bạn phải theo dõi và nhớ xóa:

grep -o '[0-9]\{12\}'  keyfile >allkeys
grep -o '^[0-9]\{12\}' bigfile >usedkeys
grep -v -f usedkeys allkeys

Điều này in tất cả các dòng trong allkeysđó không xuất hiện trong usedkeys.


Thật không may, nó chậm và tôi gặp lỗi bộ nhớ sau 40 giây:grep: Memory exhausted
Peter.O

@ Peter.O Nhưng nó đúng. Dù sao, đó là lý do tại sao tôi đề xuất một cơ sở dữ liệu hoặc comm, theo thứ tự đó.
Kevin

Vâng, nó hoạt động, nhưng chậm hơn nhiều so với vòng lặp.
Teresa e Junior

1

Keyfile không thay đổi? Sau đó, bạn nên tránh tìm kiếm các mục cũ nhiều lần.

Với tail -fbạn có thể nhận được đầu ra của một tập tin đang phát triển.

tail -f growingfile | grep -f keyfile 

grep -f đọc các mẫu từ một tệp, một dòng dưới dạng mẫu.


Điều đó sẽ tốt, nhưng tệp chính luôn khác nhau.
Teresa e Junior

1

Sẽ không đăng câu trả lời của tôi vì tôi nghĩ rằng lượng dữ liệu đó không nên được xử lý bằng tập lệnh shell và câu trả lời đúng để sử dụng cơ sở dữ liệu đã được đưa ra. Nhưng kể từ bây giờ có 7 cách tiếp cận khác ...

Đọc tệp đầu tiên trong bộ nhớ, sau đó lấy tệp thứ hai để tìm số và kiểm tra xem các giá trị có được lưu trong bộ nhớ hay không. Nó phải nhanh hơn nhiều grepgiây, nếu bạn có đủ bộ nhớ để tải toàn bộ tệp, nghĩa là.

declare -a record
while read key
do
    read name
    read job
    record[$key]="$name:$job"
done < file1

for number in $(grep -o '[0-9]\{12\}' file2)
do
    [[ -n ${mylist[$number]} ]] || echo $number >> file3
done

Tôi đã có đủ bộ nhớ, nhưng tôi thấy cái này còn chậm hơn. Cảm ơn mặc dù!
Teresa e Junior

1

Tôi đồng ý với @ jan-steinman rằng bạn nên sử dụng cơ sở dữ liệu cho loại nhiệm vụ này. Có rất nhiều cách để hack cùng một giải pháp với tập lệnh shell như các câu trả lời khác cho thấy, nhưng thực hiện theo cách đó sẽ dẫn đến rất nhiều khổ sở nếu bạn sẽ sử dụng và duy trì mã trong thời gian dài hơn chỉ là một dự án vứt bỏ một ngày.

Giả sử bạn đang sử dụng hộp Linux thì rất có thể bạn đã cài đặt Python theo mặc định bao gồm thư viện sqlite3 kể từ Python v2.5. Bạn có thể kiểm tra phiên bản Python của mình với:

% python -V
Python 2.7.2+

Tôi khuyên bạn nên sử dụng thư viện sqlite3 vì đây là một giải pháp dựa trên tệp đơn giản tồn tại cho tất cả các nền tảng (bao gồm cả bên trong trình duyệt web của bạn!) Và nó không yêu cầu phải cài đặt máy chủ. Về cơ bản không cấu hình và không bảo trì.

Dưới đây là một tập lệnh python đơn giản sẽ phân tích định dạng tệp mà bạn đã đưa ra làm ví dụ và sau đó thực hiện một truy vấn "chọn tất cả" đơn giản và xuất ra mọi thứ nó được lưu trữ trong db.

#!/usr/bin/env python

import sqlite3
import sys

dbname = '/tmp/simple.db'
filename = '/tmp/input.txt'
with sqlite3.connect(dbname) as conn:
    conn.execute('''create table if not exists people (key integer primary key, name text, job text)''')
    with open(filename) as f:
        for key in f:
            key = key.strip()
            name = f.next().strip()
            job = f.next().strip()
            try:
                conn.execute('''insert into people values (?,?,?)''', (key, name, job))
            except sqlite3.IntegrityError:
                sys.stderr.write('record already exists: %s, %s, %s\n' % (key, name, job))
    cur = conn.cursor()

    # get all people
    cur.execute('''select * from people''')
    for row in cur:
        print row

    # get just two specific people
    person_list = [1358726575123, 9973834728345]
    cur.execute('''select * from people where key in (?,?)''', person_list)
    for row in cur:
        print row

    # a more general way to get however many people are in the list
    person_list = [1358726575123, 9973834728345]
    template = ','.join(['?'] * len(person_list))
    cur.execute('''select * from people where key in (%s)''' % (template), person_list)
    for row in cur:
        print row

Vâng, điều này có nghĩa là bạn sẽ cần học một số SQL , nhưng về lâu dài nó sẽ có giá trị. Ngoài ra, thay vì phân tích tệp nhật ký của bạn, có thể bạn có thể ghi dữ liệu trực tiếp vào cơ sở dữ liệu sqlite của mình.


Cảm ơn bạn cho kịch bản python! Tôi nghĩ /usr/bin/sqlite3hoạt động theo cách tương tự đối với các tập lệnh shell ( tests.debian.org/squeeze/sqlite3 ), mặc dù tôi chưa bao giờ sử dụng nó.
Teresa e Junior

Có, bạn có thể sử dụng /usr/bin/sqlite3với các tập lệnh shell, tuy nhiên tôi khuyên bạn nên tránh các tập lệnh shell ngoại trừ các chương trình vứt bỏ đơn giản và thay vào đó sử dụng ngôn ngữ như python có khả năng xử lý lỗi tốt hơn và dễ duy trì và phát triển hơn.
aculich
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.