거대한 파일에서 많은 패턴을 가져 오기


18

나는 하루에 약 20 만 줄씩 성장하는 파일을 가지고 있으며 모두 3 줄의 블록으로 구성됩니다.

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

이제 약 10,000 개의 키 패턴을 추출하는 다른 파일이 있습니다 1358726575123. 그런 다음 for이 패턴 으로 루프를 실행 하고 첫 번째 파일과 비교하여 확인해야합니다. 파일에 이러한 패턴이 없으면 추가 처리를 위해 패턴을 세 번째 파일에 저장합니다.

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

예제 코드는 거대한 파일을 10,000 번 그 리핑하며 하루 종일 1 분한 번씩 이 루프를 실행합니다 .

거대한 파일이 계속 커지므로이 모든 작업을 더 빠르게 수행하고 CPU를 절약하려면 어떻게해야합니까? 어떻게 든 키를 기준으로 파일을 정렬하거나 (그렇다면 어떻게?) 일반 텍스트 대신 db를 사용하는 것이 도움이 될지 궁금합니다 ...


답변:


11

이 답변은 potongawk 에 의해 게시 된 답변을 기반으로합니다 . 메인 파일 의 동일한 6 백만 줄10,000 개의 키에 대해 (내 시스템의) 방법 보다 두 배 빠릅니다 ... (현재 FNR을 사용하도록 업데이트되었습니다. NR)
comm

하지만이 awk빠르게 현재의 시스템보다, 그리고, 당신과 당신의 컴퓨터 (들에게) 몇 가지 호흡 공간을 제공 당신이 설명한대로 데이터 처리와 같은 강렬한 때, 당신은 전용 데이터베이스로 전환하여 최적의 전체 결과를 얻을 것을 알고있을 것이다; 예. 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


이것은 빠르지 만 어색한 것을 이해하지 못합니다. 파일 이름은 어떻게 생겼습니까? 나는 시도 file1 -> mainfile하고 file2 -> keys둔한 및 mawk의와, 그것은 잘못된 키를 출력합니다.
Teresa e Junior

file1에는 키, 이름 및 작업이 있습니다.
Teresa e Junior

'mainfile'은 큰 파일입니다 (키, 이름 및 작업 포함). '내가하는 (파일 2 대 FILE1)이었다 파일 혼합 위로를 받고 유지하기 때문에 ..'난 그냥 "mainfile를 호출 한 키는 '에만 10,000, 또는 그러나 많은 키 .. 어떻게하지 귀하의 situaton를 들어 포함 리디렉션 anyting를. .. 그냥 file1을 사용하십시오. EOF file2 파일 의 이름 입니다. "EOF"는 스크립트가 첫 번째 파일 (메인 데이터 파일)의 끝과 두 번째 파일 ( awk일련의 파일을 읽을 수 있습니다 . 이 경우 해당 계열에 3 개의 파일이 있습니다. 출력은stdout
Peter.O

이 스크립트는 현재에있는 모든 키를 출력 할 것이다 mainfile, 그리고 그것은 또한에서 어떤 키를 인쇄 할 수 keys있는 파일 NOTmainfile... 그 (나는 ... 더 그것으로 조금 살펴 보겠습니다 ... 무슨 일이 일어나고 있는지 아마를
베드로 .O

감사합니다, @ Peter.O! 파일은 기밀이므로 $RANDOM업로드 할 샘플 파일을 만들려고합니다 .
Teresa e Junior

16

물론 문제는 큰 파일에서 grep을 10,000 번 실행한다는 것입니다. 두 파일을 한 번만 읽어야합니다. 스크립팅 언어 외부에 머 무르려면 다음과 같이하십시오.

  1. 파일 1에서 모든 숫자를 추출하여 정렬
  2. 파일 2에서 모든 숫자를 추출하여 정렬
  3. comm정렬 된 목록에서 실행 하여 두 번째 목록에만있는 것을 얻으십시오.

이 같은:

$ 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

참조하십시오 man comm.

매일 큰 파일 (예 : 로그 파일)을자를 수 있다면 정렬 된 숫자의 캐시를 유지할 수 있으며 매번 파싱 할 필요가 없습니다.


1
산뜻한! 메인 파일에 200,000 개의 임의 라인 항목 (예 : 600,000 라인)과 143,000 개의 임의 키 (테스트 데이터가 끝난 방식) ... 2 초 (특히 빠른 드라이브는 아님) ... ) ..... {12}OP가 12를 사용했지만 궁금한 점은 키가 13 개입니다.
Peter.O

2
참고로 <(grep...sort)파일 이름의 위치 를 사용하여 임시 파일을 처리하지 않고도 수행 할 수 있습니다 .
Kevin

고맙지 만 파일을 grepping하고 정렬하는 것은 이전 루프보다 훨씬 오래 걸립니다 (+2 분).
Teresa e Junior

@Teresa e Junior. 메인 파일이 얼마나 큽니까? ... 당신은 하루에 20 만 줄씩 성장한다고 말했지만 그 정도는 크지 않습니다 ... 처리해야 할 데이터의 양을 줄이려면 현재의 200,000 줄만 읽을 수 있습니다. 처리 된 마지막 줄 번호 (어제)를 사용 tail -n +$linenum하여 최신 데이터 만 출력합니다. 이렇게하면 매일 약 200,000 줄만 처리 할 수 ​​있습니다. 방금 메인 파일에 6 백만 줄과 10 만 개의 키로 테스트했습니다 . 시간 : 실제 0m0.016s, 사용자 0m0.008s, sys 0m0.008s
Peter.O

정말 아주 / 당신이 당신의 기본 파일 grep을 할 수있는 방법에 대해 호기심이 의아해하고 있습니다 시간을 빠르게 단지를 greps이 방법보다 그것을 발견 한 번 (그리고 훨씬 작은을 위해 일단 파일 1 ) 귀하의 종류보다 오래 걸립니다 ...해도 내 테스트, 나는 단지 여러 번 파일을 읽는 것이 단일 종류보다 시간이 오래 걸리지 않는다는 생각에 대해 머리를 맞출 수 없다.
Peter.O

8

예, 확실히 데이터베이스를 사용하십시오. 그들은 이와 같은 작업을 위해 정확하게 만들어졌습니다.


감사! 데이터베이스에 대한 경험이 많지 않습니다. 어떤 데이터베이스를 추천 하시겠습니까? MySQL과 sqlite3 명령이 설치되어 있습니다.
Teresa e Junior

1
sqlite는 기본적으로 파일과 파일에 액세스하는 SQL API이기 때문에 더 간단합니다. MySQL을 사용하려면 MySQL 서버를 설정해야합니다. 그다지 어렵지는 않지만 sqlite가 가장 좋습니다.
Mika Fischer

3

이것은 당신을 위해 일할 수 있습니다 :

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

편집하다:

두 파일 모두에서 복제본과 알 수없는 키를 허용하도록 수정 된 스크립트는 여전히 두 번째 파일에없는 첫 번째 파일에서 키를 생성합니다.

 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

주 파일에서 두 번 이상 발생하는 새 키가 누락됩니다 (키 파일에서 두 번 이상 발생하는 문제) 주 파일의 배열 수 증가가 1을 초과하지 않아야합니다. 또는 이에 상응하는 해결 방법 (마크에 매우 근접하여 +1)
Peter.O

1
gawk와 mawk로 시도했는데 잘못된 키가 출력됩니다 ...
Teresa e Junior

@ Peter.OI는 주 파일에 고유 키가 있고 그 파일 2가 주 파일의 하위 세트라고 가정했습니다.
potong

@potong 두 번째는 훌륭하고 빠르게 작동합니다! 감사합니다!
Teresa e Junior

@Teresa e Junior 아직 제대로 작동하고 있습니까? .. 제공테스트 데이터를 사용하여 5000 개의 키를 출력해야하는데 ,이를 실행할 때 136703 개의 키가 생성 됩니다. ... @potong 물론입니다! FNR == NR (이전에 사용한 적이 없습니다.)
Peter.O

2

많은 양의 데이터를 사용하면 실제로 데이터베이스로 전환해야합니다. 그 동안 적절한 성능을 발휘하기 위해해야 ​​할 한 가지는 file1각 키를 개별적 으로 검색하지 않는 것 입니다. 하나 grep를 실행하여 모든 제외되지 않은 키를 한 번에 추출하십시오. grep키가 포함되지 않은 행도 반환 하므로 해당 행을 필터링하십시오.

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

( -Fx문자 그대로 전체 행을 검색하는 것을 의미합니다. -f -표준 입력에서 패턴 목록을 읽는 것을 의미합니다.)


내가 실수하지 않으면 큰 파일에없는 키를 저장하는 문제를 해결하지 못하며 키가 저장되어 있습니다.
Kevin

@Kevin 정확하게, 이것은 루프를 사용하도록 강요했습니다.
Teresa e Junior

@TeresaeJunior : -v( -Fxv)를 추가하면 처리 할 수 ​​있습니다.
추후 공지가있을 때까지 일시 중지되었습니다.

@DennisWilliamson 이름, 작업 등 키 파일과 일치하지 않는 큰 파일의 모든 행을 선택합니다.
Kevin

@ 케빈 감사합니다, 나는 질문을 잘못 읽었습니다. 키가 아닌 줄에 대한 필터를 추가했지만 이제는 기본 설정이을 사용comm 합니다.
Gilles 'SO- 악마 그만해'

2

다른 사람들이 "데이터베이스에 데려다주십시오!"라고 말한 것을 강화하도록 허락하십시오.

대부분의 플랫폼에서 무료로 사용할 수 있는 MySQL 바이너리가 있습니다.

왜 SQLite가 아닌가? 메모리 기반이며 플랫 파일을 시작할 때로드 한 다음 완료되면 닫습니다. 이것은 컴퓨터가 고장 나거나 SQLite 프로세스가 사라지면 모든 데이터도 마찬가지라는 것을 의미합니다.

문제는 몇 줄의 SQL처럼 보이고 밀리 초 단위로 실행됩니다!

MySQL을 설치 한 후 (다른 선택을 권장합니다) Anthony Molinaro 의 O'Reilly의 SQL Cookbook 에 대해 40 달러를 깎았습니다 SELECT * FROM table.


예, 며칠 안에 데이터를 SQL로 마이그레이션하기 시작합니다. 감사합니다! awk 스크립트는 내가 다 끝낼 때까지 많은 도움을 주었다!
Teresa e Junior

1

이것이 당신이 찾고있는 정확한 결과인지 확실하지 않지만 아마도 가장 쉬운 방법은 다음과 같습니다.

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

다음을 사용할 수도 있습니다.

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

이들 각각은 큰 파일 ( file1) 에서 숫자를 수집하는 데 사용되는 임시 패턴 파일을 만듭니다 .


나는 이것도 큰 파일에없는 숫자가 아니라 큰 파일에 있다고 생각합니다.
Kevin

맞습니다, 나는 '!'를 보지 못했습니다 OP에서. grep -vf대신에 사용해야 합니다 grep -f.
Arcege

2
@arcege, grep -vf는 일치하지 않는 키를 표시하지 않으며 이름과 작업을 포함한 모든 것을 표시합니다.
Teresa e Junior

1

나는 당신이 데이터베이스를 얻는 것에 완전히 동의합니다 (MySQL은 사용하기 쉽습니다). 당신이 그 러닝을 얻을 때까지 나는 앵거스의 comm솔루션을 좋아 하지만 너무 많은 사람들이 노력하고 grep있고 잘못하고 있습니다 grep.

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

첫 번째 grep는 키를 얻습니다. 세 번째 grep( <(...))는 큰 파일에 사용 된 모든 키를 가져오고 두 번째 grep에서 <(...)인수로 파일처럼 전달합니다 -f. 두 번째 grep는 일치하는 줄 목록으로 사용합니다. 그런 다음이를 사용하여 파이프 (first grep) 의 입력 (키 목록)을 일치 -v시키고 큰 파일이 아닌 키 파일에서 추출 된 키를 인쇄 합니다.

물론 추적하고 삭제해야하는 임시 파일을 사용하여이 작업을 수행 할 수 있습니다.

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

allkeys표시되지 않은 모든 줄이 인쇄 됩니다 usedkeys.


불행히도 속도느리고 40 초 후에 메모리 오류가 발생합니다.grep: Memory exhausted
Peter.O

@ Peter.O 그러나 맞습니다. 어쨌든, 그래서 데이터베이스 또는를 comm순서대로 제안하는 이유 입니다.
Kevin

예, 작동하지만 루프보다 훨씬 느립니다.
Teresa e Junior

1

키 파일이 변경되지 않습니까? 그런 다음 이전 항목을 반복해서 검색하지 않아야합니다.

와 함께 tail -f증가하는 파일의 출력을 얻을 수 있습니다.

tail -f growingfile | grep -f keyfile 

grep -f는 파일에서 패턴을 한 줄씩 읽습니다.


좋겠지 만 키 파일은 항상 다릅니다.
Teresa e Junior

1

그런 양의 데이터는 쉘 스크립트로 처리해서는 안되며 데이터베이스를 사용하는 올바른 대답이 이미 제공되어 있다고 생각했기 때문에 대답을 게시하지 않았습니다. 그러나 지금부터 7 가지 다른 접근법이 있습니다 ...

첫 번째 파일을 메모리에서 읽은 다음 두 번째 파일의 숫자를 확인하고 값이 메모리에 저장되어 있는지 확인합니다. grep전체 파일을로드 할 수있는 충분한 메모리가있는 경우 여러 개보다 빠릅니다 .

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

메모리가 충분하지만이 속도가 더 느립니다. 그래도 고마워!
Teresa e Junior

1

나는 이런 종류의 작업에 데이터베이스를 사용해야한다는 @ jan-steinman에 동의합니다 . 다른 답변에서 볼 수 있듯이 셸 스크립트로 솔루션을 해킹하는 방법은 많이 있지만 그 방법으로 코드를 사용하고 오래 유지하면 많은 고통을 겪을 수 있습니다 단 하루 만 보내면됩니다.

Linux 상자에 있다고 가정하면 Python v2.5 에서 sqlite3 라이브러리 가 포함 된 Python이 기본적으로 설치되어있을 가능성이 큽니다 . 다음을 사용하여 Python 버전을 확인할 수 있습니다.

% python -V
Python 2.7.2+

sqlite3 라이브러리를 사용하는 것이 좋습니다 는 모든 플랫폼 (웹 브라우저 내부 포함)에 존재하는 간단한 파일 기반 솔루션이므로 서버를 설치할 필요가 없기 때문에 합니다. 본질적으로 제로 구성 및 제로 유지 보수.

다음은 예제로 제공 한 파일 형식을 구문 분석 한 후 간단한 "모두 선택"쿼리를 수행하고 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

예, 이는 SQL배워야 하지만 장기적으로는 그만한 가치가 있음을 의미합니다. 또한 로그 파일을 구문 분석하는 대신 sqlite 데이터베이스에 직접 데이터를 쓸 수 있습니다.


파이썬 스크립트 감사합니다! 생각 /usr/bin/sqlite3쉘 스크립트 (대한 동일한 방식으로 작동 packages.debian.org/squeeze/sqlite3을 내가 그것을 사용한 적이 있지만).
Teresa e Junior

예, /usr/bin/sqlite3쉘 스크립트와 함께 사용할 수는 있지만 간단한 폐기 프로그램을 제외하고 쉘 스크립트를 사용하지 말고 오류 처리 기능이 뛰어나고 유지 관리 및 확장이 쉬운 파이썬과 같은 언어를 사용하는 것이 좋습니다.
aculich
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.