区切り文字に基づいて1つのファイルを複数のファイルに分割します


88

-|各セクションの後に区切り文字として1つのファイルがあります... unixを使用してセクションごとに個別のファイルを作成する必要があります。

入力ファイルの例

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 2012

1
好ましいであろうコマンドラインユーティリティを使用して...
user1499178

あなたはawkを使うことができます、それをするために3または4行のプログラムを書くのは簡単でしょう。残念ながら、私は練習していません。
ctrl-alt-delor 2012

回答:


98

ワンライナー、プログラミングなし。(正規表現などを除く)

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

テスト済み: csplit (GNU coreutils) 8.30

AppleMacでの使用に関する注意

「OSXユーザーの場合csplit、OSに付属しているバージョンは機能しないことに注意してください。coreutils(Homebrew経由でインストール可能)にある、と呼ばれるバージョンが必要になりますgcsplit。」— @Danial

「追加するだけで、OS Xのバージョンを動作させることができます(少なくともHigh Sierraでは)。引数を少し調整する必要がありますcsplit -k -f=outfile infile "/-\|/+1" "{3}"。動作しないように見える機能は、"{*}"特定する必要がありました。セパレータの数-k。最後のセパレータが見つからない場合にすべての出力ファイルが削除されないように追加する必要があります。また--digits、必要に応じて、-n代わりにを使用する必要があります。」— @pebbl


31
@ zb226長い間やったので、説明は必要ありませんでした。
ctrl-alt-delor 2014年

5
追加することをお勧めします。追加--elide-empty-filesしないと、最後に空のファイルが作成されます。
luator 2014年

8
OS Xユーザーの場合、OSに付属しているバージョンのcsplitが機能しないことに注意してください。gcsplitと呼ばれるcoreutils(Homebrew経由でインストール可能)のバージョンが必要になります。
ダニエル

10
パラメータが何を意味するのか疑問に思っている人のために:--digits=2出力ファイルに番号を付けるために使用される桁数を制御します(2は私にとってデフォルトなので、必要ありません)。--quiet出力を抑制します(これも実際には必要ではないか、ここで要求されます)。--prefix出力ファイルのプレフィックスを指定します(デフォルトはxx)。したがって、すべてのパラメータをスキップして、のような出力ファイルを取得できますxx12
クリストファーK.

3
追加するだけで、OS Xのバージョンを動作させることができます(少なくともHigh Sierraでは)。引数を少し微調整する必要がありますcsplit -k -f=outfile infile "/-\|/+1" "{3}"。動作していないように見える機能は、"{*}"セパレータの数を指定する必要-kがあり、最後のセパレータが見つからない場合にすべての出力ファイルが削除されないように追加する必要がありました。また--digits、必要に応じて、-n代わりに使用する必要があります。
Pebbl 2018年

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はファイルを一度に1レコードずつ処理するため、必要な量だけ読み取ります。レコード区切り文字の最初の出現がファイルの非常に遅い位置に表示される場合、1つのレコード全体がメモリに収まらなければならないため、メモリが不足している可能性があります。また、RSで複数の文字を使用することは標準のawkではありませんが、これはgnuawkで機能することに注意してください。
William Pursell 2014

4
私にとっては、31.728秒で3.3 GBを分割しました
Cleankod

3
@ccfファイル名は、の右側にある文字列である>ため、好きなように作成できます。例print $0 "-|" > "file" NR ".txt"
William Pursell 2016

1
@AGrushこれはバージョンによって異なります。あなたにできることawk '{f="file" NR; print $0 " -|" > f}'
WilliamPursell20年

7

Debianにはcsplitありますが、それがすべて/ほとんど/他のディストリビューションに共通しているかどうかはわかりません。そうでない場合でも、ソースを追跡してコンパイルするのはそれほど難しいことではありません...


1
同意する。私のDebianボックスには、csplitはgnucoreutilsの一部であると書かれています。したがって、すべてのGnu / Linuxディストリビューションなど、すべてのGnuオペレーティングシステムに搭載されます。ウィキペディアでは、csplitページに「TheSingleUNIX®Specification、Issue 7」についても言及されているので、理解できたと思います。
ctrl-alt-delor 2012

3
csplitはPOSIXにあるので、基本的にすべてのUnixライクなシステムで利用できると思います。
ジョナサンレフラー2012

1
csplitはPOISXですが、問題(目の前にあるUbuntuシステムでテストを行っているようです)は、より最新の正規表現構文を使用する明確な方法がないことです。比較:csplit --prefix gold-data - "/^==*$/vs csplit --prefix gold-data - "/^=+$/。少なくともGNUgrepには-e
new123456 2013

5

少し異なる問題を解決しました。ファイルには、次のテキストを配置する名前の行が含まれています。このperlコードは私のためにトリックを行います:

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

このコードが機能する理由を説明していただけますか?私はあなたがここで説明したのと同様の状況にあります-必要な出力ファイル名はファイル内に埋め込まれています。しかし、私は通常のperlユーザーではないので、このコードを完全に理解することはできません。
shiri 2017年

本物の牛肉は最後のwhileループにあります。mff行の先頭に正規表現が見つかった場合は、行の残りの部分をファイル名として使用して開き、書き込みを開始します。何も閉じないため、数十後にファイルハンドルが不足します。
トリプリー2018年

スクリプトは、最後のwhileループの前にほとんどのコードを削除し、while (<>)
triplee 2018年

4

次のコマンドは私のために働きます。それが役に立てば幸い。

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

1
これは、通常、数十のファイルの後にファイルハンドルを使い果たします。修正はclose、新しいファイルを開始するときに古いファイルを明示的に行うことです。
トリプリー2018年

@tripleeeどのように閉じますか(初心者のawk質問)。更新された例を提供できますか?
–JesperRønn-Jensen 2018

1
@JesperRønn-Jensenこのボックスは、有用な例にはおそらく小さすぎますが、基本的if (file) close(filename);には新しいfilename値を割り当てる前です。
トリプリー2018年

ああ、それを閉じる方法を見つけました:; close(filename)。本当に簡単ですが、上記の例を本当に修正します
–JesperRønn-Jensen 2018

1
@JesperRønn-Jensen壊れたスクリプトを提供したため、編集をロールバックしました。他の人の回答を大幅に編集することはおそらく避けてください。別の回答に値すると思われる場合は、独自の新しい回答を投稿してください(おそらくコミュニティウィキとして)。
トリプリー2018年

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

これは、区切り文字で指定されたファイル名に基づいてファイルを複数のファイルに分割するPython3スクリプトです。入力ファイルの例:

# 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を使用しないでください。

ファイルの怠惰な読み取り

ファイルが大きすぎて一度にメモリに保持できない場合があります。1行ずつ読み取る方が望ましい場合があります。入力ファイルの名前が「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"

これにより、ファイル全体がメモリに読み込まれます。つまり、大きなファイルの場合は非効率になるか、失敗することさえあります。
トリプリー2018年

1
@tripleee非常に大きなファイルを処理するように回答を更新しました。
アーロンホール

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
トリプリー2016

1
@Reishinリンクされたページではcat、あらゆる状況で1つのファイルを回避する方法について詳しく説明しています。より多くの議論を伴うスタックオーバーフローの質問があります(受け入れられた答えは私見オフですが)。stackoverflow.com/questions/11710552/useless-use-of-cat
triplee 2018年

1
とにかく、シェルは通常、この種のことでは非常に非効率的です。を使用できない場合csplitは、おそらくAwkソリューションの方がこのソリューションよりもはるかに好ましいでしょう(shellcheck.netなどによって報告された問題を修正したとしても、現在、このソリューションのすべてのバグが検出されているわけではないことに注意してください)。
トリプリー2018年

@tripleeeですが、タスクがawk、csplitなどなしでそれを行うことである場合-bashのみですか?
Reishin

1
その後、catはまだ役に立たず、スクリプトの残りの部分は単純化され、かなり修正される可能性があります。しかし、それでも遅いでしょう。たとえば、stackoverflow.com / questions / 13762625 /…を
Tripleee 2018年

0

これは私がcontext-splitを書いた種類の問題です: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の回答を参照してください。
トリプリー2016

これは実際には最良の解決策です。何らかの理由で98Gmysqlダンプとcsplitを分割する必要があり、RAMをすべて使い果たして、強制終了されました。一度に1行だけ一致する必要があるはずですが。意味がありません。このPythonスクリプトははるかにうまく機能し、RAMをすべて使い果たすわけではありません。
Stefan Midjich 2018

0

これがそのことをするperlコードです

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