使用BASH中的shell脚本在正则表达式上将一个大的txt文件分割成200个小的txt文件

11

希望主题足够清晰,在以前提出的bin中没有找到与此特定问题相关的内容。我尝试在Perl或Python中实现这个,但我认为我可能尝试过头了。

是否有一个简单的 shell 命令/管道可以根据开始和结束的正则表达式将我的 4mb .txt 文件拆分成单独的 .txt 文件?

我在下面提供文件的简短样本...所以你可以看到每个“故事”都以短语“X OF XXX DOCUMENTS”开始,这可以用来拆分文件。

我认为这应该很容易,并且如果 bash 不能做到比 Perl/Py 更快,我会感到惊讶。

以下是示例:

                           1 of 999 DOCUMENTS


              Copyright 2011 Virginian-Pilot Companies LLC
                          All Rights Reserved
                   The Virginian-Pilot(Norfolk, VA.)

...



                           3 of 999 DOCUMENTS


                  Copyright 2011 Canwest News Service
                          All Rights Reserved
                          Canwest News Service

...

感谢您提前为所有帮助所做的努力。

Ross


1
需要这么多的示例文本吗? - jakev
1
请编辑并删除您问题中约95%的文本。 - Dennis Williamson
5个回答

22
awk '/[0-9]+ of [0-9]+ DOCUMENTS/{g++} { print $0 > g".txt"}' file

若使用OSX操作系统, 需要安装gawk, 内置的awk会产生如下错误: awk: illegal statement at source line 1

Ruby(1.9+)

#!/usr/bin/env ruby
g=1
f=File.open(g.to_s + ".txt","w")
open("file").each do |line|
  if line[/\d+ of \d+ DOCUMENTS/]
    f.close
    g+=1
    f=File.open(g.to_s + ".txt","w")
  end
  f.print line
end

哦,我们有一个赢家...速度和优雅。我在1997年的一个非常潮湿的夏天里花了很多时间阅读O'Reilly的sed/awk书籍。但愿我现在还能回忆起所有的内容。我明天一定会去找它。谢谢! - user574141
1
这个解决方案将匹配的行放入新文件中,回答了问题。但是如果像我一样,你想在开始新文件之前将匹配的行放入旧文件中,你可以这样做:awk '{print $0 > n".txt"} /text to match/ {n++} - indiv
1
注意:在Mac OS X上,您需要从MacPorts等位置获取gawk才能使此工作正常。 - Thomas Wana

10

如其他解决方案所建议,你可以使用 csplit 来实现:

csplit csplit.test '/^\.\.\./' '{*}' && sed -i '/^\.\.\./d' xx*

我还没有找到更好的方法来除去分割文件中的分隔符。


我现在无法尝试,因为我在Windows上,但csplit的手册似乎建议使用%REGEX%而不是/REGEX/:/REGEXP/[OFFSET] 复制到但不包括匹配行%REGEXP%[OFFSET] 跳过到但不包括匹配行 - Spikolynn

1
你在Perl中尝试了多少次? 编辑 这里有一种更快的方法。它将文件分割,然后打印部分文件。
use strict;
use warnings;

my $count = 1;

open (my $file, '<', 'source.txt') or die "Can't open source.txt: $!";

for (split /(?=^.*\d+[^\S\n]*of[^\S\n]*\d+[^\S\n]*DOCUMENTS)/m, join('',<$file>))
{
    if ( s/^.*(\d+)\s*of\s*\d+\s*DOCUMENTS.*(\n|$)//m )
    {
        open (my $part, '>', "Part$1_$count.txt") 
            or die "Can't open Part$1_$count for output: $!";
        print $part $_;
        close ($part);
        $count++;
    }
}
close ($file);

这是逐行方法:

use strict;
use warnings;

open (my $masterfile, '<', 'yourfilename.txt') or die "Can't open yourfilename.txt: $!";

my $count = 1;
my $fh;

while (<$masterfile>) {
    if ( /(?<!\d)(\d+)\s*of\s*\d+\s*DOCUMENTS/ ) {
        defined $fh and close ($fh);
        open ($fh, '>', "Part$1_$count.txt") or die "Can't open Part$1_$count for  output: $!";
        $count++;
        next;
    }
    defined $fh and print $fh $_;
}
defined $fh and close ($fh);
close ($masterfile);

“$count”未定义。我怀疑您想使用的是“$cnt”。此外,第一次运行循环时,“$fh”也未定义,因此在尝试关闭“$fh”时,您将会收到一个“无法使用未定义值作为符号引用”的错误/警告信息。 - CanSpice
完全不懂Perl - 不是我没有尝试...会一些Perl、Python、R、Ruby、bash和少量C++。我除了从事工作和研究之外,也在努力学习。谢谢您的帮助。 - user574141
最好也在最终的close()上加一个检查。 - user557597
@rosser - 噢,在 Perl 中并不那么糟糕。可以从命令行中完成一个经过简化的版本,所谓的一行代码。 - user557597
无法在getfile.pl的第16行使用未定义的值作为符号引用,在$masterfile的第1行。 - user574141
@rosser - 很好的发现!你是对的,你知道如何修复它吗?defined $fh and print $fh $_;这只是一个未经测试的例子,现在已经修复了。对于我的使用,我可能会以不同的方式编写它。 - user557597

0

匹配“X of XXX DOCUMENTS”的正则表达式为
\d{1,3} of \d{1,3) DOCUMENTS

逐行读取并在正则表达式匹配时开始写入新文件应该没问题。


-1

未经测试:

base=outputfile
start=1
pattern='^[[:blank:]]*[[:digit:]]+ OF [[:digit:]]+ DOCUMENTS[[:blank:]]*$

while read -r line
do
    if [[ $line =~ $pattern ]]
    then
        ((start++))
        printf -v filecount '%4d' $start
        >"$base$filecount"    # create an empty file named like foo0001
    fi
    echo "$line" >> "$base$filecount"
done

顺便说一下,以上是纯Bash。此外,我相信Python或Perl会更快。 - Dennis Williamson
1
你能用csplit做吗? csplit -k -z --digits=3 --suffix='%d.TXT' --prefix=FILE *.TXT /'SPLITONTHIS' - user574141
@rosser - 这是一个可以拆分的候选项,不过我不知道 csplit。 - user557597
@sln:split 生成的是固定大小的输出文件,而不是正则表达式。@rosser:csplit 是一个明确的选择。 - Dennis Williamson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接