ฉันจะแยกไฟล์ข้อความเป็นไฟล์ข้อความหลายไฟล์ได้อย่างไร


16

ฉันมีไฟล์ข้อความที่เรียกentry.txtว่ามีดังต่อไปนี้:

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631
[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631
[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

entry1.txtผมอยากจะแยกมันออกเป็นสามไฟล์ข้อความ: entry2.txt, entry3.txt, เนื้อหาของพวกเขามีดังนี้

entry1.txt :

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631

entry2.txt :

[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631

entry3.txt :

[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

กล่าวอีกนัยหนึ่ง[อักขระระบุว่าไฟล์ใหม่ควรเริ่มต้น รายการ ( [ entry*]ซึ่ง*เป็นจำนวนเต็ม) อยู่ในลำดับตัวเลขเสมอและเป็นจำนวนเต็มต่อเนื่องเริ่มต้นจาก 1 ถึง N (ในไฟล์อินพุตจริงของฉัน N = 200001)

มีวิธีใดบ้างที่ฉันสามารถแยกไฟล์ข้อความอัตโนมัติด้วยการทุบตีได้? ข้อมูลจริงของฉันentry.txtมี 200,001 รายการ

คำตอบ:


11

และนี่เป็นสิ่งที่ดีเรียบง่ายและเพ่งมองดู

$ gawk '/^\[/{match($0, /^\[ (.+?) \]/, k)} {print >k[1]".txt" }' entry.txt

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


คำอธิบาย:

awkและgawkอ่านไฟล์อินพุตทีละบรรทัด เมื่ออ่านแต่ละบรรทัดเนื้อหาของมันจะถูกบันทึกใน$0ตัวแปร ที่นี่เราจะบอกให้ตรงกับสิ่งที่อยู่ภายในวงเล็บและบันทึกการทำงานของลงในอาร์เรย์gawkk

ดังนั้นทุกครั้งที่มีการจับคู่นิพจน์ทั่วไปนั่นคือสำหรับทุกส่วนหัวในไฟล์ของคุณ k [1] จะมีขอบเขตที่ตรงกันของบรรทัด คือ "entry1", "entry2" หรือ "entry3" หรือ "entryN"

สุดท้ายเราพิมพ์แต่ละบรรทัดเป็นไฟล์ชื่อ<whatever value k currently has>.txtเช่น entry1.txt, entry2.txt ... entryN.txt

วิธีการนี้จะเป็นมากเร็วกว่า Perl สำหรับไฟล์ขนาดใหญ่


+1 ดี คุณไม่จำเป็นต้องmatchเข้าร่วม: /^\[/ { name=$2 }ควรจะเพียงพอ
ธ.ค.

ขอบคุณ @Thor ข้อเสนอแนะของคุณถูกต้องสำหรับกรณีที่อธิบาย แต่จะถือว่าไม่มีช่องว่างในชื่อของรายการ นั่นคือเหตุผลที่ฉันใช้ตัวอย่าง[ blahblah blah blah ]ในคำตอบของฉัน
terdon

อาฉันพลาดนิดหน่อยเกี่ยวกับรายการคั่นด้วยช่องว่าง นอกจากนี้คุณยังสามารถรองรับผู้ที่มีเช่นFS -F '\\[ | \\]'
ธ.ค.

@terdon ฉันชอบวิธีแก้ปัญหาสั้น ๆ นี้ แต่น่าเสียดายที่ฉันมักจะไม่พูดคุยกับพวกเขาตามความต้องการของฉัน คุณมอบมือฉันได้ไหม ไฟล์ของฉันมีบรรทัดที่ขึ้นต้นด้วย#S xโดยที่ x คือหมายเลข 1, 2 หรือ 3 หลัก เพียงบันทึกลงใน x.dat จะพอเพียง ฉันลอง: gawk '/^#S/{match($0, / [0-9]* /, k)} {print >k[1]".dat" }' myFile.txtและรูปแบบบางอย่างของมัน
mikuszefski

รับมันgawk '/^#S/{match($0, /^#S (\s+?)([0-9]+)(\s+?)/, k)} {print >k[2]".txt" }' test.txtทำเคล็ดลับ ไม่เข้าใจหมายเลขอาเรย์2เป็นอย่างดี
mikuszefski

17

ด้วยcsplitจาก GNU coreutils (ไม่ฝัง Linux, Cygwin):

csplit -f entry -b '%d.txt' entry.txt '/^\[ .* \]$/' '{*}'

คุณจะท้ายด้วยไฟล์ว่างเปล่าพิเศษentry0.txt(มีส่วนก่อนส่วนหัวแรก)

csplitมาตรฐานไม่มี{*}repeater ที่ไม่ จำกัด และ-bตัวเลือกในการระบุรูปแบบคำต่อท้ายดังนั้นในระบบอื่น ๆ คุณจะต้องนับจำนวนส่วนก่อนและเปลี่ยนชื่อไฟล์เอาต์พุตหลังจากนั้น

csplit -f entry -n 9 entry.txt '/^\[ .* \]$/' "{$(egrep -c '^'\[ .* \]$' <entry.txt)}"
for x in entry?????????; do
  y=$((1$x - 1000000000))
  mv "entry$x" "entry$y.txt"
done

ฉันพบว่า csplit ค่อนข้างแปลกสักหน่อย แต่ก็มีประโยชน์อย่างมากเมื่อฉันต้องการทำสิ่งนี้
ixtmixilix


9

นี่เป็นซับสั้นหนึ่ง awk สั้น ๆ :

awk '/^\[/ {ofn=$2 ".txt"} ofn {print > ofn}' input.txt

มันทำงานอย่างไร

  • /^\[/ จับคู่บรรทัดที่ขึ้นต้นด้วยวงเล็บเหลี่ยมด้านซ้ายและ
  • {ofn=$2 ".txt"}ตั้งค่าตัวแปรเป็นคำที่มีการเว้นวรรคสีขาวที่สองเป็นชื่อไฟล์เอาต์พุตของเรา จากนั้น
  • ofn เป็นเงื่อนไขที่ประเมินว่าเป็นจริงหากตั้งค่าตัวแปรไว้ (ทำให้เกิดบรรทัดก่อนส่วนหัวแรกของคุณที่จะถูกละเว้น)
  • {print > ofn} เปลี่ยนเส้นทางบรรทัดปัจจุบันไปยังไฟล์ที่ระบุ

โปรดทราบว่าทั้งหมดของช่องว่างในสคริปต์ awk นี้สามารถถอดออกได้ถ้าเป็นปึกแผ่นทำให้คุณมีความสุข

โปรดทราบว่าสคริปต์ด้านบนต้องการส่วนหัวของส่วนจริง ๆ เพื่อให้มีช่องว่างและไม่อยู่ในส่วนนั้น หากคุณต้องการที่จะสามารถจัดการกับส่วนหัวเช่น[foo]และ[ this that ]คุณจะต้องรหัสเพิ่มเติมเล็กน้อย:

awk '/^\[/ {sub(/^\[ */,""); sub(/ *\] *$/,""); ofn=$0 ".txt"} ofn {print > ofn}' input.txt

สิ่งนี้ใช้sub()ฟังก์ชั่นของ awk ในการตัดส่วนนำและส่วนท้ายของช่องสี่เหลี่ยมวงเล็บเหลี่ยม - บวก - ช่องว่าง โปรดทราบว่าสำหรับพฤติกรรม awk มาตรฐานสิ่งนี้จะยุบช่องว่าง (ตัวคั่นฟิลด์) ลงในช่องว่างเดียว (เช่น[ this that ]ถูกบันทึกไว้"this that.txt") หากการรักษาช่องว่างดั้งเดิมในชื่อไฟล์ที่ส่งออกของคุณเป็นสิ่งสำคัญคุณสามารถทดลองโดยตั้งค่า FS


2

มันสามารถทำได้จากบรรทัดคำสั่งในหลามเป็น:

paddy$ python3 -c 'out=0
> with open("entry.txt") as f: 
>   for line in f:
>     if line[0] == "[":
>       if out: out.close()
>       out = open(line.split()[1] + ".txt", "w")
>     else: out.write(line)'

2

นี่เป็นวิธีที่ค่อนข้างหยาบ แต่เข้าใจง่าย: ใช้grep -l '[ entry ]' FILENAMEเพื่อรับหมายเลขบรรทัดเพื่อแยกที่ [รายการ] ใช้การผสมผสานระหว่างส่วนหัวและส่วนท้ายเพื่อให้ได้ชิ้นที่เหมาะสม

เหมือนที่ฉันพูด มันไม่สวย แต่ง่ายต่อการเข้าใจ


2

สิ่งที่เกี่ยวกับการใช้ awk ด้วย[เป็นตัวแยกเรคคอร์ดและช่องว่างเป็นตัวคั่นฟิลด์ นี้จะช่วยให้เราได้อย่างง่ายดายข้อมูลที่จะใส่ในแฟ้มเป็น$0ที่ที่เขาจะต้องนำกลับมาออกชั้นนำและชื่อไฟล์เป็น[ $1จากนั้นเราจะต้องจัดการกับกรณีพิเศษของระเบียนที่ 1 ซึ่งว่างเปล่า สิ่งนี้ทำให้เรา:

awk -v "RS=[" -F " " 'NF != 0 {print "[" $0 > $1}' entry.txt

2

คำตอบของ terdon นั้นใช้ได้สำหรับฉัน แต่ฉันต้องการใช้เพ่งพิศ คู่มือการเพ่งพิศ (ค้นหาสำหรับ 'การจับคู่ (') อธิบายว่าอาร์กิวเมนต์อาร์เรย์ในการแข่งขัน () เป็นส่วนขยายเพ่งพิศ. บางทีมันอาจจะขึ้นอยู่กับลินุกซ์ของคุณติดตั้งและคุณรุ่น awk / nawk / เพ่งพิศ แต่ในเครื่องอูบุนตูของฉันเท่านั้นที่ยอดเยี่ยมเพ่งพิศวิ่ง terdon ของ ตอบ:

$ gawk '{if(match($0, /^\[ (.+?) \]/, k)){name=k[1]}} {print >name".txt" }' entry.txt

1

นี่คือทางออกของ perl สคริปต์นี้ตรวจพบ[ entryN ]บรรทัดและเปลี่ยนไฟล์เอาต์พุตตามลำดับ แต่ไม่ตรวจสอบแยกวิเคราะห์หรือประมวลผลข้อมูลในแต่ละส่วนโดยจะพิมพ์บรรทัดอินพุตไปยังไฟล์เอาต์พุต

#! /usr/bin/perl 

# default output file is /dev/null - i.e. dump any input before
# the first [ entryN ] line.

$outfile='/dev/null';
open(OUTFILE,">",$outfile) || die "couldn't open $outfile: $!";

while(<>) {
  # uncomment next two lines to optionally remove comments (starting with
  # '#') and skip blank lines.  Also removes leading and trailing
  # whitespace from each line.
  # s/#.*|^\s*|\s*$//g;
  # next if (/^$/)

  # if line begins with '[', extract the filename
  if (m/^\[/) {
    (undef,$outfile,undef) = split ;
    close(OUTFILE);
    open(OUTFILE,">","$outfile.txt") || die "couldn't open $outfile.txt: $!";
  } else {
    print OUTFILE;
  }
}
close(OUTFILE);

1

สวัสดีฉันเขียนสคริปต์ง่ายๆนี้โดยใช้ทับทิมเพื่อแก้ปัญหาของคุณ

#!ruby
# File Name: split.rb

fout = nil

while STDIN.gets
  line = $_
  if line.start_with? '['
    fout.close if fout
    fname = line.split(' ')[1] + '.txt'
    fout = File.new fname,'w'
  end
  fout.write line if fout
end

fout.close if fout

คุณสามารถใช้วิธีนี้:

ruby split.rb < entry.txt

ฉันได้ทำการทดสอบแล้วและใช้งานได้ดี ..


1

ฉันชอบcsplitตัวเลือก แต่เป็นทางเลือกนี่เป็น GNU awk solution:

parse.awk

BEGIN { 
  RS="\\[ entry[0-9]+ \\]\n"  # Record separator
  ORS=""                      # Reduce whitespace on output
}
NR == 1 { f=RT }              # Entries are of-by-one relative to matched RS
NR  > 1 {
  split(f, a, " ")            # Assuming entries do not have spaces 
  print f  > a[2] ".txt"      # a[2] now holds the bare entry name
  print   >> a[2] ".txt"
  f = RT                      # Remember next entry name
}

เรียกใช้ดังนี้:

gawk -f parse.awk entry.txt

1
FWIW RTตัวแปรดูเหมือนจะเฉพาะเจาะจงเพ่งพิศ โซลูชันนี้ใช้งานไม่ได้สำหรับฉันโดยใช้ awk ของ FreeBSD
ghoti

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