【问题标题】:Splitting a large txt file into 200 smaller txt files on a regex using shell script in BASH使用 BASH 中的 shell 脚本在正则表达式上将一个大的 txt 文件拆分为 200 个较小的 txt 文件
【发布时间】:2011-06-24 12:48:35
【问题描述】:

我希望主题足够清楚,我在之前询问的垃圾箱中没有找到任何具体的内容。我已经尝试在 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

...

提前感谢您的帮助。

罗斯

【问题讨论】:

标签: regex bash shell unix scripting


【解决方案1】:

匹配“X of XXX DOCUMENTS”的正则表达式是
\d{1,3} 个,共 \d{1,3) 个文档

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

【讨论】:

    【解决方案2】:

    未经测试:

    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 会快得多。
    • 你能用 csplit 做吗? csplit -k -z --digits=3 --suffix='%d.TXT' --prefix=FILE *.TXT /'SPLITONTHIS'
    • @rosser - 这是分裂的候选者,虽然不知道 csplit
    • @sln: split 执行固定大小的输出文件,而不是正则表达式。 @rosser:csplit 绝对有可能。
    【解决方案3】:

    您在 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 时会收到 Can't use an undefined value as a symbol reference 错误/警告。
    • 在 Perl 中,我的头脑太复杂了——并不是说我没有尝试... Perl,Python,R,Ruby 的一些片段,bash,一点 C++。以及作为一名求职医生并尝试做一些研究......寻求帮助。
    • 最好也检查一下最后的 close()
    • @rosser - 哦,它在 Perl 中还不错。可以从命令行完成一个精简的版本,即所谓的 1 班轮。
    • 不能在 getfile.pl 第 16 行, 第 1 行使用未定义的值作为符号引用。
    【解决方案4】:
    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 书。希望我现在能回忆起这一切。我去拿它tmrw。 谢谢你
    • 此解决方案将匹配行放入新文件中,从而回答问题。但是,如果像我一样,您想在开始新文件之前将匹配的行放在旧文件中,您可以这样做:awk '{print $0 &gt; n".txt"} /text to match/ {n++}
    • 注意:在 Mac OS X 上,您需要来自例如 gawk MacPorts 让这个工作
    【解决方案5】:

    根据其他解决方案的建议,您可以为此使用 csplit

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

    我还没有找到更好的方法来消除拆分文件中的记忆分隔符。

    【讨论】:

    • 我现在无法尝试,因为在 Windows 上,但 csplit 的手册页似乎建议使用 %REGEX% 而不是 /REGEX/ : /REGEXP/[OFFSET] 复制到但不包括匹配行 %REGEXP%[OFFSET] 跳到,但不包括匹配行
    猜你喜欢
    • 1970-01-01
    • 2017-06-24
    • 2017-03-09
    • 2013-08-05
    • 2013-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-11
    相关资源
    最近更新 更多