구분 기호에 따라 하나의 파일을 여러 파일로 분할


88

-|각 섹션 뒤에 구분 기호가있는 파일이 하나 있습니다 ... 유닉스를 사용하여 각 섹션에 대해 별도의 파일을 만들어야합니다.

입력 파일의 예

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

파일 1의 예상 결과

wertretr
ewretrtret
1212132323
000232
-|

파일 2의 예상 결과

ereteertetet
232434234
erewesdfsfsfs
0234342343
-|

파일 3의 예상 결과

jdhg3875jdfsgfd
sjdhfdbfjds
347674657435
-|

1
프로그램을 작성 중입니까 아니면 명령 줄 유틸리티를 사용하여 수행 하시겠습니까?
— rkyser

1
명령 줄 유틸리티를 사용하는 것이 좋습니다.
— user1499178

awk를 사용할 수 있습니다. 3 줄 또는 4 줄 프로그램을 작성하는 것은 쉽습니다. 불행히도 나는 연습이 없습니다.
— CTRL-ALT-delor

답변:


98

하나의 라이너, 프로그래밍 없음. (정규식 등 제외)

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

테스트 대상 : csplit (GNU coreutils) 8.30

Apple Mac에서의 사용에 대한 참고 사항

"OS X 사용자의 경우 OS csplit와 함께 제공 되는 버전이 작동하지 않는다는 점에 유의하십시오 . Coreutils (Homebrew를 통해 설치 가능)라는 버전을 원할 것 gcsplit입니다. — @ 다니 알

"추가하기 만하면 OS X가 작동 할 버전을 얻을 수 있습니다 (적어도 High Sierra에서). args를 약간 조정하면 csplit -k -f=outfile infile "/-\|/+1" "{3}"됩니다. 작동하지 않는 기능은 다음과 같습니다 "{*}". 구분자의 수이며 -k최종 구분자를 찾을 수없는 경우 모든 outfile을 삭제하지 않도록 추가 해야합니다. 또한 원하는 경우 대신 --digits사용해야 -n합니다. " — @Pebbl


31
@ zb226 나는 오랫동안 그것을 했으므로 설명이 필요하지 않았습니다.
— ctrl-alt-delor 2014-06-07

5
을 추가하는 것이 좋습니다 --elide-empty-files. 그렇지 않으면 끝에 빈 파일이 있습니다.
— luator

8
OS X 사용자의 경우 OS와 함께 제공되는 csplit 버전이 작동하지 않습니다. gcsplit 이라는 coreutils 버전 (Homebrew를 통해 설치 가능)을 원할 것 입니다.
— Daniel

10
매개 변수가 무엇을 의미하는지 궁금한 사람들을 위해 : --digits=2출력 파일에 번호를 매기는 데 사용되는 자릿수를 제어합니다 (2는 기본값이므로 필요하지 않음). --quiet출력을 억제합니다 (실제로 필요하지 않거나 여기서 요청하지 않음). --prefix출력 파일의 접두사를 지정합니다 (기본값은 xx). 따라서 모든 매개 변수를 건너 뛸 수 있으며 xx12.
— Christopher K.

3
추가하기 만하면 OS X가 작동 할 버전을 얻을 수 있습니다 (적어도 High Sierra에서). args를 약간 조정하면 csplit -k -f=outfile infile "/-\|/+1" "{3}"됩니다. 작동하지 않는 것 같은 기능은이며 "{*}", 구분자 수를 구체적으로 지정 -k해야했으며 최종 구분자를 찾을 수없는 경우 모든 아웃 파일을 삭제하지 않도록 추가 해야했습니다. 또한 원하는 경우 대신 --digits사용해야 -n합니다.
— Pebbl

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

설명 (편집 됨) :

RS이 솔루션은 두 개 이상의 문자가 될 수있는 gnu awk 확장을 사용합니다. NR레코드 번호입니다.

print 문 " -|"은 이름에 레코드 번호가 포함 된 파일에 뒤에 레코드를 인쇄합니다 .


1
RS이 솔루션은 두 개 이상의 문자가 될 수있는 gnu awk 확장을 사용합니다. NR은 레코드 번호입니다. print 문은 "-|"뒤에 레코드를 인쇄합니다. 이름에 레코드 번호를 포함하는 파일로.
— William Pursell 2014

1
@rzetterbeg 이것은 큰 파일에서 잘 작동합니다. awk는 파일을 한 번에 한 레코드 씩 처리하므로 필요한만큼만 읽습니다. 레코드 구분 기호의 첫 번째 항목이 파일에서 매우 늦게 나타나는 경우 하나의 전체 레코드가 메모리에 맞아야하므로 메모리 크런치 일 수 있습니다. 또한 RS에서 하나 이상의 문자를 사용하는 것은 표준 awk가 아니지만 gnu awk에서 작동합니다.
— William Pursell

4
나를 위해 그것은 31.728s에서 3.3GB를 분할
— Cleankod

3
@ccf 파일 이름은의 오른쪽에있는 문자열 >이므로 원하는대로 구성 할 수 있습니다. 예 :print $0 "-|" > "file" NR ".txt"
— William Pursell

1
@AGrush 버전에 따라 다릅니다. 당신은 할 수 있습니다awk '{f="file" NR; print $0 " -|" > f}'
— William Pursell

7

데비안에는 csplit이 있지만 이것이 모든 / 대부분 / 다른 배포판에 공통적인지 모르겠습니다. 그렇지 않다면 소스를 추적하고 컴파일하는 것이 너무 어렵지 않습니다.


1
나는 동의한다. 내 데비안 상자에 csplit이 gnu coreutils의 일부라고 나와 있습니다. 따라서 모든 Gnu / Linux 배포판과 같은 모든 Gnu 운영 체제에는이 기능이 있습니다. 위키피디아는 csplit 페이지에서 'The Single UNIX® Specification, Issue 7'을 언급하기도하므로 이해하신 것 같습니다.
— CTRL-ALT-delor

3
csplitPOSIX에 있기 때문에 본질적으로 모든 유닉스 계열 시스템에서 사용할 수있을 것으로 기대합니다.
— Jonathan Leffler 2012

1
csplit은 POISX이지만 문제는 (내 앞에 앉아있는 Ubuntu 시스템에서 테스트를 수행하는 것 같습니다)보다 현대적인 정규식 구문을 사용하도록 만드는 분명한 방법이 없다는 것입니다. 비교:csplit --prefix gold-data - "/^==*$/ 대 csplit --prefix gold-data - "/^=+$/. 적어도 GNU grep에는 -e.
— new123456

5

나는 약간 다른 문제를 해결했는데, 파일에는 뒤에 오는 텍스트가 들어가야하는 이름이있는 줄이 포함되어 있습니다. 이 펄 코드는 나를 위해 트릭을 수행합니다.

#!/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 "$_"};
  }

이 코드가 작동하는 이유를 설명해 주시겠습니까? 여기서 설명한 것과 비슷한 상황이 있습니다. 필요한 출력 파일 이름이 파일 안에 포함되어 있습니다. 하지만 저는 일반 펄 사용자가 아니므로이 코드를 이해하기 어렵습니다.
— 쉬리

진짜 쇠고기는 마지막 while루프에 있습니다. mff줄의 시작 부분 에서 정규식을 찾으면 나머지 줄을 열고 쓰기를 시작할 파일 이름으로 사용합니다. 아무것도 닫지 않으므로 수십 후에 파일 핸들이 부족합니다.
— tripleee

스크립트는 실제로 최종 while루프 전에 대부분의 코드를 제거 하고 다음으로 전환하여 개선 될 것입니다.while (<>)
— tripleee

4

다음 명령이 저에게 효과적입니다. 도움이 되었기를 바랍니다.

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

1
일반적으로 수십 개의 파일 후에 파일 핸들이 부족합니다. 수정 사항은 close새 파일을 시작할 때 이전 파일 을 명시 적으로 지정 하는 것입니다.
— tripleee 2010 년

@tripleee 어떻게 닫습니까 (초보 awk 질문). 업데이트 된 예를 제공 할 수 있습니까?
— Jesper Rønn-Jensen 2011

1
@ JesperRønn-Jensen이 상자는 유용한 예제에 비해 너무 작지만 기본적으로 if (file) close(filename);새 filename값을 할당하기 전 입니다.
— tripleee

aah가 닫는 방법을 찾았습니다 ; close(filename).. 정말 간단하지만 정말 위의 예를 해결
— 예스퍼 Rønn - 젠슨

1
@ JesperRønn-Jensen 깨진 스크립트를 제공했기 때문에 수정 사항을 롤백했습니다. 다른 사람의 답변에 대한 중대한 수정은 피해야 합니다. 별도의 답변이 적합하다고 생각되면 자신의 새 답변 (아마도 커뮤니티 위키 )을 자유롭게 게시하세요 .
— tripleee

2

awk를 사용할 수도 있습니다. 나는 awk에 익숙하지 않지만 다음은 나를 위해 작동하는 것 같습니다. part1.txt, part2.txt, part3.txt 및 part4.txt를 생성했습니다. 이것이 생성하는 마지막 partn.txt 파일은 비어 있습니다. 나는 그것을 어떻게 고칠지는 모르겠지만 약간의 조정으로 할 수 있다고 확신합니다. 어떤 제안이라도?

awk_pattern 파일 :

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

bash 명령 :

awk -f awk_pattern input.file


2

다음은 구분 기호에서 제공하는 파일 이름을 기반으로 파일을 여러 파일로 분할하는 Python 3 스크립트입니다. 입력 파일 예 :

# Ignored

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

# Ignored

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

다음은 스크립트입니다.

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

마지막으로 실행 방법은 다음과 같습니다.

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

2

csplit가지고 있다면 사용하십시오 .

그렇지 않지만 Python이있는 경우 Perl을 사용하지 마십시오.

파일 지연 읽기

파일이 너무 커서 메모리에 한 번에 저장할 수 없습니다. 한 줄씩 읽는 것이 좋습니다. 입력 파일의 이름이 "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"

이것은 전체 파일을 메모리로 읽어들이므로 비효율적이거나 대용량 파일의 경우 실패 할 수도 있습니다.
— tripleee 2010 년

1
@tripleee 매우 큰 파일을 처리하기 위해 답변을 업데이트했습니다.
— 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 )

및 형식화 된 버전 :

#!/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
이제까지로, 는 cat쓸모가 없다 .
— tripleee

1
@Reishin 링크 된 페이지는 cat모든 상황에서 단일 파일을 피할 수있는 방법을 훨씬 더 자세히 설명합니다 . 더 많은 토론이있는 Stack Overflow 질문이 있습니다 (수용된 답변은 IMHO 해제 됨). stackoverflow.com/questions/11710552/useless-use-of-cat
— tripleee 2010 년

1
어쨌든 쉘은 일반적으로 이런 종류의 일에서 매우 비효율적입니다. 를 사용할 수 없다면 csplitAwk 솔루션이이 솔루션보다 훨씬 더 선호 될 것입니다 ( shellcheck.net 등에서 보고 된 문제를 수정하려는 경우에도 마찬가지입니다 . 현재 여기에서 모든 버그를 찾을 수는 없습니다).
— tripleee

@tripleee 그러나 작업이 awk, csplit 등없이 수행하는 경우-bash 만?
— Reishin

1
그런 다음 cat여전히 쓸모가 없으며 나머지 스크립트를 단순화하고 좋은 방법으로 수정할 수 있습니다. 하지만 여전히 느릴 것입니다. 참조 예 : stackoverflow.com/questions/13762625/…
— tripleee

0

이것은 내가 문맥 분할을 위해 작성한 일종의 문제입니다. 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

어, 이것은 본질적으로 표준 csplit유틸리티 의 복제물처럼 보입니다 . @richard의 대답을 참조하십시오 .
— tripleee

이것은 실제로 최고의 솔루션 imo입니다. 나는 98G mysql 덤프를 분할하고 어떤 이유로 든 csplit을해야 내 모든 RAM을 차지하고 죽었다. 한 번에 한 줄만 일치하면되지만 말이 안 돼. 이 파이썬 스크립트는 훨씬 더 잘 작동하며 모든 숫양을 먹지 않습니다.
— Stefan Midjich

0

다음은 작업을 수행 할 펄 코드입니다.

#!/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);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.