วิธีการแยกไฟล์ข้อความขนาดใหญ่อย่างมีประสิทธิภาพโดยไม่ต้องแยกหลายระเบียน


9

ฉันมีไฟล์ข้อความขนาดใหญ่ (~ 50Gb เมื่อ gz'ed) ไฟล์มี4*Nเส้นหรือNบันทึก นั่นคือทุกระเบียนประกอบด้วย 4 บรรทัด ฉันต้องการแบ่งไฟล์นี้เป็นไฟล์ขนาดเล็กลง 4 ไฟล์แต่ละไฟล์มีขนาดประมาณ 25% ของไฟล์อินพุต ฉันจะแบ่งไฟล์ที่ขอบเขตการบันทึกได้อย่างไร?

วิธีการที่ไร้เดียงสาจะเป็นzcat file | wc -lที่จะได้รับการนับเส้นแบ่งว่าจำนวน 4 split -l <number> fileและการใช้งานแล้ว อย่างไรก็ตามสิ่งนี้จะข้ามไฟล์สองครั้งและการนับบรรทัดช้ามาก (36 นาที) มีวิธีที่ดีกว่า?

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

แก้ไข:

ไฟล์มีข้อมูลลำดับในรูปแบบ fastq สองระเบียนมีลักษณะเช่นนี้ (ไม่ระบุชื่อ):

@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTTTATGTTTTTAATTAATTCTGTTTCCTCAGATTGATGATGAAGTTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFFFFFFFFFAFFFFF#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF<AFFFFFFFFFFAFFFFFFFFFFFFFFFFFFF<FFFFFFFFFAFFFAFFAFFAFFFFFFFFAFFFFFFAAFFF<FAFAFFFFA
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCCCTCTGCTGGAACTGACACGCAGACATTCAGCGGCTCCGCCGCCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFF7FFFFFFAFFFFA#F7FFFFFFFFF7FFFFFAF<FFFFFFFFFFFFFFAFFF.F.FFFFF.FAFFF.FFFFFFFFFFFFFF.)F.FFA))FFF7)F7F<.FFFF.FFF7FF<.FFA<7FA.<.7FF.FFFAFF

@บรรทัดแรกของแต่ละระเบียนจะเริ่มต้นด้วย

EDIT2:

zcat file > /dev/null ใช้เวลา 31 นาที

edit3: Onlye @บรรทัดแรกเริ่มต้นด้วย ไม่มีผู้อื่นเลย ดูที่นี่ บันทึกต้องอยู่ในระเบียบ ไม่สามารถเพิ่มอะไรลงในไฟล์ผลลัพธ์ได้


ใช้เวลานานเท่าzcat file > /dev/nullไหร่?
choroba

คุณสามารถให้ตัวอย่างของไฟล์ที่เป็นปัญหาได้หรือไม่?
FloHim เอง

คุณบอกว่าทุกระเบียนเริ่มต้นด้วย@และยังมี 4 บรรทัดต่อระเบียน ทั้งสองอย่างนี้แน่นอนหรือไม่ - และบรรทัดที่ 2,3,4 สามารถเริ่มต้นด้วย@? และมีส่วนหัวที่ไม่ใช่บันทึกของบรรทัดส่วนท้ายในไฟล์หรือไม่
Peter.O

1
คุณกำลังมองหาโซลูชันที่จัดการอินพุตที่ถูกบีบอัดและ / หรือสร้างเอาต์พุตที่บีบอัดหรือไม่? คุณกำลังมองหาไฟล์บีบอัดขนาดสี่เท่ากันหรือไม่?
สตีเฟ่น Kitt

คำตอบ:


4

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

ยิ่งไปกว่าgzipนั้นไม่รองรับการจัดเก็บขนาดดั้งเดิมของไฟล์บีบอัดที่มีขนาดใหญ่กว่า 4gbs - มันไม่สามารถจัดการได้ ดังนั้นคุณจึงไม่สามารถสืบค้นที่เก็บถาวรเพื่อให้ได้ขนาดที่เชื่อถือได้เพราะจะหลอกคุณ

สิ่ง 4 บรรทัด - ง่ายมากจริง ๆ สิ่งที่ 4 ไฟล์ - ฉันไม่ทราบว่าคุณสามารถทำได้อย่างน่าเชื่อถือและมีการกระจายอย่างสม่ำเสมอโดยไม่ต้องแยกเก็บถาวรเพื่อให้ได้ขนาดที่ไม่บีบอัด ฉันไม่คิดว่าคุณจะทำได้เพราะฉันพยายาม

อย่างไรก็ตามสิ่งที่คุณสามารถทำได้คือการตั้งค่าขนาดสูงสุดสำหรับไฟล์เอาต์พุตแบบแยกและตรวจสอบให้แน่ใจว่าไฟล์เหล่านั้นเสียที่อุปสรรคการบันทึกเสมอ ที่คุณสามารถทำได้ง่ายๆ นี่เป็นสคริปต์เล็กน้อยที่จะทำโดยการแยกgzipไฟล์เก็บถาวรและไพพ์เนื้อหาผ่านddไพพ์บัฟเฟอร์อย่างชัดเจนสองสามตัวที่มีcount=$rptอาร์กิวเมนต์เฉพาะก่อนที่จะส่งผ่านlz4ไปยังเพื่อคลาย / บีบอัดไฟล์แต่ละไฟล์ได้ทันที ฉันยังโยนteeลูกเล่นท่อเล็กน้อยเพื่อพิมพ์สี่บรรทัดสุดท้ายสำหรับแต่ละเซ็กเมนต์ไปยัง stderr เช่นกัน

(       IFS= n= c=$(((m=(k=1024)*k)/354))
        b=bs=354xk bs=bs=64k
        pigz -d </tmp/gz | dd i$bs o$b |
        while   read -r line _$((n+=1))
        do      printf \\n/tmp/lz4.$n\\n
        { {     printf %s\\n "$line"
                dd count=$c i$b o$bs
        }|      tee /dev/fd/3|lz4 -BD -9 >/tmp/lz4.$n
        } 3>&1| tail -n4 |tee /dev/fd/2 |
                wc -c;ls -lh /tmp/[gl]z*
        done
)

มันจะดำเนินต่อไปจนกว่ามันจะจัดการอินพุตทั้งหมด มันไม่ได้พยายามที่จะแยกมันออกเป็นเปอร์เซ็นต์ - ซึ่งมันไม่สามารถรับได้ - แต่มันจะแยกมันต่อจำนวนไบต์ดิบสูงสุดต่อการแยก ส่วนใหญ่ของปัญหาของคุณคือคุณไม่สามารถรับขนาดที่เชื่อถือได้ในไฟล์เก็บถาวรของคุณเพราะมันใหญ่เกินไป - ไม่ว่าคุณจะทำอะไรอย่าทำอย่างนั้นอีกครั้ง - ทำให้การแยกน้อยกว่า 4 กรัมต่อชิ้น , อาจจะ. อย่างน้อยสคริปต์ตัวนี้จะช่วยให้คุณสามารถทำสิ่งนี้ได้โดยไม่ต้องเขียนไบต์ที่ไม่ได้บีบอัดลงดิสก์

ต่อไปนี้เป็นรุ่นที่สั้นกว่าซึ่งตัดออกจากข้อมูลสำคัญ - มันไม่ได้เพิ่มในเนื้อหารายงานทั้งหมด:

(       IFS= n= c=$((1024*1024/354))
        pigz -d | dd ibs=64k obs=354xk |
        while   read -r line _$((n+=1))
        do {    printf %s\\n "$line"
                dd count=$c obs=64k ibs=354xk
        }  |    lz4 -BD -9  >/tmp/lz4.$n
        done
)  </tmp/gz

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

IFS=สิ่งที่เป็นเพียงในการจัดการอย่างใดอย่างหนึ่งreadต่อบรรทัดซ้ำ เราreadหนึ่งเพราะเราต้องการวนรอบของเราที่จะสิ้นสุดเมื่ออินพุตสิ้นสุด ขึ้นอยู่กับขนาดบันทึกของคุณ- ซึ่งตามตัวอย่างของคุณคือ 354 ไบต์ต่อ ฉันสร้างgzipไฟล์เก็บถาวร4 + gb พร้อมข้อมูลสุ่มเพื่อทดสอบ

ข้อมูลแบบสุ่มได้มาด้วยวิธีนี้:

(       mkfifo /tmp/q; q="$(echo '[1+dPd126!<c]sc33lcx'|dc)"
        (tr '\0-\33\177-\377' "$q$q"|fold -b144 >/tmp/q)&
        tr '\0-\377' '[A*60][C*60][G*60][N*16][T*]' | fold -b144 |
        sed 'h;s/^\(.\{50\}\)\(.\{8\}\)/@N\1+\2\n/;P;s/.*/+/;H;x'|
        paste "-d\n" - - - /tmp/q| dd bs=4k count=kx2k  | gzip
)       </dev/urandom >/tmp/gz 2>/dev/null

... แต่บางทีคุณไม่ต้องกังวลมากนักเพราะคุณมีข้อมูลและทุกอย่างอยู่แล้ว กลับไปที่วิธีแก้ปัญหา ...

โดยทั่วไปpigz- ซึ่งดูเหมือนว่าจะคลายความเร็วได้เร็วกว่าzcat- บีบอัดสตรีมที่ไม่มีการบีบอัดและddบัฟเฟอร์ที่ส่งออกไปยังบล็อกการเขียนที่มีขนาดเฉพาะที่หลาย 354 ไบต์ ห่วงจะครั้งเดียวซ้ำไปทดสอบว่าการป้อนข้อมูลจะยังคงเดินทางมาถึงซึ่งแต่ละคนจะหลังจากที่ก่อนอื่นจะเรียกว่าการอ่านบล็อกขนาดเฉพาะที่หลายของ 354 ไบต์ - การประสานกับบัฟเฟอร์กระบวนการ - ระยะเวลา จะมีการอ่านสั้น ๆ หนึ่งครั้งต่อการวนซ้ำเนื่องจากการเริ่มต้น- แต่นั่นไม่สำคัญเพราะเรากำลังพิมพ์ที่- กระบวนการการสะสมของเรา - อย่างไรก็ตามread$lineprintfprintflz4ddddread $linelz4

ฉันได้ตั้งค่าไว้ดังนั้นการทำซ้ำแต่ละครั้งจะอ่านข้อมูลที่ไม่มีการบีบอัดประมาณ 1 กิกะไบต์และบีบอัดข้อมูลนั้นในสตรีมไปที่ประมาณ 650Mb หรือมากกว่านั้น lz4เร็วกว่าวิธีการบีบอัดที่มีประโยชน์อื่น ๆ - ซึ่งเป็นเหตุผลที่ฉันเลือกที่นี่เพราะฉันไม่ชอบที่จะรอ xzจะทำได้ดีกว่ามากในการบีบอัดจริงแม้ว่าอาจจะ อย่างไรก็ตามสิ่งหนึ่งที่เกี่ยวกับlz4มันคือมันสามารถคลายการบีบอัดที่ความเร็วใกล้เคียงกับ RAM - ซึ่งหมายความว่าหลายครั้งที่คุณสามารถคลายการบีบอัดlz4ไฟล์เก็บถาวรได้อย่างรวดเร็วเพราะคุณสามารถเขียนลงในหน่วยความจำได้

รายงานขนาดใหญ่จะรายงานไม่กี่ครั้งต่อการวนซ้ำ ลูปทั้งสองจะพิมพ์ddรายงานตามจำนวนไบต์ดิบที่ถ่ายโอนและความเร็วและอื่น ๆ ลูปขนาดใหญ่จะพิมพ์อินพุต 4 บรรทัดสุดท้ายต่อรอบและนับจำนวนไบต์เดียวกันตามด้วยlsไดเรกทอรีที่ฉันเขียนlz4ไฟล์เก็บถาวร นี่คือสองรอบของการส่งออก:

/tmp/lz4.1
2961+1 records in
16383+1 records out
1073713090 bytes (1.1 GB) copied, 169.838 s, 6.3 MB/s
@NTACGTANTTCATTGGNATGACGCGCGTTTATGNGAGGGCGTCCGGAANGC+TCTCTNCC
TACGTANTTCATTGGNATGACGCGCGTTTATGNGAGGGCGTCCGGAANGCTCTCTNCCGAGCTCAGTATGTTNNAAGTCCTGANGNGTNGCGCCTACCCGACCACAACCTCTACTCGGTTCCGCATGCATGCAACACATCGTCA
+
I`AgZgW*,`Gw=KKOU:W5dE1m=-"9W@[AG8;<P7P6,qxE!7P4##,Q@c7<nLmK_u+IL4Kz.Rl*+w^A5xHK?m_JBBhqaLK_,o;p,;QeEjb|">Spg`MO6M'wod?z9m.yLgj4kvR~+0:.X#(Bf
354

-rw-r--r-- 1 mikeserv mikeserv 4.7G Jun 16 08:58 /tmp/gz
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:32 /tmp/lz4.1

/tmp/lz4.2
2961+1 records in
16383+1 records out
1073713090 bytes (1.1 GB) copied, 169.38 s, 6.3 MB/s
@NTTGTTGCCCTAACCANTCCTTGGGAACGCAATGGTGTGANCTGCCGGGAC+CTTTTGCT
TTGTTGCCCTAACCANTCCTTGGGAACGCAATGGTGTGANCTGCCGGGACCTTTTGCTGCCCTGGTACTTTTGTCTGACTGGGGGTGCCACTTGCAGNAGTAAAAGCNAGCTGGTTCAACNAATAAGGACNANTTNCACTGAAC
+
>G-{N~Q5Z5QwV??I^~?rT+S0$7Pw2y9MV^BBTBK%HK87(fz)HU/0^%JGk<<1--7+r3e%X6{c#w@aA6Q^DrdVI0^8+m92vc>RKgnUnMDcU:j!x6u^g<Go?p(HKG@$4"T8BWZ<z.Xi
354

-rw-r--r-- 1 mikeserv mikeserv 4.7G Jun 16 08:58 /tmp/gz
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:32 /tmp/lz4.1
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:35 /tmp/lz4.2

gzip -lใช้งานได้กับไฟล์ที่ไม่มีการบีบอัด <2GiB เท่านั้น IIRC (มีขนาดเล็กกว่าไฟล์ของ OP อยู่แล้ว)
Stéphane Chazelas

@ StéphaneChazelas - แช่ง นั่นเป็นวิธีเดียวที่ฉันสามารถหาขนาดที่ไม่มีการบีบอัดได้ หากปราศจากสิ่งนี้ก็ใช้ไม่ได้ผล
mikeserv

4

การแยกไฟล์ในขอบเขตการบันทึกเป็นเรื่องง่ายมากโดยไม่ต้องใช้รหัสใด ๆ :

zcat your_file.gz | split -l 10000 - output_name_

สิ่งนี้จะสร้างไฟล์เอาต์พุตแต่ละบรรทัด 10,000 บรรทัดด้วยชื่อ output_name_aa, output_name_ab, output_name_ac, ... ด้วยอินพุตที่มีขนาดใหญ่เท่ากับของคุณสิ่งนี้จะให้ไฟล์เอาต์พุตจำนวนมาก แทนที่10000ด้วยหลาย ๆ สี่และคุณสามารถทำให้ไฟล์ที่ส่งออกมีขนาดใหญ่หรือเล็กตามที่คุณต้องการ โชคไม่ดีที่คำตอบอื่น ๆ ไม่มีวิธีที่ดีที่จะรับประกันว่าคุณจะได้รับจำนวนที่ต้องการ (โดยประมาณ) ขนาดไฟล์ที่ส่งออกโดยไม่ต้องคาดเดาเกี่ยวกับอินพุต (หรือจริง ๆ แล้วการส่งผ่านwcทั้งหมด) หากบันทึกของคุณมีขนาดเท่ากันโดยประมาณ (หรืออย่างน้อยก็กระจายทั่ว ๆ ไปอย่างสม่ำเสมอ) คุณสามารถลองประเมินด้วยวิธีนี้:

zcat your_file.gz | head -n4000 | gzip | wc -c

ซึ่งจะบอกขนาดที่บีบอัดของ 1,000 รายการแรกของไฟล์ของคุณ ขึ้นอยู่กับว่าคุณสามารถประเมินจำนวนแถวที่คุณต้องการในแต่ละไฟล์เพื่อจบลงด้วยสี่ไฟล์ (หากคุณไม่ต้องการให้ไฟล์ที่ห้าเสื่อมลงให้ทิ้งส่วนที่เหลือไว้เล็กน้อยหรือเตรียมที่จะตะปูไฟล์ที่ห้าไปยังส่วนท้ายของสี่)

แก้ไข: นี่คือเคล็ดลับอีกข้อหนึ่งโดยสมมติว่าคุณต้องการไฟล์เอาต์พุตที่บีบอัด:

#!/bin/sh

base=$(basename $1 .gz)
unpigz -c $1 | split -l 100000 --filter='pigz -c > _$FILE.gz' - ${base}_

batch=$((`ls _*.gz | wc -l` / 4 + 1))
for i in `seq 1 4`; do
  files=`ls _*.gz | head -$batch`
  cat $files > ${base}_$i.gz && rm $files
done

การทำเช่นนี้จะสร้างไฟล์ขนาดเล็กจำนวนมากจากนั้นจึงรวมเข้าด้วยกันอย่างรวดเร็ว (คุณอาจต้องปรับแต่งพารามิเตอร์ -l ขึ้นอยู่กับว่าไฟล์ในไฟล์ของคุณมีความยาวเท่าใด) มันถือว่าคุณมี coreutils GNU รุ่นล่าสุด (สำหรับตัวแยก - ตัวกรอง) และประมาณ 130% ของขนาดไฟล์อินพุตของคุณ พื้นที่ว่างในดิสก์ ทดแทน gzip / zcat สำหรับ pigz / unpigz หากคุณไม่มี ฉันได้ยินมาว่าห้องสมุดซอฟต์แวร์บางแห่ง (Java?) ไม่สามารถจัดการไฟล์ gzip ที่ต่อกันได้ แต่ฉันไม่เคยมีปัญหาใด ๆ (pigz ใช้เคล็ดลับเดียวกันในการบีบอัดข้อมูลแบบขนาน)


หากคุณติดตั้ง pigz ไว้คุณสามารถเพิ่มความเร็วได้เล็กน้อยโดยแทนที่ 'pigz -cd' เป็น 'zcat'
Drew

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

3

จากสิ่งที่ฉันรวบรวมหลังจากตรวจสอบ google-sphere และทดสอบ.gzไฟล์7.8 GiB เพิ่มเติมดูเหมือนว่าข้อมูลเมตาของขนาดไฟล์ที่ไม่มีการบีบอัดดั้งเดิมนั้นไม่ถูกต้อง (เช่นผิด ) สำหรับขนาดใหญ่.gzไฟล์ (มากกว่า 4GiB (อาจ 2GiB สำหรับบางไฟล์) รุ่นของgzip) โปรด
ทดสอบข้อมูลเมตาของ gzip อีกครั้ง:

* The compressed.gz file is  7.8 GiB ( 8353115038 bytes) 
* The uncompressed  file is 18.1 GiB (19436487168 bytes)
* The metadata says file is  2.1 GiB ( 2256623616 bytes) uncompressed

ดังนั้นดูเหมือนว่าเป็นไปไม่ได้ที่จะยับยั้งขนาดที่ไม่มีการบีบอัดโดยไม่บีบอัดจริง (ซึ่งค่อนข้างหยาบพูดน้อยที่สุด!)

อย่างไรก็ตามนี่คือวิธีการแยกไฟล์ที่ไม่มีการบีบอัดที่ขอบเขตการบันทึกที่แต่ละระเบียนมี 4 สาย

มันใช้ขนาดของไฟล์เป็นไบต์ (ผ่านstat) และด้วยการawkนับไบต์ (ไม่ใช่ตัวอักษร) ไม่ว่าจุดสิ้นสุดของบรรทัดจะเป็นLF| หรือไม่ CR| CRLFสคริปต์นี้จัดการกับความยาวสิ้นสุดบรรทัดผ่านตัวแปรบิวอินRT )

LC_ALL=C gawk 'BEGIN{"stat -c %s "ARGV[1] | getline inSize
                      segSiz=int(inSize/4)+((inSize%4)==0?0:1)
                      ouSplit=segSiz; segNb=0 }
               { lnb++; bytCt+=(length+length(RT))
                 print $0 > ARGV[1]"."segNb
                 if( lnb!=4 ) next
                 lnb=0
                 if( bytCt>=ouSplit ){ segNb++; ouSplit+=segSiz }
               }' myfile

ด้านล่างคือการทดสอบที่ฉันใช้เพื่อตรวจสอบว่าจำนวนบรรทัดของแต่ละไฟล์เป็นเท่าไหร่ mod 4 == 0

for i in myfile  myfile.{0..3}; do
    lc=$(<"$i" wc -l)
    printf '%s\t%s\t' "$i" $lc; 
    (( $(echo $lc"%4" | bc) )) && echo "Error: mod 4 remainder !" || echo 'mod 4 ok'  
done | column -ts$'\t' ;echo

ผลการทดสอบ:

myfile    1827904  mod 4 ok
myfile.0  456976   mod 4 ok
myfile.1  456976   mod 4 ok
myfile.2  456976   mod 4 ok
myfile.3  456976   mod 4 ok

myfile ถูกสร้างโดย:

printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4} > myfile

2

นี่ไม่ได้หมายความว่าจะเป็นคำตอบที่จริงจัง! ฉันแค่เล่นกับflexและสิ่งนี้อาจจะไม่สามารถทำงานกับไฟล์อินพุตด้วย ~ 50Gb (ถ้าเป็นเลยสำหรับข้อมูลอินพุตขนาดใหญ่กว่าไฟล์ทดสอบของฉัน):

สิ่งนี้ใช้ได้กับฉันในไฟล์input.txt ~ 1Gb :

ที่ได้รับflexแฟ้มใส่splitter.l :

%{
#include <stdio.h>
extern FILE* yyin;
extern FILE* yyout;

int input_size = 0;

int part_num;
int part_num_max;
char **part_names;
%}

%%
@.+ {
        if (ftell(yyout) >= input_size / part_num_max) {
            fclose(yyout);
            if ((yyout = fopen(part_names[++part_num], "w")) == 0) {
                exit(1);
            }
        }
        fprintf(yyout, "%s", yytext);
    }
%%

int main(int argc, char *argv[]) {

    if (argc < 2) {
        return 1;
    } else if ((yyin = fopen(argv[1], "r")) == 0) {
        return 1;
    } else if ((yyout = fopen(argv[2], "w")) == 0) {
        fclose(yyin);
        return 1;
    } else {

        fseek(yyin, 0L, SEEK_END);
        input_size = ftell(yyin);
        rewind(yyin);

        part_num = 0;
        part_num_max = argc - 2;
        part_names = argv + 2;

        yylex();

        fclose(yyin);
        fclose(yyout);
        return 0;
    }
}

สร้างlex.yy.cและรวบรวมไปยังsplitterไบนารีด้วย:

$ flex splitter.l && gcc lex.yy.c -ll -o splitter

การใช้งาน:

$ ./splitter input.txt output.part1 output.part2 output.part3 output.part4

เวลาทำงานสำหรับ 1Gb input.txt :

$ time ./splitter input.txt output.part1 output.part2 output.part3 output.part4

real    2m43.640s
user    0m48.100s
sys     0m1.084s

lexing ที่นี่จริงง่ายมากคุณไม่ได้รับประโยชน์จาก lex จริงๆ เพียงแค่โทรหาgetc(stream)และใช้ตรรกะง่ายๆ นอกจากนี้คุณรู้หรือไม่ว่า (จุด) อักขระ regex ใน (f) lex จับคู่อักขระใด ๆยกเว้นขึ้นบรรทัดใหม่ใช่ไหม ในขณะที่ระเบียนเหล่านี้มีหลายบรรทัด
Kaz

@Kaz แม้ว่าโดยทั่วไปข้อความของคุณจะสัมพันธ์กัน แต่สิ่งนี้ใช้ได้กับข้อมูลที่ให้ไว้ในคำถาม
FloHimself เอง

บังเอิญเพราะมีกฎเริ่มต้นเมื่อไม่มีอะไรที่ตรง: กินตัวอักษรและพิมพ์ไปยังเอาท์พุท! ใน rwords อื่น ๆ คุณสามารถสลับไฟล์โดยใช้กฎที่จดจำ@อักขระได้จากนั้นให้กฎเริ่มต้นคัดลอกข้อมูล ตอนนี้คุณมีกฎการคัดลอกส่วนหนึ่งของข้อมูลเป็นโทเค็นขนาดใหญ่หนึ่งตัวจากนั้นกฎเริ่มต้นจะรับบรรทัดที่สองทีละตัวอักษร
Kaz

ขอบคุณสำหรับการชี้แจง ฉันสงสัยว่าคุณจะแก้ปัญหานี้txrอย่างไร
FloHim เองเมื่อ

ฉันไม่แน่ใจว่าฉันจะเพราะงานคือการทำสิ่งที่ง่ายมากกับข้อมูลจำนวนมากโดยเร็วที่สุด
Kaz

1

ต่อไปนี้เป็นวิธีแก้ปัญหาใน Python ที่ส่งผ่านไฟล์อินพุตหนึ่งไฟล์เพื่อเขียนไฟล์เอาต์พุต

คุณลักษณะเกี่ยวกับการใช้wc -lคือคุณสมมติว่าแต่ละระเบียนที่นี่มีขนาดเท่ากัน นั่นอาจเป็นจริงที่นี่ แต่วิธีแก้ปัญหาด้านล่างใช้ได้แม้ว่าจะไม่เป็นเช่นนั้นก็ตาม มันเป็นพื้นใช้wc -cหรือจำนวนไบต์ในไฟล์ ใน Python สามารถทำได้ผ่าน os.stat ()

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

โปรแกรมนี้เหมาะสมที่สุดในแง่นี้มันอ่านจำนวนไบต์ของไฟล์อินพุตหนึ่งครั้ง รับขนาดไฟล์ไม่จำเป็นต้องอ่านข้อมูลไฟล์ การจัดเก็บข้อมูลที่จำเป็นเป็นสัดส่วนกับขนาดของเส้น แต่ Python หรือระบบอาจมีบัฟเฟอร์ไฟล์ที่เหมาะสมเพื่อเพิ่มความเร็ว I / O

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

และชัดเจนว่านี่สามารถแปลเป็นภาษาโปรแกรมอื่นได้เช่นกัน

อีกอย่างหนึ่งฉันไม่แน่ใจว่า Windows ที่มี crlf จัดการกับความยาวของบรรทัดได้อย่างถูกต้องเช่นเดียวกับในระบบ Unix-y หาก len () ถูกปิดโดยที่นี่ฉันหวังว่าจะเห็นได้ชัดว่าจะปรับโปรแกรมอย่างไร

#!/usr/bin/env python
import os

# Adjust these
filename = 'file.txt'
rec_size = 4
file_splits = 4

size = os.stat(filename).st_size
splits = [(i+1)*size/file_splits for i in range(file_splits)]
with open(filename, 'r') as fd:
    linecount = 0
    i = 0 # File split number
    out = open('file%d.txt' % i, 'w')
    offset = 0  # byte offset of where we are in the file: 0..size
    r = 0 # where we are in the record: 0..rec_size-1
    for line in fd:
        linecount += 1
        r = (r+1) % rec_size
        if offset + len(line) > splits[i] and r == 1 :
            out.close()
            i += 1
            out = open('file%d.txt' % i, 'w')
        out.write(line)
        offset += len(line)
    out.close()
    print("file %s has %d lines" % (filename, linecount))

มันไม่ได้แยกที่ขอบเขตการบันทึก เช่น. การแบ่งไฟล์ย่อยครั้งแรกเกิดขึ้นหลังจากบรรทัดที่ 3 พร้อมอินพุตนี้printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
Peter.O

1

ผู้ใช้ FloHimself ดูเหมือนอยากรู้เกี่ยวกับโซลูชันTXR นี่คือสิ่งหนึ่งที่ใช้TXR Lispแบบฝังตัว:

(defvar splits 4)
(defvar name "data")

(let* ((fi (open-file name "r"))                 ;; input stream
       (rc (tuples 4 (get-lines fi)))            ;; lazy list of 4-tuples
       (sz (/ (prop (stat name) :size) splits))  ;; split size
       (i 1)                                     ;; split enumerator
       (n 0)                                     ;; tuplecounter within split
       (no `@name.@i`)                           ;; output split file name
       (fo (open-file no "w")))                  ;; output stream
  (whilet ((r (pop rc)))  ;; pop each 4-tuple
    (put-lines r fo) ;; send 4-tuple into output file
    ;; if not on the last split, every 1000 tuples, check the output file
    ;; size with stat and switch to next split if necessary.
    (when (and (< i splits)
               (> (inc n) 1000)
               (>= (seek-stream fo 0 :from-current) sz))
      (close-stream fo)
      (set fo (open-file (set no `@name.@(inc i)`) "w")
           n 0)))
  (close-stream fo))

หมายเหตุ:

  1. ด้วยเหตุผลเดียวกันการpopพิมพ์ tuple แต่ละรายการจากรายการ lazy ของ tuples นั้นเป็นสิ่งสำคัญดังนั้นจึงใช้รายการ Lazy เราจะต้องไม่เก็บรักษาการอ้างอิงถึงจุดเริ่มต้นของรายการนั้นเพราะหน่วยความจำจะเพิ่มขึ้นเมื่อเราเดินผ่านไฟล์

  2. (seek-stream fo 0 :from-current)ไม่มีกรณีของseek-streamซึ่งทำให้ตัวเองมีประโยชน์โดยการกลับตำแหน่งปัจจุบัน

  3. ประสิทธิภาพ: ไม่ต้องพูดถึง ใช้งานได้ แต่จะไม่นำถ้วยรางวัลกลับบ้าน

  4. เนื่องจากเราทำการตรวจสอบขนาดทุกๆ 1,000 tuples เราจึงสามารถทำให้ tuple ขนาด 4000 บรรทัด


0

หากคุณไม่ต้องการให้ไฟล์ใหม่เป็นส่วนต่อเนื่องของไฟล์ต้นฉบับคุณสามารถทำได้ด้วยsedวิธีต่อไปนี้:

sed -n -e '1~16,+3w1.txt' -e '5~16,+3w2.txt' -e '9~16,+3w3.txt' -e '13~16,+3w4.txt'

การ-nหยุดมันจากการพิมพ์แต่ละบรรทัดและแต่ละ-eสคริปต์ก็ทำสิ่งเดียวกัน 1~16ตรงกับบรรทัดแรกและทุกบรรทัดที่ 16 หลังจากนั้น ,+3หมายถึงจับคู่สามบรรทัดถัดไปหลังจากแต่ละบรรทัด w1.txtบอกว่าเขียนทุกบรรทัดเหล่านั้นไปยังไฟล์1.txtกล่าวว่าเขียนทั้งหมดของเส้นที่ไปยังแฟ้มนี่คือการพาทุกกลุ่มที่ 4 ของ 4 บรรทัดและเขียนลงในไฟล์โดยเริ่มจากกลุ่มแรกของ 4 บรรทัด อีกสามคำสั่งทำสิ่งเดียวกัน แต่แต่ละคำสั่งนั้นเลื่อนไปข้างหน้า 4 บรรทัดและเขียนไปยังไฟล์อื่น

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

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