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


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.