【问题标题】:Split a large, compressed file into multiple outputs using AWK and BASH使用 AWK 和 BASH 将大型压缩文件拆分为多个输出
【发布时间】:2011-10-09 16:01:43
【问题描述】:

我有一个大 (3GB) 的 gzip 文件,其中包含两个字段:NAME 和 STRING。我想将此文件拆分为较小的文件 - 如果字段一是 john_smith,我希望将字符串放在 john_smith.gz 中。注意:字符串字段可以并且确实包含特殊字符。

我可以使用 BASH 在域上的 for 循环中轻松完成此操作,但我更喜欢使用 AWK 一次读取文件的效率。

我尝试在 awk 中使用系统函数,并在字符串周围使用转义单引号

zcat large_file.gz | awk '{system("echo -e '"'"'"$1"\t"$2"'"'"' | gzip >> "$1".gz");}'

并且它在大多数行上都能正常工作,但是其中一些会打印到 STDERR 并给出 shell 无法执行命令的错误(shell 认为字符串的一部分是命令)。看起来特殊字符可能会破坏它。

对如何解决这个问题有任何想法,或者有什么替代实现会有所帮助?

谢谢!

-肖恩

【问题讨论】:

  • 欢迎来到 SO!您收到了几个高质量的答案。如果您收到了一个很好的答案,请立即通过投票让他们;如果答案完全解决了您的问题,最好“接受”(绿色复选标记),这样其他人就会知道他们何时正在寻找相同的解决方案。

标签: linux bash awk split gzip


【解决方案1】:

将此程序创建为largesplitter.c 并使用命令

zcat large_file.gz | largesplitter

朴素的程序是:

#include <errno.h>
#include <stdio.h>
#include <string.h>

int main (void)
{
        char    buf [32000];  // todo:  resize this if the second field is larger than 
        char    cmd [120];
        long    linenum = 0;
        while (fgets (buf, sizeof buf, stdin))
        {
                ++linenum;
                char *cp = strchr (buf, '\t');   // identify first field delimited by tab
                if (!cp)
                {
                        fprintf (stderr, "line %d missing delimiter\n", linenum);
                        continue;
                }
                *cp = '\000';  // split line
                FILE *out = fopen (buf, "w");
                if (!out)
                {
                        fprintf (stderr, "error creating '%s': %s\n", buf, strerror(errno));
                        continue;
                }
                fprintf (out, "%s", cp+1);
                fclose (out);
                snprintf (cmd, sizeof cmd, "gzip %s", buf);
                system (cmd);
        }
        return 0;
}

这在我的系统上编译没有错误,但我没有测试它的功能。

【讨论】:

  • 2 注意:这将为每一行打开/关闭一个文件,并创建非常次优的压缩输出(每行的压缩头和统计信息)。
  • @sehe:是的,这是我对原始命令所做的解释,尽管我现在看到它应该将压缩内容附加到现有文件中。但这似乎并不完全正确。
  • FWIW 我对其进行了测试,它确实有效,但是,压缩比会受到影响(包括非常小的输入行可能会出现大小爆炸)
【解决方案2】:

也许可以尝试以下方式:

zcat large_file.gz | echo $("awk '{system("echo -e '"'"'"$1"\t"$2"'"'"' | gzip &gt;&gt; "$1".gz");}'")

我自己没有尝试过,因为我没有任何大文件可以玩。

【讨论】:

    【解决方案3】:

    这个小 perl 脚本很好地完成了这项工作

    • 保持所有目标文件打开以提高性能
    • 进行错误基本处理
    • 编辑现在还可以通过 gzip 即时管道输出

    $fh 有点混乱,因为显然直接使用哈希条目是行不通的

    #!/usr/bin/perl
    use strict;
    use warnings;
    
    my $suffix = ".txt.gz";
    
    my %pipes;
    while (my ($id, $line) = split /\t/,(<>),2)
    {
        exists $pipes{$id} 
            or open ($pipes{$id}, "|gzip -9 > '$id$suffix'") 
            or die "can't open/create $id$suffix, or cannot spawn gzip";
    
        my $fh = $pipes{$id};
        print $fh $line;
    }
    
    print STDERR "Created: " . join(', ', map { "$_$suffix" } keys %pipes) . "\n"
    

    哦,像这样使用它

    zcat input.gz | ./myscript.pl
    

    【讨论】:

    • 这似乎完全符合我的要求。感谢您的解决方案。
    • 干杯,肖恩;如果还有其他问题,请告诉我
    【解决方案4】:

    您面临着时间与磁盘空间的巨大权衡。 我假设您正在尝试通过将记录附加到 ${name}.gz 文件的末尾来节省空间。 @sehe cmets 和代码绝对值得考虑。

    无论如何,您的时间比 3 GB 的磁盘空间更有价值。为什么不试试

     zcat large_file.gz \
     | awk '-F\t' { 
        name=$1; string=$2; outFile=name".txt"
        print name "\t" string >> outFile
        # close( outFile) 
       }'
    
     echo *.txt | xargs gzip -9
    

    您可能需要取消注释#close(outFile)。 包含 xargs 是因为我假设您将创建超过 1000 个文件名。即使您不这样做,使用该技术也不会受到伤害。

    请注意,此代码假定数据以制表符分隔,根据需要更改 -F 的 arg 值以及打印语句中的“\t”以提供所需的字段分隔符。

    没有时间测试这个。如果您喜欢这个想法并遇到困难,请发布您收到的小样本数据、预期输出和错误消息。

    我希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-11-24
      • 1970-01-01
      • 1970-01-01
      • 2016-08-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多