Làm cách nào tôi có thể chia một tệp văn bản thành nhiều tệp văn bản?


16

Tôi có một tệp văn bản được gọi entry.txtcó chứa như sau:

[ 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

Tôi muốn chia nó thành ba tập tin văn bản: entry1.txt, entry2.txt, entry3.txt. Nội dung của chúng như sau.

mục1.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

mục2.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

mục3.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

Nói cách khác, [ký tự chỉ ra một tệp mới sẽ bắt đầu. Các mục ( [ entry*], trong đó *là một số nguyên) luôn theo thứ tự số và là các số nguyên liên tiếp bắt đầu từ 1 đến N (trong tệp đầu vào thực tế của tôi, N = 200001).

Có cách nào để tôi có thể thực hiện phân tách tệp văn bản tự động trong bash không? Đầu vào thực tế của tôi thực entry.txtsự chứa 200.001 mục.

Câu trả lời:


11

Và đây là một miếng lót đẹp, đơn giản, đẹp mắt:

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

Điều này sẽ làm việc cho bất kỳ kích thước tệp, bất kể số lượng dòng trong mỗi mục, miễn là mỗi tiêu đề mục nhập trông như thế nào [ blahblah blah blah ]. Chú ý không gian ngay sau khi mở [và ngay trước khi đóng cửa ].


GIẢI TRÌNH:

awkgawkđọc một dòng tập tin đầu vào theo dòng. Khi mỗi dòng được đọc, nội dung của nó được lưu trong $0biến. Ở đây, chúng tôi đang nói gawkđể khớp bất cứ thứ gì trong dấu ngoặc vuông và lưu kết quả khớp của nó vào mảng k.

Vì vậy, mỗi khi biểu thức chính quy được khớp, nghĩa là, đối với mọi tiêu đề trong tệp của bạn, k [1] sẽ có vùng khớp của dòng. Cụ thể, "entry1", "entry2" hoặc "entry3" hoặc "entryN".

Cuối cùng, chúng tôi in mỗi dòng thành một tệp có tên <whatever value k currently has>.txt, tức là entry1.txt, entry2.txt ... entryN.txt.

Phương pháp này sẽ được nhiều nhanh hơn perl cho các tập tin lớn hơn.


+1 đẹp. Bạn không cần vào matchmục: /^\[/ { name=$2 }nên là đủ.
Thor

Cảm ơn @Thor. Đề xuất của bạn là chính xác cho trường hợp được mô tả, nhưng nó cho rằng không bao giờ có khoảng trắng trong tên của mục nhập. Đó là lý do tại sao tôi sử dụng ví dụ [ blahblah blah blah ]trong câu trả lời của tôi.
terdon

Ah tôi đã bỏ lỡ một chút về các mục tách không gian. Bạn cũng có thể chứa những người với FS, ví dụ -F '\\[ | \\]'.
Thor

@terdon Tôi thực sự thích giải pháp ngắn này, tiếc là tôi thường không khái quát chúng theo nhu cầu của mình. Bạn có thể giúp tôi một tay không? Tệp của tôi có các dòng bắt đầu bằng #S x, trong đó x là số có 1, 2 hoặc 3 chữ số. Chỉ cần lưu chúng vào x.dat là đủ. Tôi đã thử: gawk '/^#S/{match($0, / [0-9]* /, k)} {print >k[1]".dat" }' myFile.txtvà một số biến thể của điều đó.
mikuszefski

Có nó gawk '/^#S/{match($0, /^#S (\s+?)([0-9]+)(\s+?)/, k)} {print >k[2]".txt" }' test.txtđã lừa 2Mặc dù vậy, không hiểu số mảng rất tốt.
mikuszefski

17

Với csplit từ GNU coreutils (Linux không nhúng, Cygwin):

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

Bạn sẽ kết thúc với một tệp trống bổ sung entry0.txt(chứa phần trước tiêu đề đầu tiên).

Csplit tiêu chuẩn thiếu {*}bộ lặp không xác định và -btùy chọn chỉ định định dạng hậu tố, vì vậy trên các hệ thống khác, bạn sẽ phải đếm số phần trước và đổi tên các tệp đầu ra sau đó.

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

Tôi thấy csplit thỉnh thoảng hơi kỳ quặc, nhưng cực kỳ hữu ích khi tôi muốn làm điều này.
ixtmixilix

10

Trong perl nó có thể được thực hiện đơn giản hơn nhiều:

perl -ne 'open(F, ">", ($1).".txt") if /\[ (entry\d+) \]/; print F;' file

9

Đây là một lót ngắn awk:

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

Cái này hoạt động ra sao?

  • /^\[/ khớp với các dòng bắt đầu bằng dấu ngoặc vuông bên trái và
  • {ofn=$2 ".txt"}đặt một biến thành từ được phân cách bằng khoảng trắng thứ hai làm tên tệp đầu ra của chúng tôi. Sau đó,
  • ofn là một điều kiện đánh giá là đúng nếu biến được đặt (do đó làm cho các dòng trước tiêu đề đầu tiên của bạn bị bỏ qua)
  • {print > ofn} chuyển hướng dòng hiện tại đến tập tin được chỉ định.

Lưu ý rằng tất cả các khoảng trắng trong tập lệnh awk này có thể được loại bỏ, nếu sự gọn nhẹ làm bạn hài lòng.

Cũng lưu ý rằng tập lệnh trên thực sự cần các tiêu đề của phần để có khoảng trắng xung quanh và không nằm trong chúng. Nếu bạn muốn có thể xử lý các tiêu đề phần như [foo][ this that ], bạn sẽ cần nhiều mã hơn một chút:

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

Điều này sử dụng sub()chức năng của awk để loại bỏ dấu ngoặc vuông và dấu ngoặc vuông cộng với khoảng trắng. Lưu ý rằng trên mỗi hành vi awk tiêu chuẩn, điều này sẽ thu gọn khoảng trắng (dấu tách trường) thành một khoảng trắng (nghĩa [ this that ]là được lưu vào "this that.txt"). Nếu việc duy trì khoảng trắng ban đầu trong tên tệp đầu ra của bạn là quan trọng, bạn có thể thử nghiệm bằng cách đặt FS.


2

Nó có thể được thực hiện từ dòng lệnh trong python như:

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

Đây là một cách hơi thô thiển, nhưng dễ hiểu để thực hiện: sử dụng grep -l '[ entry ]' FILENAMEđể lấy số dòng để phân chia tại [mục]. Sử dụng kết hợp đầu và đuôi để có được các mảnh phù hợp.

Như tôi đã nói; nó không đẹp, nhưng dễ hiểu


2

Còn về việc sử dụng awk với [dấu phân cách bản ghi và khoảng trắng làm dấu phân cách trường. Điều này cho chúng ta dễ dàng đưa dữ liệu vào tệp như là $0nơi anh ta phải đặt lại phần đầu bị loại bỏ [và tên tệp là $1. Chúng tôi sau đó chỉ phải xử lý trường hợp đặc biệt của bản ghi thứ 1 trống. Điều này cho chúng ta:

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

2

Câu trả lời của terdon có tác dụng với tôi nhưng tôi cần sử dụng gawk chứ không phải awk. Các nhãn hiệu gawk (tìm kiếm cho 'trận đấu (') giải thích rằng đối số mảng trong trận đấu () là một phần mở rộng gawk. Có lẽ nó phụ thuộc vào Linux của bạn cài đặt và awk / nawk / gawk phiên bản của bạn, nhưng trên máy tính Ubuntu của tôi chỉ xuất sắc gawk ran terdon của câu trả lời:

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

1

Đây là một giải pháp perl. Kịch bản lệnh này phát hiện các [ entryN ]dòng và thay đổi tệp đầu ra tương ứng, nhưng không xác thực, phân tích hoặc xử lý dữ liệu trong mỗi phần, nó chỉ in dòng đầu vào thành tệp đầu ra.

#! /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

Xin chào, tôi đã viết kịch bản đơn giản này bằng ruby ​​để giải quyết vấn đề của bạn

#!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

bạn có thể sử dụng nó theo cách này:

ruby split.rb < entry.txt

tôi đã thử nó, và nó hoạt động tốt ..


1

Tôi thích csplittùy chọn này nhưng là một giải pháp thay thế GNU awk:

phân tích

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
}

Chạy nó như thế này:

gawk -f parse.awk entry.txt

1
FWIW, RTbiến có vẻ là đặc trưng của gawk. Giải pháp này không hiệu quả với tôi khi sử dụng awk của FreeBSD.
ghoti

@ghoti: Đúng vậy, tôi nên đã đề cập đến điều đó. Tôi đã bao gồm điều đó trong câu trả lời ngay bây giờ. Cảm ơn.
Thor
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.