Tách tập tin lớn thành nhiều phần mà không tách mục


8

Tôi có một tệp .msg khá lớn được định dạng theo định dạng UIEE.

$ wc -l big_db.msg
8726593 big_db.msg

Về cơ bản, tệp được tạo thành từ các mục có độ dài khác nhau trông giống như thế này:

UR|1
AA|Condon, Richard
TI|Prizzi's Family
CN|Collectable- Good/Good
MT|FICTION
PU|G.P. Putnam & Sons
DP|1986
ED|First Printing.
BD|Hard Cover
NT|0399132104
KE|MAFIA
KE|FICTION
PR|44.9
XA|4
XB|1
XC|BO
XD|S

UR|10
AA|Gariepy, Henry
TI|Portraits of Perseverance
CN|Good/No Jacket
MT|SOLD
PU|Victor Books
DP|1989
BD|Mass Market Paperback
NT|1989 tpb g 100 meditations from the Book of Job "This book...help you
NT| persevere through the struggles of your life..."
KE|Bible
KE|religion
KE|Job
KE|meditations
PR|28.4
XA|4
XB|5
XC|BO
XD|S

Đây là một ví dụ về hai mục, cách nhau bởi một dòng trống. Tôi muốn chia tập tin lớn này thành các tập tin nhỏ hơn mà không phá vỡ một mục nhập thành hai tập tin.

Mỗi mục riêng lẻ được phân tách bằng một dòng mới (một dòng hoàn toàn trống) trong tệp. Tôi muốn chia tập tin 8,7 triệu này thành 15 tập tin. Tôi hiểu rằng các công cụ như splittồn tại nhưng tôi không chắc chắn làm cách nào để tách tệp mà chỉ tách nó trên một dòng mới để một mục nhập không bị chia thành nhiều tệp.


csplitcũng tồn tại.
mikeerv

Bạn có thể tạo tập tin tạm thời?
Braiam

@Braiam, không chắc ý của bạn là gì nhưng tôi nghĩ vậy. Tôi có toàn quyền truy cập vào hệ thống tập tin.
dùng2036066

anh ta có nghĩa là tạo các tệp được sử dụng tạm thời cho quy trình
polym

1
Tại sao chính xác 15 tập tin, nếu tôi có thể yêu cầu? Là các tiền tố trước ống |(như UR, AA, TI) thích hợp cho số lượng các tập tin, thậm chí giống nhau để được chính xác?
polym

Câu trả lời:


2

Đây là một giải pháp có thể hoạt động:

seq 1 $(((lines=$(wc -l </tmp/file))/16+1)) $lines |
sed 'N;s|\(.*\)\(\n\)\(.*\)|\1d;\1,\3w /tmp/uptoline\3\2\3|;P;$d;D' |
sed -ne :nl -ne '/\n$/!{N;bnl}' -nf - /tmp/file

Nó hoạt động bằng cách cho phép người đầu tiên sedviết sedkịch bản thứ hai . Đầu sedtiên thứ hai tập hợp tất cả các dòng đầu vào cho đến khi nó gặp một dòng trống. Sau đó nó ghi tất cả các dòng đầu ra vào một tập tin. Cái đầu tiên sedviết ra một kịch bản cho cái thứ hai hướng dẫn nó về nơi viết đầu ra của nó. Trong trường hợp thử nghiệm của tôi, kịch bản trông như thế này:

1d;1,377w /tmp/uptoline377
377d;377,753w /tmp/uptoline753
753d;753,1129w /tmp/uptoline1129
1129d;1129,1505w /tmp/uptoline1505
1505d;1505,1881w /tmp/uptoline1881
1881d;1881,2257w /tmp/uptoline2257
2257d;2257,2633w /tmp/uptoline2633
2633d;2633,3009w /tmp/uptoline3009
3009d;3009,3385w /tmp/uptoline3385
3385d;3385,3761w /tmp/uptoline3761
3761d;3761,4137w /tmp/uptoline4137
4137d;4137,4513w /tmp/uptoline4513
4513d;4513,4889w /tmp/uptoline4889
4889d;4889,5265w /tmp/uptoline5265
5265d;5265,5641w /tmp/uptoline5641

Tôi đã thử nó như thế này:

printf '%s\nand\nmore\nlines\nhere\n\n' $(seq 1000) >/tmp/file

Điều này cung cấp cho tôi một tệp gồm 6000 dòng, trông như thế này:

<iteration#>
and
more
lines
here
#blank

... lặp lại 1000 lần.

Sau khi chạy đoạn script trên:

set -- /tmp/uptoline*
echo $# total splitfiles
for splitfile do
    echo $splitfile
    wc -l <$splitfile
    tail -n6 $splitfile
done    

ĐẦU RA

15 total splitfiles
/tmp/uptoline1129
378
188
and
more
lines
here

/tmp/uptoline1505
372
250
and
more
lines
here

/tmp/uptoline1881
378
313
and
more
lines
here

/tmp/uptoline2257
378
376
and
more
lines
here

/tmp/uptoline2633
372
438
and
more
lines
here

/tmp/uptoline3009
378
501
and
more
lines
here

/tmp/uptoline3385
378
564
and
more
lines
here

/tmp/uptoline3761
372
626
and
more
lines
here

/tmp/uptoline377
372
62
and
more
lines
here

/tmp/uptoline4137
378
689
and
more
lines
here

/tmp/uptoline4513
378
752
and
more
lines
here

/tmp/uptoline4889
372
814
and
more
lines
here

/tmp/uptoline5265
378
877
and
more
lines
here

/tmp/uptoline5641
378
940
and
more
lines
here

/tmp/uptoline753
378
125
and
more
lines
here

3

Sử dụng gợi ý của csplit:

Tách dựa trên số dòng

$ csplit file.txt <num lines> "{repetitions}"

Thí dụ

Nói rằng tôi có một tập tin với 1000 dòng trong đó.

$ seq 1000 > file.txt

$ csplit file.txt 100 "{8}"
288
400
400
400
400
400
400
400
400
405

kết quả trong các tập tin như vậy:

$ wc -l xx*
  99 xx00
 100 xx01
 100 xx02
 100 xx03
 100 xx04
 100 xx05
 100 xx06
 100 xx07
 100 xx08
 101 xx09
   1 xx10
1001 total

Bạn có thể vượt qua giới hạn tĩnh của việc phải chỉ định số lần lặp lại bằng cách tính toán trước các số dựa trên số dòng trong tệp cụ thể của bạn trước thời hạn.

$ lines=100
$ echo $lines 
100

$ rep=$(( ($(wc -l file.txt | cut -d" " -f1) / $lines) -2 ))
$ echo $rep
8

$ csplit file.txt 100 "{$rep}"
288
400
400
400
400
400
400
400
400
405

Tách dựa trên các dòng trống

Mặt khác, nếu bạn muốn tách một tệp trên các dòng trống có trong tệp, bạn có thể sử dụng phiên bản này của split:

$ csplit file2.txt '/^$/' "{*}"

Thí dụ

Giả sử tôi đã thêm 4 dòng trống file.txtở trên và tạo tệp file2.txt. Bạn có thể thấy rằng họ đã được thêm thủ công như vậy:

$ grep -A1 -B1 "^$" file2.txt
20

21
--
72

73
--
112

113
--
178

179

Ở trên cho thấy tôi đã thêm chúng vào giữa các số tương ứng trong tệp mẫu của mình. Bây giờ khi tôi chạy csplitlệnh:

$ csplit file2.txt '/^$/' "{*}"
51
157
134
265
3290

Bạn có thể thấy rằng bây giờ tôi có 4 tệp đã được phân tách dựa trên dòng trống:

$ grep -A1 -B1 '^$' xx0*
xx01:
xx01-21
--
xx02:
xx02-73
--
xx03:
xx03-113
--
xx04:
xx04-179

Người giới thiệu


Tôi đã chỉnh sửa OP với nỗ lực sử dụng cái này và tôi không thể làm cho nó hoạt động được.
dùng2036066

Các tập tin không được phân chia trên một dòng mới, trống, đó là những gì tôi đã cố gắng thực hiện.
dùng2036066

@ user2036066 - bạn muốn chia tệp thành 15 phần tệp để đảm bảo không bị tách trên một dòng hoặc một cái gì khác?
slm

@ user2036066 - Đợi tập tin có 14-15 dòng hoàn toàn trống trong đó bạn muốn tách ra?
slm

Đã chỉnh sửa lại op với nhiều ngữ cảnh hơn @slm
user2036066

3

Nếu bạn không quan tâm đến các đơn đặt hàng của hồ sơ, bạn có thể làm:

gawk -vRS= '{printf "%s", $0 RT > "file.out." (NR-1)%15}' file.in

Mặt khác, trước tiên bạn cần lấy số lượng bản ghi trước, để biết số lượng cần đặt trong mỗi tệp đầu ra:

gawk -vRS= -v "n=$(gawk -vRS= 'END {print NR}' file.in)" '
  {printf "%s", $0 RT > "file.out." int((NR-1)*15/n)}' file.in

Sử dụng awk để phân chia trên các dòng trống cũng là suy nghĩ đầu tiên của tôi - +1
godlygeek

Là gì file.infile.out?
mikeerv

1

Nếu bạn đang tìm cách phân tách chỉ ở cuối dòng, bạn sẽ có thể làm điều đó với -ltùy chọn cho split.

Nếu bạn đang tìm cách phân chia trên một dòng trống ( \n\n), đây là cách tôi sẽ làm trong ksh. Tôi đã không thử nó, và nó có thể không lý tưởng, nhưng một cái gì đó dọc theo dòng này sẽ hoạt động:

filenum=0
counter=0
limit=580000

while read LINE
do
  counter=counter+1

  if (( counter >= limit ))
  then
    if [[ $LINE == "" ]]
    then
      filenum=filenum+1
      counter=0
    fi
  fi

  echo $LINE >>big_db$filenum.msg
done <big_db.msg

1
Có thể tôi đã đọc sai, nhưng op đang hỏi làm thế nào để tách ra \n\n, tôi nghĩ vậy.
mikeerv

Điều đó không thực sự giúp tôi vì điều đó vẫn sẽ phân chia tập tin giữa. Tôi cần nó để tập tin sẽ chỉ được phân chia trên một dòng trống.
dùng2036066

Có tôi đã đọc sai, xin lỗi. Nó có thể không phải là cách tốt nhất, tôi chỉ đọc trong tệp gốc thành một vòng lặp với số lượng bạn đã vượt qua và một khi bạn nhấn vào số bạn muốn tách để bắt đầu xuất ra một tệp mới ở lần tiếp theo dòng trống.
hornj

Cố gắng để kiểm tra kịch bản này ngay bây giờ.
dùng2036066

1
Tôi nghĩ OP không hỏi làm thế nào để tách ra \n\n, mà là không tách ra giữa dòng. Ông đang gọi một dòng mới là một dòng trống.
polym

0

Thử awk

awk 'BEGIN{RS="\n\n"}{print $0 > FILENAME"."FNR}' big_db.msg

Đang thử giải pháp này ngay bây giờ
user2036066

2
Giải pháp này tạo ra một tệp mới cho mỗi mục, đây không phải là điều tôi muốn.
dùng2036066

0

Nếu bạn không quan tâm đến thứ tự của các bản ghi nhưng bạn đặc biệt về việc nhận được một số lượng tệp đầu ra nhất định, câu trả lời của Stephane là cách tôi sẽ đi. Nhưng tôi có cảm giác bạn có thể quan tâm nhiều hơn đến việc chỉ định kích thước mà mỗi tệp đầu ra không được vượt quá. Điều đó thực sự làm cho nó dễ dàng hơn bởi vì bạn có thể đọc qua tệp đầu vào của mình và thu thập các bản ghi cho đến khi bạn đạt được kích thước đó, và sau đó bắt đầu một tệp đầu ra mới. Nếu điều đó phù hợp với bạn, hầu hết các ngôn ngữ lập trình có thể xử lý công việc của bạn với một tập lệnh ngắn. Đây là một triển khai awk:

BEGIN {
    RS = "\n\n"
    ORS = "\n\n"
    maxlen = (maxlen == 0 ? 500000 : maxlen)
    oi = 1
}

{
    reclen = length($0) + 2
    if (n + reclen > maxlen) {
        oi++
        n = 0
    }
    n += reclen
    print $0 > FILENAME"."oi
}

Đặt cái này vào một tệp, giả sử program.awkvà chạy nó với awk -v maxlen=10000 -f program.awk big_db.msggiá trị maxlenlà nhiều byte nhất bạn muốn trong bất kỳ một tệp nào. Nó sẽ sử dụng 500k làm mặc định.

Nếu bạn muốn có được một số lượng tập tin, có lẽ cách dễ nhất là chỉ chia kích thước của tệp đầu vào của bạn cho số tệp bạn muốn, sau đó thêm một chút vào số đó để lấy maxlen. Ví dụ: để lấy 15 tệp trong số 8726593 byte của bạn, chia cho 15 để nhận 581773 và thêm một số tệp, vì vậy có thể cung cấp maxlen=590000hoặc maxlen=600000. Nếu bạn muốn làm điều này lặp đi lặp lại, có thể cấu hình chương trình để làm điều đó.

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.