使用AWK和BASH将一个大的压缩文件拆分为多个输出

4
我有一个大小为3GB的压缩文件,其中包含两个字段:NAME和STRING。我想将此文件拆分成较小的文件 - 如果第一个字段是john_smith,则希望将字符串放置在john_smith.gz中。注意:字符串字段可以并且确实包含特殊字符。
我可以使用BASH循环轻松地完成域的操作,但我更喜欢使用AWK一次读取文件的效率。
我尝试在awk内使用system函数,并在字符串周围加上转义的单引号:
zcat large_file.gz | awk '{system("echo -e '"'"'"$1"\t"$2"'"'"' | gzip >> "$1".gz");}'
它在大多数行上完美运行,但有些行会打印到STDERR,并显示无法执行命令的错误(shell认为该字符串的一部分是命令)。看起来特殊字符可能会破坏它。
有没有想法如何修复这个问题,或者任何其他实现方法可以帮助?
谢谢!
-Sean

欢迎来到SO!您已经收到了几个高质量的答案。如果您收到了有帮助的答案,请通过投票表达感谢;如果一个答案完全解决了您的问题,那么点击“接受”(绿色勾号)是很好的,这样其他人在寻找相同解决方案时就会知道。 - sehe
4个回答

2

您面临着时间与磁盘空间之间的巨大抉择。我猜您正在尝试通过将记录追加到${name}.gz文件的末尾来节省空间。@sehe的评论和代码绝对值得考虑。

无论如何,您的时间比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个文件名。即使没有这么多,使用该技巧也不会有害。

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

暂无时间测试此代码。如果您喜欢这个想法但遇到问题,请发布一些样本数据、期望的输出以及您正在收到的错误消息。

希望这可以帮到您。


0
将此程序命名为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;
}

在我的系统上可以编译通过,但我还没有测试其功能。


这将为每一行打开/关闭一个文件,并创建非常低效的压缩输出(每行都有压缩头和统计信息)。 - sehe
@sehe:是的,那是我对原始命令的理解,尽管我现在看到它应该将压缩内容附加到现有文件中。但仍然感觉不太对。 - wallyk
就我测试过的结果来说,它是可行的。但是压缩比可能会受到影响(包括对于非常小的输入行可能导致大小扩张)。 - sehe

0

这个小的 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

这似乎正是我想要的。感谢您提供的解决方案。 - Sean
谢谢,Sean;如果有进一步的问题,请告诉我。 - sehe

0
也许可以尝试以下代码: zcat large_file.gz | echo $("awk '{system("echo -e '"'"'"$1"\t"$2"'"'"' | gzip >> "$1".gz");}'") 由于我没有大文件可供测试,所以无法亲自尝试。

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