テキストファイルを複数のテキストファイルに分割するにはどうすればよいですか?


16

entry.txt次の内容を含むテキストファイルがあります。

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631
[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631
[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

私は3つのテキストファイルに分割したいと思います:entry1.txtentry2.txtentry3.txt。内容は次のとおりです。

entry1.txt

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631

entry2.txt

[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631

entry3.txt

[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

つまり、この[文字は新しいファイルを開始する必要があることを示します。エントリ([ entry*]*は整数)は常に数値順であり、1からNまでの連続した整数です(実際の入力ファイルでは、N = 200001)。

bashで自動テキストファイル分割を実現する方法はありますか?実際の入力entry.txtには、実際に200,001エントリが含まれています。

回答:


11

そして、ここに素敵でシンプルなgawkのワンライナーがあります:

$ gawk '/^\[/{match($0, /^\[ (.+?) \]/, k)} {print >k[1]".txt" }' entry.txt

これは、各エントリヘッダーがのように見える限り、各エントリの行数に関係なく、任意のファイルサイズで機能します[ blahblah blah blah ]。ただ開封後のスペースに注意してください[、ちょうど終了する前に]


説明:

awkそしてgawkラインによる入力ファイルの行を読み取ります。各行が読み取られると、その内容が$0変数に保存されます。ここでは、gawk角かっこ内のすべてのものに一致するように指示し、その一致を配列に保存しkます。

そのため、その正規表現が一致するたびに、つまり、ファイル内のすべてのヘッダーに対して、k [1]には一致した行の領域が含まれます。つまり、「entry1」、「entry2」または「entry3」または「entryN」です。

最後に、<whatever value k currently has>.txt各行を、entry1.txt、entry2.txt ... entryN.txt というファイルに出力します。

この方法は、大きなファイルの場合、perlよりもはるかに高速です。


+1いいね。matchエントリーする必要はありません:/^\[/ { name=$2 }十分なはずです。
トール

@Thorに感謝します。記載されているケースでは、提案は正しいものですが、エントリ名にスペースが含まれていないことを前提としています。それが[ blahblah blah blah ]私の答えで例を使用した理由です。
テルドン

ああ、スペースで区切られたエントリについて少し見逃しました。また、これらと収容できるFS、例えば-F '\\[ | \\]'
トール

@terdon私はこの短い解決策が本当に好きですが、残念ながら私は通常それらを私のニーズに合わせて一般化できません。手を貸してくれませんか?私のファイルには#S x、xが1、2、または3桁の数字で始まる行があります。それらをx.datに保存するだけで十分です。私が試した: gawk '/^#S/{match($0, / [0-9]* /, k)} {print >k[1]".dat" }' myFile.txtそして、そのいくつかのバリエーション。
mikuszefski

それgawk '/^#S/{match($0, /^#S (\s+?)([0-9]+)(\s+?)/, k)} {print >k[2]".txt" }' test.txtはトリックをしました。2ただし、配列番号を十分に理解しないでください。
mikuszefski

17

csplitは GNUのcoreutilsの(非組込みLinux、Cygwinの)から:

csplit -f entry -b '%d.txt' entry.txt '/^\[ .* \]$/' '{*}'

余分な空のファイルentry0.txt(最初のヘッダーの前の部分を含む)になります。

標準のcsplitには、{*}不定リピータと-b接尾辞形式を指定するオプションがないため、他のシステムでは、最初にセクションの数を数え、その後で出力ファイルの名前を変更する必要があります。

csplit -f entry -n 9 entry.txt '/^\[ .* \]$/' "{$(egrep -c '^'\[ .* \]$' <entry.txt)}"
for x in entry?????????; do
  y=$((1$x - 1000000000))
  mv "entry$x" "entry$y.txt"
done

csplitは時々ちょっと風変わりですが、この種のことをしたいときは非常に便利です。
ixtmixilix

10

perlではもっと簡単にできます:

perl -ne 'open(F, ">", ($1).".txt") if /\[ (entry\d+) \]/; print F;' file

9

ここに短いawkのワンライナーがあります:

awk '/^\[/ {ofn=$2 ".txt"} ofn {print > ofn}' input.txt

これはどのように作動しますか?

  • /^\[/ 左角括弧で始まる行に一致します。
  • {ofn=$2 ".txt"}出力ファイル名として2番目の空白で区切られた単語に変数を設定します。その後、
  • ofn 変数が設定されている場合にtrueと評価される条件です(したがって、最初のヘッダーの前の行は無視されます)
  • {print > ofn} 現在の行を指定されたファイルにリダイレクトします。

コンパクトさがあなたを幸せにするなら、このawkスクリプトのすべてのスペースを削除できることに注意してください。

また、上記のスクリプトでは、セクションヘッダーの内部ではなく周囲にスペースが必要であることに注意してください。[foo]やなどのセクションヘッダーを処理できるようにし[ this that ]たい場合は、もう少しコードが必要になります。

awk '/^\[/ {sub(/^\[ */,""); sub(/ *\] *$/,""); ofn=$0 ".txt"} ofn {print > ofn}' input.txt

これは、awkのsub()関数を使用して、先頭と末尾の角括弧と空白を取り除きます。標準のawkの動作に従って、これは空白(フィールドセパレーター)を単一のスペースに折りたたむ(つまり[ this that ]に保存される"this that.txt")ことに注意してください。出力ファイル名の元の空白を維持することが重要な場合は、FSを設定して実験できます。


2

Pythonのコマンドラインから次のように実行できます。

paddy$ python3 -c 'out=0
> with open("entry.txt") as f: 
>   for line in f:
>     if line[0] == "[":
>       if out: out.close()
>       out = open(line.split()[1] + ".txt", "w")
>     else: out.write(line)'

2

これはやや粗雑ですが、簡単に理解できる方法grep -l '[ entry ]' FILENAMEです。[entry]で行番号を分割するために使用します。頭と尻尾の組み合わせを使用して、適切なピースを取得します。

私が言ったように。きれいではありませんが、理解するのは簡単です。


2

[レコードセパレータとしてawkを使用し、フィールドセパレータとしてスペースを使用するのはどうですか。これにより$0、削除された先頭[とファイル名を$1。次に、空の1番目のレコードの特殊なケースのみを処理する必要があります。これにより、以下が得られます。

awk -v "RS=[" -F " " 'NF != 0 {print "[" $0 > $1}' entry.txt

2

terdonの答えは私には有効ですが、awkではなくgawkを使用する必要がありました。gawkのマニュアル(「(試合」)を検索し、一致で配列引数は()gawkの拡張であることを説明しています。たぶんそれは、インストールあなたのLinuxに依存し、あなたのawk / nawkの/ gawkのバージョンが、私のUbuntuマシンのみgawkのRANのterdonの優れた上で回答:

$ gawk '{if(match($0, /^\[ (.+?) \]/, k)){name=k[1]}} {print >name".txt" }' entry.txt

1

これがperlソリューションです。このスクリプトは[ entryN ]行を検出し、それに応じて出力ファイルを変更しますが、各セクションのデータを検証、解析、処理するのではなく、入力行を出力ファイルに出力するだけです。

#! /usr/bin/perl 

# default output file is /dev/null - i.e. dump any input before
# the first [ entryN ] line.

$outfile='/dev/null';
open(OUTFILE,">",$outfile) || die "couldn't open $outfile: $!";

while(<>) {
  # uncomment next two lines to optionally remove comments (starting with
  # '#') and skip blank lines.  Also removes leading and trailing
  # whitespace from each line.
  # s/#.*|^\s*|\s*$//g;
  # next if (/^$/)

  # if line begins with '[', extract the filename
  if (m/^\[/) {
    (undef,$outfile,undef) = split ;
    close(OUTFILE);
    open(OUTFILE,">","$outfile.txt") || die "couldn't open $outfile.txt: $!";
  } else {
    print OUTFILE;
  }
}
close(OUTFILE);

1

こんにちは私はあなたの問題を解決するためにルビーを使用してこの簡単なスクリプトを書きました

#!ruby
# File Name: split.rb

fout = nil

while STDIN.gets
  line = $_
  if line.start_with? '['
    fout.close if fout
    fname = line.split(' ')[1] + '.txt'
    fout = File.new fname,'w'
  end
  fout.write line if fout
end

fout.close if fout

次のように使用できます。

ruby split.rb < entry.txt

私はそれをテストしました、そしてそれはうまく働きます..


1

私はこのcsplitオプションを好みますが、代替としてここにGNU awkソリューションがあります:

parse.awk

BEGIN { 
  RS="\\[ entry[0-9]+ \\]\n"  # Record separator
  ORS=""                      # Reduce whitespace on output
}
NR == 1 { f=RT }              # Entries are of-by-one relative to matched RS
NR  > 1 {
  split(f, a, " ")            # Assuming entries do not have spaces 
  print f  > a[2] ".txt"      # a[2] now holds the bare entry name
  print   >> a[2] ".txt"
  f = RT                      # Remember next entry name
}

次のように実行します。

gawk -f parse.awk entry.txt

1
FWIW、RT変数はgawk固有のようです。この解決策は、FreeBSDのawkを使用している場合は機能しません。
ゴティ

@ghoti:そうですね。今、私はそれを答えに含めました。ありがとう。
トール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.