แทนที่สตริงในขนาดใหญ่ (70GB) หนึ่งบรรทัดไฟล์ข้อความ


126

ฉันมีขนาดใหญ่ (70GB) หนึ่งบรรทัดไฟล์ข้อความและฉันต้องการแทนที่สตริง (โทเค็น) ในนั้น ฉันต้องการแทนที่โทเค็น<unk>ด้วยโทเค็นจำลองอีกตัว ( ปัญหาถุงมือ )

ฉันพยายามsed:

sed 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new

แต่ไฟล์ที่ส่งออกcorpus.txt.newมีศูนย์ไบต์!

ฉันยังลองใช้ Perl:

perl -pe 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new

แต่ฉันได้รับข้อผิดพลาดหน่วยความจำไม่เพียงพอ

สำหรับไฟล์ขนาดเล็กคำสั่งทั้งสองข้างต้นจะใช้งานได้

ฉันจะแทนที่สตริงเป็นไฟล์ดังกล่าวได้อย่างไร นี่เป็นคำถามที่เกี่ยวข้อง แต่ไม่มีคำตอบสำหรับฉันเลย

แก้ไข : สิ่งที่เกี่ยวกับการแยกไฟล์ในกลุ่มของ 10GBs (หรืออะไรก็ได้) และนำไปใช้sedกับแต่ละคนแล้วรวมกับพวกเขาcat? มันสมเหตุสมผลไหม มีวิธีแก้ปัญหาที่สง่างามกว่านี้ไหม?


ดังที่ @Gilles ตั้งข้อสังเกตคุณสามารถตรวจสอบตัวละครซ้ำบางตัวที่สามารถทำหน้าที่เป็นตัวคั่นที่กำหนดเองในสายใหญ่เดี่ยวของคุณได้หรือไม่?
RomanPerekhrest

ฉันคิดว่าเครื่องมือที่สามารถค้นหาและแทนที่ได้ แต่ไม่ใช่ regex ที่ซับซ้อนกว่านี้จะเร็วกว่า มันจะไม่ได้รับประโยชน์จากการทำสายในเวลาเดียวกันดังนั้นจะไม่ทำให้หายใจไม่ออกในไฟล์นี้ น่าเสียดายที่ฉันไม่มีความคิดที่จะมีเครื่องมือเช่นนี้แม้ว่ามันจะไม่ยากที่จะเขียน หากเป็นการปิดการใช้งานการแทนที่ด้วยอักขระขึ้นบรรทัดใหม่เหมือนกับหนึ่งในคำตอบอาจเป็นวิธีที่ง่ายที่สุด
ctrl-alt-delor

ไฟล์ของคุณมีอย่างอื่นนอกเหนือจาก ASCII หรือไม่? ถ้าเป็นเช่นนั้นการจัดการยูนิโค้ดทั้งหมดอาจถูกตัดออกและไบต์ดิบอาจถูกประมวลผล
Patrick Bucher

ฉันเห็นด้วยกับ @PatrickButcher ดูภาพใหญ่ขึ้น นอกจากความจำเป็นที่จะต้องแทนที่ข้อความนี้แล้วไฟล์นี้ควรจะใช้เพื่ออะไรอีก? หากเป็นบันทึกบางประเภทจะไม่มีใครสามารถทำงานได้อย่างมีประสิทธิภาพ หากเป็นไฟล์ข้อมูลที่บางแอปใช้แอปนั้นควรรับผิดชอบในการรักษาข้อมูลในไฟล์นั้น
โทมัสคาร์ไลเซิล

2
คุณสามารถใช้splitกับ-bตัวเลือกที่กำหนดขนาดของไฟล์ก้อนเป็นไบต์ ประมวลผลตามลำดับโดยใช้sedและประกอบใหม่ มีความเสี่ยงคือ<unk>สามารถแยกไฟล์ได้สองไฟล์และจะไม่พบ ...
Vladislavs Dovgalecs

คำตอบ:


106

เครื่องมือประมวลผลข้อความตามปกติไม่ได้ออกแบบมาเพื่อจัดการกับสายที่ไม่พอดีกับ RAM พวกเขามีแนวโน้มที่จะทำงานโดยการอ่านหนึ่งระเบียน (หนึ่งบรรทัด) จัดการกับมันและส่งออกผลลัพธ์จากนั้นดำเนินการไปยังระเบียนถัดไป (บรรทัด)

หากมีอักขระ ASCII ที่ปรากฏขึ้นบ่อยครั้งในไฟล์และไม่ปรากฏใน<unk>หรือ<raw_unk>คุณสามารถใช้มันเป็นตัวแยกเรคคอร์ด เนื่องจากเครื่องมือส่วนใหญ่ไม่อนุญาตให้มีตัวคั่นเร็กคอร์ดที่กำหนดเองให้สลับระหว่างตัวละครนั้นกับบรรทัดใหม่ trประมวลผลเป็นไบต์ไม่ใช่บรรทัดดังนั้นจึงไม่สนใจขนาดเรคคอร์ดใด ๆ เผื่อว่าจะได้;ผล:

<corpus.txt tr '\n;' ';\n' |
sed 's/<unk>/<raw_unk>/g' |
tr '\n;' ';\n' >corpus.txt.new

คุณสามารถยึดกับอักขระตัวแรกของข้อความที่คุณกำลังค้นหาโดยสมมติว่าข้อความนั้นไม่ซ้ำในข้อความค้นหาและปรากฏบ่อยครั้งพอ หากไฟล์อาจขึ้นต้นด้วยunk>ให้เปลี่ยนคำสั่ง sed เป็นsed '2,$ s/…เพื่อหลีกเลี่ยงการจับคู่แบบเก๊

<corpus.txt tr '\n<' '<\n' |
sed 's/^unk>/raw_unk>/g' |
tr '\n<' '<\n' >corpus.txt.new

หรือใช้อักขระตัวสุดท้าย

<corpus.txt tr '\n>' '>\n' |
sed 's/<unk$/<raw_unk/g' |
tr '\n>' '>\n' >corpus.txt.new

โปรดทราบว่าเทคนิคนี้ถือว่า sed ทำงานได้อย่างราบรื่นบนไฟล์ที่ไม่ได้จบด้วยการขึ้นบรรทัดใหม่นั่นคือมันจะประมวลผลบางส่วนของบรรทัดสุดท้ายโดยไม่ตัดทอนและไม่ต้องต่อท้าย newline สุดท้าย มันทำงานร่วมกับ GNU sed หากคุณสามารถเลือกอักขระสุดท้ายของไฟล์เป็นตัวคั่นเร็กคอร์ดคุณจะหลีกเลี่ยงปัญหาการพกพา


8
ฉันไม่มีไฟล์ดังกล่าวที่จะทดสอบด้วย แต่ใน Awk คุณสามารถระบุ "ตัวแยกเร็กคอร์ด" และ "ตัวแยกเร็กคอร์ดเอาท์พุท" ดังนั้นสมมติว่าคุณมีเครื่องหมายจุลภาคที่ดีในไฟล์ของคุณมันเป็นไปได้ที่คุณจะแก้ปัญหานี้ด้วย: awk -v RS=, -v ORS=, '{gsub(/<unk>/, "<raw_unk>"); print}' ไม่?
สัญลักษณ์แทน

4
@ Wildcard ใช่นั่นเป็นอีกวิธีหนึ่ง Awk มีแนวโน้มที่จะช้ากว่า sed แม้ว่านั่นเป็นเหตุผลที่ฉันไม่ได้เสนอเป็นโซลูชั่นที่ต้องการสำหรับไฟล์ขนาดใหญ่
Gilles

คุณสามารถตั้งค่าตัวคั่นเรคคอร์ดใน Perl พร้อมตัวเลือกบรรทัดคำสั่ง-0และค่าฐานแปดของถ่านหรือภายในสคริปต์ก็สามารถตั้งค่าด้วยตัวแปรพิเศษ$/
beasy

@Gilles: แต่การใช้หลีกเลี่ยงการผ่านกระแสสองครั้งเพื่อawk trดังนั้นมันจะช้าลงหรือไม่
285259

2
@ user285259 โดยทั่วไปไม่ trเร็วมากและสามารถต่อขนานกับท่อได้
Gilles

110

สำหรับไฟล์ขนาดใหญ่ความเป็นไปได้อย่างหนึ่งคือเฟล็กซ์ อนุญาตunk.l:

%%
\<unk\>     printf("<raw_unk>");  
%%

จากนั้นรวบรวมและดำเนินการ:

$ flex -o unk.c  unk.l
$ cc -o unk -O2 unk.c -lfl
$ unk < corpus.txt > corpus.txt.new

5
makeมีกฎระเบียบที่เริ่มต้นสำหรับการนี้แทนเฟล็กซ์ / ซีซีคุณสามารถเพิ่ม%option mainเป็นบรรทัดแรกของ unk.l make unkแล้วเพียงแค่ ฉันมากขึ้นหรือน้อยลงโดยอัตโนมัติใช้%option main 8bit fastและมีในของฉันexport CFLAGS='-march=native -pipe -Os' .bashrc
2560

1
@undercat: ถ้าไม่ใช่หัวข้อฉันสามารถแสดงให้คุณเห็นถึงแอพพลิเคชั่นส่วนหน้าแบบไม่คอมไพเลอร์ตั้งแต่การแก้ปัญหาระดับน้ำไปจนถึงการวิเคราะห์คำที่มีวัตถุประสงค์พิเศษ มันน่าพิศวงสิ่งที่คุณสามารถทำอะไรกับมันถ้าคุณคิดนอกกรอบนิด :-)
jamesqf

@jthill ขอขอบคุณ: %option main+ make+ เลือกCFLAGSเป็นเคล็ดลับที่ดีมาก !! เป็น-march=nativeพฤติกรรมเริ่มต้นหรือไม่
JJoao

1
@jamesqf ตามที่คุณพูด - จะยากที่จะทำให้คำถามในหัวข้อ - แต่ฉันต้องการเห็นมันยัง
Steven Penny

1
@jamesqf ผู้เชี่ยวชาญของฉันที่ uni ใช้ flex เพื่อสร้างเครื่องมือที่รู้จักประเภทผ้าสำหรับโรงงาน! ลองถามสิ่งที่ชอบ: "flex ดูเหมือนว่าเป็นเครื่องมือที่ทรงพลังมาก แต่ฉันไม่น่าจะเขียน compilers / parsers ใด ๆ - มีกรณีใช้อื่น ๆ สำหรับ flex หรือไม่"
พอลอีแวนส์

41

ดังนั้นคุณไม่มีหน่วยความจำกายภาพ (RAM) เพียงพอที่จะเก็บไฟล์ทั้งหมดพร้อมกัน แต่ในระบบ 64 บิตคุณมีพื้นที่ที่อยู่เสมือนเพียงพอที่จะแมปไฟล์ทั้งหมด การจับคู่เสมือนอาจมีประโยชน์เหมือนกับการแฮ็กแบบง่ายๆในกรณีเช่นนี้

การดำเนินการที่จำเป็นทั้งหมดรวมอยู่ใน Python มีรายละเอียดปลีกย่อยที่น่ารำคาญหลายอย่าง แต่ไม่ควรเขียนรหัส C โดยเฉพาะอย่างยิ่งการดูแลเป็นสิ่งจำเป็นเพื่อหลีกเลี่ยงการคัดลอกไฟล์ในหน่วยความจำซึ่งจะเอาชนะจุดทั้งหมด ในด้านบวกคุณจะได้รับรายงานข้อผิดพลาดฟรี (หลาม "ข้อยกเว้น") :)

#!/usr/bin/python3
# This script takes input from stdin
# (but it must be a regular file, to support mapping it),
# and writes the result to stdout.

search = b'<unk>'
replace = b'<raw_unk>'


import sys
import os
import mmap

# sys.stdout requires str, but we want to write bytes
out_bytes = sys.stdout.buffer

mem = mmap.mmap(sys.stdin.fileno(), 0, access=mmap.ACCESS_READ)
i = mem.find(search)
if i < 0:
    sys.exit("Search string not found")

# mmap object subscripts to bytes (making a copy)
# memoryview object subscripts to a memoryview object
# (it implements the buffer protocol).
view = memoryview(mem)

out_bytes.write(view[:i])
out_bytes.write(replace)
out_bytes.write(view[i+len(search):])

หากระบบของฉันมีหน่วยความจำเหลืออยู่ประมาณ 4 gb ทำให้หน่วยความจำว่างจาก 8 gb, mem = mmap.mmap (sys.stdin.fileno (), 0, access = mmap.ACCESS_READ) หมายความว่าวางข้อมูลไว้ในพื้นที่นั้นหรือไม่? หรือมันจะต่ำกว่ามาก (1gb?)>
ราหุล

1
@Rahul "ดังนั้นคุณไม่มี RAM เพียงพอ แต่ในระบบ 64 บิตคุณมีพื้นที่ที่อยู่เสมือนเพียงพอที่จะแมปไฟล์ทั้งหมด" มันเป็นเพจเข้าและออกจาก ram ทางกายภาพตามความต้องการ (หรือขาดมัน) โปรแกรมนี้ควรทำงานได้โดยไม่ต้องใช้ฟิสิคัลแรมจำนวนมาก ระบบ 64 บิตมีพื้นที่ที่อยู่เสมือนมากกว่า RAM จริงสูงสุด แต่ละกระบวนการที่ทำงานอยู่นั้นจะมีพื้นที่ที่อยู่เสมือนของตัวเอง ซึ่งหมายความว่าระบบทั้งหมดที่ใช้พื้นที่ว่างของที่อยู่เสมือนไม่ใช่สิ่งใดสิ่งหนึ่งมันไม่ใช่แนวคิดที่ถูกต้อง
sourcejedi

4
@Rahul yep! หลาม mmap.mmap () เป็นเสื้อคลุมที่ค่อนข้างบางรอบ ๆ ฟังก์ชั่น C mmap () และ mmap () เป็นกลไกเดียวกับที่ใช้ในการรันโปรแกรมเรียกทำงานและรหัสจากไลบรารีที่แชร์
sourcejedi

2
@jamesqf ฉันอาจจะผิด แต่ฉันรู้สึกว่ามันเป็นเพียงตัวเลือกส่วนบุคคล เนื่องจากการสูญเสียประสิทธิภาพจะน้อยมาก (เพราะเขากล่าวว่าฟังก์ชั่นที่เกิดขึ้นจริงเรียกว่าฟังก์ชั่น c) การสิ้นเปลืองค่าโสหุ้ยต่ำมากเนื่องจากไม่มีสิ่งอื่นใดเกิดขึ้นระหว่างนั้น C น่าจะดีกว่านี้ แต่โซลูชันนี้ไม่ได้มีเป้าหมายเพื่อเพิ่มประสิทธิภาพเพียงเพื่อแก้ปัญหา 70gb ที่ใหญ่และยากขึ้น
ราหุล

1
โดยทั่วไปการเขียนในไพ ธ อนมีขนาดเล็กลง ในกรณีนี้มันกลับกลายเป็นว่ามีรายละเอียดบางอย่างในเวอร์ชั่นหลามและรุ่น C อาจจะดีกว่าที่จะเขียน (แม้ว่ามันจะไม่ง่ายเลยหากsearchมีตัวอักษร NUL อยู่และฉันสังเกตว่ารุ่น C อื่น ๆ ที่นี่ไม่รองรับตัวอักษร NUL replace) เรายินดีอย่างยิ่งที่ได้รับเวอร์ชัน C สำหรับการเปรียบเทียบ อย่างไรก็ตามโปรดจำไว้ว่ารุ่นของฉันมีการรายงานข้อผิดพลาดพื้นฐานสำหรับการดำเนินการที่ทำ อย่างน้อยเวอร์ชั่น C น่าจะน่ารำคาญกว่าที่จะอ่าน IMO เมื่อมีการรายงานข้อผิดพลาด
sourcejedi

17

ฉันคิดว่าเวอร์ชั่น C อาจทำงานได้ดีขึ้นมาก:

#include <stdio.h>
#include <string.h>

#define PAT_LEN 5

int main()
{
    /* note this is not a general solution. In particular the pattern
     * must not have a repeated sequence at the start, so <unk> is fine
     * but aardvark is not, because it starts with "a" repeated, and ababc
     * is not because it starts with "ab" repeated. */
    char pattern[] = "<unk>";          /* set PAT_LEN to length of this */
    char replacement[] = "<raw_unk>"; 
    int c;
    int i, j;

    for (i = 0; (c = getchar()) != EOF;) {
        if (c == pattern[i]) {
            i++;
            if (i == PAT_LEN) {
                printf("%s", replacement);
                i = 0;
            }
        } else {
            if (i > 0) {
                for (j = 0; j < i; j++) {
                    putchar(pattern[j]);
                }
                i = 0;
            }
            if (c == pattern[0]) {
                i = 1;
            } else {
                putchar(c);
            }
        }
    }
    /* TODO: fix up end of file if it ends with a part of pattern */
    return 0;
}

แก้ไข: แก้ไขตามคำแนะนำจากความคิดเห็น <<unk>ข้อผิดพลาดคงยังมีรูปแบบ


2
คุณอาจพิมพ์ (รูปแบบ [j]) แทน (buf [j]) (เท่ากัน ณ จุดนี้ดังนั้นคุณไม่จำเป็นต้องใช้บัฟเฟอร์
RiaD

3
รหัสจะไม่ทำงานกับสตริง "<" ideone.com/ncM2yy
RiaD

10
30 MB ใน 0.3 วินาที? นั่นเป็นเพียง 90 MB / วินาที memcpyความเร็ว (เช่นคอขวดของหน่วยความจำ) นั้นคล้ายกับ 12GB / วินาทีสำหรับซีพียู x86 ล่าสุด (เช่น Skylake) แม้ว่าจะมี stdio + system call overhead สำหรับไฟล์ 30MB ที่ร้อนแรงในแคชดิสก์ผมคาดว่าอาจจะ 1GB / วินาทีสำหรับการติดตั้งที่มีประสิทธิภาพ คุณได้คอมไพล์ด้วยการออปติไมซ์ที่ปิดใช้งานหรือ I / O แบบใช้ครั้งละถ่านหนึ่งตัวนั้นช้ามากหรือไม่? getchar_unlocked/ putchar_unlockedอาจช่วย แต่แน่นอนดีกว่าที่จะอ่าน / เขียนในชิ้นอาจ 128kiB (ครึ่งหนึ่งของขนาดแคช L2 ในส่วนซีพียู x86 เพื่อให้คุณตีส่วนใหญ่ในขณะที่วนลูป L2 หลังจากอ่าน)
ปีเตอร์ Cordes

2
จากส่วนบนของหัวฉัน getchar และ putchar ก็ช้า
Rui F Ribeiro

3
fixในการเขียนโปรแกรมสำหรับ"<<unk>"ยังคงไม่ทำงานถ้าpatternเริ่มต้นด้วยซ้ำลำดับของตัวอักษร (คือมันจะไม่ทำงานถ้าคุณกำลังพยายามที่จะแทนที่มดกับม้าลายและคุณต้องใส่ของ aaardvak หรือคุณกำลังพยายามที่จะเข้ามาแทนที่และ ababc มีอินพุตของ abababc) โดยทั่วไปคุณไม่สามารถก้าวไปข้างหน้าตามจำนวนตัวอักษรที่คุณอ่านเว้นแต่คุณจะรู้ว่าไม่มีความเป็นไปได้ที่จะมีการแข่งขันเริ่มขึ้นในตัวละครที่คุณอ่าน
รัส

16

มีreplaceยูทิลิตี้ในแพ็คเกจ mariadb-server / mysql-server มันแทนที่สตริงง่าย (ไม่แสดงออกปกติ) และแตกต่างจาก grep / sed / awk replaceไม่สนใจเกี่ยวกับและ\n \0การใช้หน่วยความจำคงที่เมื่อมีไฟล์อินพุต (ประมาณ 400kb บนเครื่องของฉัน)

แน่นอนคุณไม่จำเป็นต้องเรียกใช้เซิร์ฟเวอร์ mysql เพื่อที่จะใช้replaceมันเป็นแพคเกจแบบนั้นใน Fedora เท่านั้น distros / ระบบปฏิบัติการอื่น ๆ อาจแยกบรรจุเป็นชุด


14

GNU grepสามารถแสดงออฟเซตของการจับคู่ในไฟล์ "ไบนารี" โดยไม่ต้องอ่านทั้งบรรทัดในหน่วยความจำ จากนั้นคุณสามารถใช้ddอ่านค่าออฟเซ็ตนี้ข้ามการแข่งขันแล้วคัดลอกไฟล์ต่อไป

file=...
newfile=...
replace='<raw_unk>'
grep -o -b -a -F '<unk>' <"$file" |
(   pos=0
    while IFS=$IFS: read offset pattern
    do size=${#pattern}
       let skip=offset-pos
       let big=skip/1048576
       let skip=skip-big*1048576
       dd bs=1048576 count=$big <&3
       dd bs=1 count=$skip <&3
       dd bs=1 count=$size of=/dev/null <&3
       printf "%s" "$replace"
       let pos=offset+size
    done
    cat <&3
) 3<"$file" >"$newfile"

สำหรับความเร็วฉันได้แบ่งการddอ่านขนาดใหญ่เป็นบล็อคของ 1048576 และการอ่านครั้งละ 1 ไบต์ที่มีขนาดเล็กลง แต่การดำเนินการนี้จะช้าลงเล็กน้อยสำหรับไฟล์ขนาดใหญ่ grepเอาท์พุทเป็นเช่น13977:<unk>นี้และแยกลำไส้ใหญ่โดยการอ่านลงในตัวแปรและoffset patternเราต้องติดตามposจำนวนไบต์ที่ถูกคัดลอกจากไฟล์


11

นี่คือบรรทัดคำสั่ง UNIX บรรทัดเดียวที่อาจทำงานได้ดีกว่าตัวเลือกอื่นเนื่องจากคุณสามารถ "ค้นหา" สำหรับ "ขนาดบล็อก" ที่ทำงานได้ดี คุณต้องรู้ว่าคุณมีช่องว่างอย่างน้อยหนึ่งช่องในอักขระ X ทุกตัวโดยที่ X คือขนาดบล็อกของคุณ ในตัวอย่างด้านล่างฉันได้เลือก "ขนาดบล็อก" ของ 1024 ตัวอักษร

fold -w 1024 -s corpus.txt | sed 's/<unk>/<raw_unk>/g' | tr '/n' '/0'

ที่นี่โฟลด์จะคว้าได้สูงสุด 1024 ไบต์ แต่ -s ทำให้แน่ใจว่ามันหยุดบนช่องว่างถ้ามีอย่างน้อยหนึ่งอันนับตั้งแต่การพักครั้งล่าสุด

คำสั่ง sed เป็นของคุณและทำสิ่งที่คุณคาดหวัง

จากนั้นคำสั่ง tr จะ "คลาย" ไฟล์ที่แปลงบรรทัดใหม่ที่แทรกกลับไปเป็นอะไร

คุณควรลองใช้ขนาดบล็อกที่ใหญ่ขึ้นเพื่อดูว่ามันทำงานได้เร็วขึ้นหรือไม่ แทนที่จะเป็น 1024 คุณอาจลอง 10240 และ 102400 และ 1048576 สำหรับตัวเลือก -w การพับ

นี่คือตัวอย่างที่แยกตามแต่ละขั้นตอนที่แปลง N ทั้งหมดเป็นตัวพิมพ์เล็ก:

[root@alpha ~]# cat mailtest.txt
test XJS C4JD QADN1 NSBN3 2IDNEN GTUBE STANDARD ANTI UBE-TEST EMAIL*C.34X test

[root@alpha ~]# fold -w 20 -s mailtest.txt
test XJS C4JD QADN1
NSBN3 2IDNEN GTUBE
STANDARD ANTI
UBE-TEST
EMAIL*C.34X test

[root@alpha ~]# fold -w 20 -s mailtest.txt | sed 's/N/n/g'
test XJS C4JD QADn1
nSBn3 2IDnEn GTUBE
STAnDARD AnTI
UBE-TEST
EMAIL*C.34X test

[root@alpha ~]# fold -w 20 -s mailtest.txt | sed 's/N/n/g' | tr '\n' '\0'
test XJS C4JD QADn1 nSBn3 2IDnEn GTUBE STAnDARD AnTI UBE-TEST EMAIL*C.34X test

คุณจะต้องเพิ่มการขึ้นบรรทัดใหม่ไปยังส่วนท้ายสุดของไฟล์หากมีอย่างใดอย่างหนึ่งเนื่องจากคำสั่ง tr จะลบออก


1
คุณจะแน่ใจได้อย่างไรว่าคุณไม่ได้ทำลายรูปแบบในกรณีขอบซึ่งมีพื้นที่ว่างไม่เพียงพอ?
rackandboneman

1
ตามที่ระบุไว้สำหรับสิ่งนี้เพื่อความแข็งแกร่งมีข้อกำหนดว่ามีอย่างน้อยหนึ่งช่องว่างทุกอักขระ X คุณสามารถทำการวิเคราะห์นั้นง่ายพอโดยใช้ขนาดบล็อกที่คุณเลือก: fold -w X mailtest.txt | grep -v "" | wc -l จำนวนที่ส่งคืนคือจำนวนบรรทัดที่ถูกพับด้วยเคสขอบที่เป็นไปได้ หากเป็นศูนย์โซลูชันจะรับประกันว่าจะใช้งานได้
alfreema

10

การใช้ perl

จัดการบัฟเฟอร์ของคุณเอง

คุณสามารถใช้IO::Handle's setvbufในการจัดการบัฟเฟอร์เริ่มต้นหรือคุณสามารถจัดการบัฟเฟอร์ของคุณเองด้วยและsysread syswriteตรวจสอบperldoc -f sysreadและperldoc -f syswriteดูข้อมูลเพิ่มเติมโดยพื้นฐานแล้วพวกเขาจะข้ามบัฟเฟอร์ io

ที่นี่เรากลิ้งบัฟเฟอร์ของเราเอง IO แต่เราทำเองและโดยพลการใน 1024 ไบต์ เรายังเปิดไฟล์สำหรับ RW เพื่อให้เราทำมันทั้งหมดใน FH เดียวกันในครั้งเดียว

use strict;
use warnings;
use Fcntl qw(:flock O_RDWR);
use autodie;
use bytes;

use constant CHUNK_SIZE => 1024 * 32;

sysopen my $fh, 'file', O_RDWR;
flock($fh, LOCK_EX);

my $chunk = 1;
while ( sysread $fh, my $bytes, CHUNK_SIZE * $chunk ) {
  if ( $bytes =~ s/<unk>/<raw_unk>/g ) {
    seek( $fh, ($chunk-1)* CHUNK_SIZE, 0 );
    syswrite( $fh, $bytes, 1024);
    seek( $fh, $chunk * CHUNK_SIZE, 0 );
  }
  $chunk++;
}

หากคุณกำลังจะไปเส้นทางนี้

  1. ตรวจสอบให้แน่ใจ<unk>และ<raw_unk>มีขนาดไบต์เดียวกัน
  2. คุณอาจต้องการตรวจสอบให้แน่ใจว่าวิธีการบัฟเฟอร์ของเราไม่ข้ามCHUNKSIZEขอบเขตหากคุณเปลี่ยนมากกว่า 1 ไบต์

2
เกิดอะไรขึ้นถ้า<unk>ตกอยู่ในขอบเขตระหว่างชิ้น?
liori

8

คุณสามารถลองbbe (ตัวแก้ไขบล็อกไบนารี ) ซึ่งเป็น " sedสำหรับไฟล์ไบนารี"

ฉันประสบความสำเร็จในการใช้งานกับไฟล์ข้อความ 7GB โดยไม่มีEOLตัวอักษรแทนที่สตริงที่มีความยาวต่างกันหลายรายการ หากไม่พยายามเพิ่มประสิทธิภาพใด ๆ มันจะทำให้การประมวลผลโดยเฉลี่ย>> 50MB / s


5

ด้วยperlคุณสามารถทำงานกับบันทึกความยาวคงที่เช่น:

perl -pe 'BEGIN{$/=\1e8}
          s/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new

และหวังว่าจะไม่มีการ<unk>ขยายไปทั่วทั้งสองระเบียน 100MB เหล่านั้น


ฉันยังคิดเกี่ยวกับวิธีนี้ แต่ใช้while read -N 1000 chunk;( 1000เลือกเป็นตัวอย่าง) วิธีแก้ปัญหาสำหรับการ<unk>แบ่งระหว่างชิ้นคือสองผ่านผ่านไฟล์: ครั้งแรกกับชิ้น 100MB และครั้งที่สองกับชิ้น '100MB + 5 ไบต์' แต่มันไม่ใช่ทางออกที่ดีที่สุดในกรณีของไฟล์ 70GB
MiniMax

3
คุณไม่จำเป็นต้องผ่านสองครั้ง อ่านบล็อก A. ในขณะที่ไม่ใช่ EOF ให้อ่านบล็อก B. ค้นหา / แทนที่ใน A + B A: = B. Loop ความซับซ้อนคือการทำให้แน่ใจว่าคุณไม่ได้เปลี่ยนภายในแทน
roaima

@MiniMax ที่ผ่านที่สองจะไม่จำเป็นต้องช่วยเป็นครั้งแรกผ่านจะได้เพิ่ม 5 <unk>ไบต์สำหรับการเกิดขึ้นของแต่ละ
Stéphane Chazelas

1
@roaima ใช่ว่าจะเป็นทางออกที่เกี่ยวข้องมากขึ้น นี่เป็นวิธีการที่ง่ายซึ่งมีความเป็นไปได้สูงมาก (สมมติว่าสิ่งที่<unk>เกิดขึ้นนั้นเป็นสิ่งที่ห่างไกลมากหากไม่ใช่ใช้$/ = ">"และs/<unk>\z/<raw_unk>/g) ในการแก้ไขให้ถูกต้อง
Stéphane Chazelas

5

นี่คือโปรแกรม Go ขนาดเล็กที่ทำงาน ( unk.go):

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
)

func main() {
    const (
        pattern     = "<unk>"
        replacement = "<raw_unk>"
    )
    var match int
    var char rune
    scanner := bufio.NewScanner(os.Stdin)
    scanner.Split(bufio.ScanRunes)
    for scanner.Scan() {
        char = rune(scanner.Text()[0])
        if char == []rune(pattern)[match] {
            match++
            if match == len(pattern) {
                fmt.Print(replacement)
                match = 0
            }
        } else {
            if match > 0 {
                fmt.Print(string(pattern[:match]))
                match = 0
            }
            if char == rune(pattern[0]) {
                match = 1
            } else {
                fmt.Print(string(char))
            }
        }
    }
    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
}

เพียงแค่สร้างมันด้วยและเรียกมันว่าgo build unk.go./unk <input >output

แก้ไข:

ขออภัยฉันไม่ได้อ่านว่าทุกอย่างอยู่ในบรรทัดเดียวดังนั้นฉันพยายามอ่านตัวอักษรของไฟล์ทีละตัวอักษร

แก้ไขครั้งที่สอง:

ใช้โปรแกรมแก้ไขเดียวกันกับโปรแกรม C


1
สิ่งนี้จะหลีกเลี่ยงการอ่านไฟล์ทั้งหมดในหน่วยความจำ?
แมว

1
มันอ่านตัวอักษรของไฟล์โดยตัวละครและไม่เคยเก็บไฟล์ทั้งหมดในหน่วยความจำเพียงตัวละครแต่ละตัว
Patrick Bucher

1
scanner.Split(bufio.ScanRunes)ทำเวทย์มนตร์
Patrick Bucher

ตรวจสอบgo doc bufio.MaxScanTokenSizeขนาดบัฟเฟอร์เริ่มต้นด้วย
Patrick Bucher

เช่นเดียวกับCโปรแกรมของคุณวิธีนี้ใช้ไม่ได้กับการแทนที่ aardvark ด้วย zebra ด้วยอินพุตของ aaardvark
รัส

1

สิ่งนี้อาจเกินความจำเป็นสำหรับไฟล์ 70GB และการค้นหาและแทนที่อย่างง่าย แต่กรอบงาน Hadoop MapReduce จะช่วยแก้ปัญหาของคุณได้ทันทีโดยไม่ต้องเสียค่าใช้จ่าย (เลือกตัวเลือก 'โหนดเดียว' เมื่อตั้งค่าเพื่อเรียกใช้ภายในเครื่อง) - และสามารถ ปรับขนาดเป็นความจุไม่ จำกัด ในอนาคตโดยไม่จำเป็นต้องแก้ไขโค้ด

กวดวิชาอย่างเป็นทางการที่https://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.htmlใช้ (ง่ายมาก) Java แต่คุณสามารถค้นหาห้องสมุดลูกค้าสำหรับ Perl หรือ ภาษาอะไรก็ตามที่คุณรู้สึกอยากใช้

ดังนั้นหากในภายหลังคุณพบว่าคุณกำลังทำการดำเนินการที่ซับซ้อนมากขึ้นในไฟล์ข้อความ 7000GB - และต้องทำเช่นนี้ 100 ครั้งต่อวัน - คุณสามารถแจกจ่ายภาระงานข้ามหลายโหนดที่คุณจัดเตรียมไว้หรือจัดเตรียมไว้ให้คุณโดยอัตโนมัติ คลัสเตอร์ Hadoop


1
ใช่มันเป็น "อย่าใช้ Hadoop - ข้อมูลของคุณไม่ว่าใหญ่" นี่เป็นปัญหาการสตรีม IO อย่างง่าย
sourcejedi

0

คำแนะนำก่อนหน้านี้ทั้งหมดต้องการการอ่านไฟล์ทั้งหมดและการเขียนไฟล์ทั้งหมด สิ่งนี้ไม่เพียงใช้เวลานาน แต่ยังต้องการพื้นที่ว่าง 70GB

1) ถ้าฉันเข้าใจกรณีที่คุณระบุอย่างถูกต้องเป็นที่ยอมรับหรือไม่ที่จะแทนที่ด้วยสตริงอื่นของความยาว SAME

2a) มีเหตุการณ์หลายครั้งหรือไม่? 2b) ถ้าเช่นนั้นคุณรู้ว่ามีเท่าไหร่

ฉันแน่ใจว่าคุณได้แก้ไขปัญหาในปีนี้แล้วและฉันต้องการทราบว่าคุณใช้โซลูชันใด

ฉันเสนอวิธีแก้ปัญหา (เป็นไปได้มากที่สุดใน C) ที่จะอ่านบล็อกของไฟล์ที่ค้นหาแต่ละสายสำหรับคำนึงถึงการข้ามบล็อกที่เป็นไปได้ เมื่อพบว่าแทนที่สตริงด้วยความยาว SAME ทางเลือกและเขียนเฉพาะบล็อกนั้น ดำเนินการต่อสำหรับจำนวนเหตุการณ์ที่ทราบหรือจนถึงจุดสิ้นสุดไฟล์ สิ่งนี้จะต้องมีการเขียนจำนวนครั้งน้อยที่สุดและมากที่สุดเป็นสองเท่า (ถ้าการเกิดขึ้นทุกครั้งถูกแบ่งระหว่าง 2 บล็อก) นี้จะต้องไม่มีพื้นที่เพิ่มเติม!


-1

หากเรามีจำนวนขั้นต่ำ<unk>(ตามที่กฎหมายของ Zipf คาดไว้)

awk -v RS="<unk>" -v ORS="<raw_unk>" 1

1
ไม่sedอ่านบรรทัดต่อครั้งในหน่วยความจำโดยไม่คำนึงถึง มันจะไม่สามารถที่จะพอดีกับสายนี้
Kusalananda

1
ฉันไม่พบเอกสารใด ๆ ที่ระบุสิ่งอื่นนอกจาก GNU sedนั้นจะไม่ทำการบัฟเฟอร์อินพุต / เอาต์พุตเมื่อใช้แฟล็กนี้ ฉันไม่เห็นว่ามันจะอ่านบรรทัดบางส่วน
Kusalananda
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.