Tách một tệp thành nhiều tệp dựa trên dấu phân cách


86

Tôi có một tệp có -|dấu phân cách sau mỗi phần ... cần tạo các tệp riêng biệt cho từng phần bằng unix.

ví dụ về tệp đầu vào

wertretr
ewretrtret
1212132323
000232
-|
ereteertetet
232434234
erewesdfsfsfs
0234342343
-|
jdhg3875jdfsgfd
sjdhfdbfjds
347674657435
-|

Kết quả mong đợi trong Tệp 1

wertretr
ewretrtret
1212132323
000232
-|

Kết quả mong đợi trong Tệp 2

ereteertetet
232434234
erewesdfsfsfs
0234342343
-|

Kết quả mong đợi trong Tệp 3

jdhg3875jdfsgfd
sjdhfdbfjds
347674657435
-|

1
Bạn đang viết một chương trình hay bạn muốn thực hiện việc này bằng các tiện ích dòng lệnh?
rkyser

1
sử dụng các tiện ích dòng lệnh sẽ được ưu tiên hơn ..
user1499178

Bạn có thể sử dụng awk, rất dễ dàng để viết một chương trình 3 hoặc 4 dòng để làm điều đó. Thật không may, tôi không thực hành.
ctrl-alt-delor

Câu trả lời:


97

Một lớp lót, không cần lập trình. (ngoại trừ regexp, v.v.)

csplit --digits=2  --quiet --prefix=outfile infile "/-|/+1" "{*}"

đã thử nghiệm trên: csplit (GNU coreutils) 8.30

Lưu ý về việc sử dụng trên Apple Mac

"Đối với người dùng OS X, hãy lưu ý rằng phiên bản csplitđi kèm với hệ điều hành này không hoạt động. Bạn sẽ muốn phiên bản trong coreutils (có thể cài đặt qua Homebrew), được gọi là gcsplit". - @Danial

"Chỉ cần nói thêm, bạn có thể có được phiên bản cho OS X hoạt động (ít nhất là với High Sierra). Bạn chỉ cần chỉnh sửa args một chút csplit -k -f=outfile infile "/-\|/+1" "{3}". Các tính năng dường như không hoạt động "{*}", tôi phải nói rõ về số lượng dấu phân tách và cần thêm -kđể tránh nó xóa tất cả các tệp ngoại lai nếu không thể tìm thấy dấu phân tách cuối cùng. Ngoài ra, nếu muốn --digits, bạn cần sử dụng -nthay thế. " - @Pebbl


31
@ zb226 Tôi đã làm điều đó trong một thời gian dài, vì vậy không cần giải thích.
ctrl-alt-delor

5
Tôi đề nghị thêm --elide-empty-files, nếu không sẽ có một tệp trống ở cuối.
luator

8
Đối với người dùng OS X, lưu ý rằng phiên bản csplit đi kèm với hệ điều hành này không hoạt động. Bạn sẽ muốn phiên bản trong coreutils (có thể cài đặt qua Homebrew), được gọi là gcsplit .
Daniel

10
Chỉ dành cho những ai thắc mắc các tham số có nghĩa là gì: --digits=2kiểm soát số lượng chữ số được sử dụng để đánh số các tệp đầu ra (2 là mặc định đối với tôi, vì vậy không cần thiết). --quietngăn chặn đầu ra (cũng không thực sự cần thiết hoặc được yêu cầu ở đây). --prefixchỉ định tiền tố của các tệp đầu ra (mặc định là xx). Vì vậy, bạn có thể bỏ qua tất cả các tham số và sẽ nhận được các tệp đầu ra như xx12.
Christopher K.

3
Chỉ cần thêm, bạn có thể có được phiên bản cho OS X hoạt động (ít nhất là với High Sierra). Bạn chỉ cần tinh chỉnh args một chút csplit -k -f=outfile infile "/-\|/+1" "{3}". Các tính năng dường như không hoạt động là "{*}", tôi phải nói rõ về số lượng dấu phân tách và cần thêm -kđể tránh nó xóa tất cả các ngoại lai nếu nó không thể tìm thấy dấu phân tách cuối cùng. Ngoài ra nếu muốn --digits, bạn cần sử dụng -nthay thế.
Pebbl

38
awk '{f="file" NR; print $0 " -|"> f}' RS='-\\|'  input-file

Giải thích (đã chỉnh sửa):

RSlà dấu phân tách bản ghi và giải pháp này sử dụng phần mở rộng gnu awk cho phép nó có nhiều hơn một ký tự. NRlà con số kỷ lục.

Câu lệnh print in một bản ghi theo sau " -|"vào một tệp có chứa số bản ghi trong tên của nó.


1
RSlà dấu phân tách bản ghi và giải pháp này sử dụng phần mở rộng gnu awk cho phép nó có nhiều hơn một ký tự. NR là số kỷ lục. Câu lệnh print in ra một bản ghi theo sau là "- |" vào một tệp có chứa số bản ghi trong tên của nó.
William Pursell

1
@rzetterbeg Điều này sẽ hoạt động tốt với các tệp lớn. awk xử lý tệp một bản ghi tại một thời điểm, vì vậy nó chỉ đọc nhiều nhất có thể. Nếu lần xuất hiện đầu tiên của dấu phân tách bản ghi hiển thị rất muộn trong tệp, đó có thể là lỗi bộ nhớ vì toàn bộ bản ghi phải vừa với bộ nhớ. Ngoài ra, lưu ý rằng sử dụng nhiều hơn một ký tự trong RS không phải là awk tiêu chuẩn, nhưng điều này sẽ hoạt động trong awk gnu.
William Pursell

4
Đối với tôi, nó chia 3,3 GB trong 31,728 giây
Cleankod

3
@ccf Tên tệp chỉ là chuỗi ở phía bên phải của >, vì vậy bạn có thể xây dựng nó theo cách bạn muốn. ví dụ:print $0 "-|" > "file" NR ".txt"
William Pursell

1
@AGrush Đó là phiên bản phụ thuộc. Bạn có thể làmawk '{f="file" NR; print $0 " -|" > f}'
William Pursell

7

Debian có csplit, nhưng tôi không biết liệu điều đó có phổ biến với tất cả / hầu hết / các bản phân phối khác hay không. Tuy nhiên, nếu không, sẽ không quá khó để truy tìm nguồn và biên dịch nó ...


1
Tôi đồng ý. Hộp Debian của tôi nói rằng csplit là một phần của coreutils gnu. Vì vậy, bất kỳ hệ điều hành Gnu nào, chẳng hạn như tất cả các bản phân phối Gnu / Linux sẽ có nó. Wikipedia cũng đề cập đến 'Đặc điểm kỹ thuật UNIX® duy nhất, ấn bản 7' trên trang csplit, vì vậy tôi nghi ngờ bạn đã hiểu.
ctrl-alt-delor

3
csplitcó trong POSIX, tôi hy vọng nó sẽ có sẵn trên tất cả các hệ thống giống Unix.
Jonathan Leffler

1
Mặc dù csplit là POISX, nhưng vấn đề (có vẻ như đang thực hiện một bài kiểm tra với nó trên hệ thống Ubuntu trước mặt tôi) là không có cách rõ ràng nào để khiến nó sử dụng cú pháp regex hiện đại hơn. Hãy so sánh: csplit --prefix gold-data - "/^==*$/vs csplit --prefix gold-data - "/^=+$/. Ít nhất GNU grep có -e.
new123456 14/09/13

5

Tôi đã giải quyết một vấn đề hơi khác, trong đó tệp chứa một dòng có tên mà văn bản theo sau sẽ đến. Mã perl này thực hiện mẹo cho tôi:

#!/path/to/perl -w

#comment the line below for UNIX systems
use Win32::Clipboard;

# Get command line flags

#print ($#ARGV, "\n");
if($#ARGV == 0) {
    print STDERR "usage: ncsplit.pl --mff -- filename.txt [...] \n\nNote that no space is allowed between the '--' and the related parameter.\n\nThe mff is found on a line followed by a filename.  All of the contents of filename.txt are written to that file until another mff is found.\n";
    exit;
}

# this package sets the ARGV count variable to -1;

use Getopt::Long;
my $mff = "";
GetOptions('mff' => \$mff);

# set a default $mff variable
if ($mff eq "") {$mff = "-#-"};
print ("using file switch=", $mff, "\n\n");

while($_ = shift @ARGV) {
    if(-f "$_") {
    push @filelist, $_;
    } 
}

# Could be more than one file name on the command line, 
# but this version throws away the subsequent ones.

$readfile = $filelist[0];

open SOURCEFILE, "<$readfile" or die "File not found...\n\n";
#print SOURCEFILE;

while (<SOURCEFILE>) {
  /^$mff (.*$)/o;
    $outname = $1;
#   print $outname;
#   print "right is: $1 \n";

if (/^$mff /) {

    open OUTFILE, ">$outname" ;
    print "opened $outname\n";
    }
    else {print OUTFILE "$_"};
  }

Bạn có thể vui lòng giải thích tại sao mã này hoạt động? Tôi gặp trường hợp tương tự như những gì bạn đã mô tả ở đây - tên tệp đầu ra bắt buộc được nhúng bên trong tệp. Nhưng tôi không phải là người dùng perl thường xuyên nên không hiểu rõ về mã này.
shiri

Thịt bò thật ở whilevòng cuối cùng . Nếu nó tìm thấy mffregex ở đầu dòng, nó sẽ sử dụng phần còn lại của dòng làm tên tệp để mở và bắt đầu ghi vào. Nó không bao giờ đóng bất cứ thứ gì vì vậy nó sẽ hết các tệp xử lý sau vài chục.
tripleee

Tập lệnh sẽ thực sự được cải thiện bằng cách loại bỏ hầu hết mã trước whilevòng lặp cuối cùng và chuyển sangwhile (<>)
tripleee

4

Lệnh sau phù hợp với tôi. Hy vọng nó giúp.

awk 'BEGIN{file = 0; filename = "output_" file ".txt"}
    /-|/ {getline; file ++; filename = "output_" file ".txt"}
    {print $0 > filename}' input

1
Điều này sẽ hết xử lý tệp sau khi thường có vài chục tệp. Cách khắc phục là xóa closetệp cũ một cách rõ ràng khi bạn bắt đầu một tệp mới.
tripleee

@tripleee bạn đóng nó như thế nào (câu hỏi awk dành cho người mới bắt đầu). Bạn có thể cung cấp một ví dụ cập nhật không?
Jesper Rønn-Jensen

1
@ JesperRønn-Jensen Hộp này có thể quá nhỏ đối với bất kỳ ví dụ hữu ích nào nhưng về cơ bản if (file) close(filename);trước khi gán một filenamegiá trị mới .
tripleee

aah phát hiện ra làm thế nào để đóng nó: ; close(filename). Thật đơn giản, nhưng nó thực sự sửa chữa ví dụ trên
Jesper Ronn-Jensen

1
@ JesperRønn-Jensen Tôi đã lùi bản chỉnh sửa của bạn vì bạn đã cung cấp một tập lệnh bị hỏng. Có thể nên tránh những chỉnh sửa đáng kể đối với câu trả lời của người khác - hãy đăng một câu trả lời mới của riêng bạn (có thể là wiki cộng đồng ) nếu bạn cho rằng một câu trả lời riêng là xứng đáng.
tripleee

2

Bạn cũng có thể sử dụng awk. Tôi không quen thuộc lắm với awk, nhưng những điều sau đây dường như hiệu quả với tôi. Nó tạo ra part1.txt, part2.txt, part3.txt và part4.txt. Xin lưu ý rằng tệp partn.txt cuối cùng mà tệp này tạo ra trống. Tôi không chắc làm thế nào để khắc phục điều đó, nhưng tôi chắc chắn rằng nó có thể được thực hiện với một chút điều chỉnh. Bất kỳ đề nghị bất cứ ai?

tệp awk_pattern:

BEGIN{ fn = "part1.txt"; n = 1 }
{
   print > fn
   if (substr($0,1,2) == "-|") {
       close (fn)
       n++
       fn = "part" n ".txt"
   }
}

lệnh bash:

awk -f awk_pattern input.file


2

Đây là một tập lệnh Python 3 chia một tệp thành nhiều tệp dựa trên tên tệp được cung cấp bởi các dấu phân cách. Tệp đầu vào mẫu:

# Ignored

######## FILTER BEGIN foo.conf
This goes in foo.conf.
######## FILTER END

# Ignored

######## FILTER BEGIN bar.conf
This goes in bar.conf.
######## FILTER END

Đây là kịch bản:

#!/usr/bin/env python3

import os
import argparse

# global settings
start_delimiter = '######## FILTER BEGIN'
end_delimiter = '######## FILTER END'

# parse command line arguments
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--input-file", required=True, help="input filename")
parser.add_argument("-o", "--output-dir", required=True, help="output directory")

args = parser.parse_args()

# read the input file
with open(args.input_file, 'r') as input_file:
    input_data = input_file.read()

# iterate through the input data by line
input_lines = input_data.splitlines()
while input_lines:
    # discard lines until the next start delimiter
    while input_lines and not input_lines[0].startswith(start_delimiter):
        input_lines.pop(0)

    # corner case: no delimiter found and no more lines left
    if not input_lines:
        break

    # extract the output filename from the start delimiter
    output_filename = input_lines.pop(0).replace(start_delimiter, "").strip()
    output_path = os.path.join(args.output_dir, output_filename)

    # open the output file
    print("extracting file: {0}".format(output_path))
    with open(output_path, 'w') as output_file:
        # while we have lines left and they don't match the end delimiter
        while input_lines and not input_lines[0].startswith(end_delimiter):
            output_file.write("{0}\n".format(input_lines.pop(0)))

        # remove end delimiter if present
        if not input_lines:
            input_lines.pop(0)

Cuối cùng, đây là cách bạn chạy nó:

$ python3 script.py -i input-file.txt -o ./output-folder/

2

Sử dụng csplitnếu bạn có nó.

Nếu bạn không, nhưng bạn có Python ... đừng sử dụng Perl.

Lười đọc tệp

Tệp của bạn có thể quá lớn để chứa tất cả trong bộ nhớ cùng một lúc - đọc từng dòng có thể thích hợp hơn. Giả sử tệp đầu vào có tên là "samplein":

$ python3 -c "from itertools import count
with open('samplein') as file:
    for i in count():
        firstline = next(file, None)
        if firstline is None:
            break
        with open(f'out{i}', 'w') as out:
            out.write(firstline)
            for line in file:
                out.write(line)
                if line == '-|\n':
                    break"

Điều này sẽ đọc toàn bộ tệp vào bộ nhớ, có nghĩa là nó sẽ không hiệu quả hoặc thậm chí không thành công đối với các tệp lớn.
tripleee

1
@tripleee Tôi đã cập nhật câu trả lời để xử lý các tệp rất lớn.
Aaron Hall

0
cat file| ( I=0; echo -n "">file0; while read line; do echo $line >> file$I; if [ "$line" == '-|' ]; then I=$[I+1]; echo -n "" > file$I; fi; done )

và phiên bản định dạng:

#!/bin/bash
cat FILE | (
  I=0;
  echo -n"">file0;
  while read line; 
  do
    echo $line >> file$I;
    if [ "$line" == '-|' ];
    then I=$[I+1];
      echo -n "" > file$I;
    fi;
  done;
)

4
Hơn bao giờ hết, những catlà vô dụng .
tripleee

1
@Reishin Trang được liên kết giải thích chi tiết hơn nhiều cách bạn có thể tránh cattrên một tệp trong mọi tình huống. Có một câu hỏi Stack Overflow với nhiều thảo luận hơn (mặc dù câu trả lời được chấp nhận là IMHO tắt); stackoverflow.com/questions/11710552/useless-use-of-cat
tripleee

1
Dù sao thì shell thường rất kém hiệu quả ở dạng này; nếu bạn không thể sử dụng csplit, giải pháp Awk có lẽ phù hợp hơn nhiều so với giải pháp này (ngay cả khi bạn đã khắc phục sự cố được báo cáo bởi shellcheck.net, v.v.; lưu ý rằng nó hiện không tìm thấy tất cả các lỗi trong điều này).
tripleee

@tripleee nhưng nếu nhiệm vụ là thực hiện mà không có awk, csplit và v.v. - chỉ bash?
Reishin

1
Sau đó, catvẫn vô dụng, và phần còn lại của tập lệnh có thể được đơn giản hóa và sửa chữa một cách tốt; nhưng nó sẽ vẫn còn chậm. Xem ví dụ: stackoverflow.com/questions/13762625/…
tripleee

0

Đây là loại sự cố tôi đã viết phân tách ngữ cảnh cho: http://stromberg.dnsalias.org/~strombrg/context-split.html

$ ./context-split -h
usage:
./context-split [-s separator] [-n name] [-z length]
        -s specifies what regex should separate output files
        -n specifies how output files are named (default: numeric
        -z specifies how long numbered filenames (if any) should be
        -i include line containing separator in output files
        operations are always performed on stdin

Uh, về cơ bản nó giống như một bản sao của csplittiện ích tiêu chuẩn . Xem câu trả lời của @ richard .
tripleee

Đây thực sự là giải pháp tốt nhất imo. Tôi đã phải chia một bãi chứa mysql 98G và csplit vì một lý do nào đó ăn hết RAM của tôi và bị giết. Mặc dù nó chỉ cần khớp với một dòng tại thời điểm đó. Không có ý nghĩa gì. Tập lệnh python này hoạt động tốt hơn nhiều và không ăn hết ram.
Stefan Midjich 20/02/18

0

Đây là một mã perl sẽ làm điều đó

#!/usr/bin/perl
open(FI,"file.txt") or die "Input file not found";
$cur=0;
open(FO,">res.$cur.txt") or die "Cannot open output file $cur";
while(<FI>)
{
    print FO $_;
    if(/^-\|/)
    {
        close(FO);
        $cur++;
        open(FO,">res.$cur.txt") or die "Cannot open output file $cur"
    }
}
close(FO);
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.