【问题标题】:Split a large gz file into smaller ones filtering and distributing content将大的 gz 文件拆分为较小的文件,过滤和分发内容
【发布时间】:2020-12-20 23:46:19
【问题描述】:

我有一个大小为 81G 的 gzip 文件,我解压后的文件大小为 254G。我想实现一个 bash 脚本,它采用 gzip 文件并根据第一列对其进行拆分。第一列的值范围在 1-10 之间。我想将文件拆分为 10 个子文件,其中第一列中值为 1 的所有行都放入 1 个文件中。第一列中值为 2 的所有行都放入第二个文件中,依此类推。当我这样做时,我不想将第 3 列和第 5 列放在新的子文件中。该文件也是制表符分隔的。例如:

col_1    col_2.   col_3.  col_4.  col_5.  col_6
1.       7464      sam.    NY.     0.738.  28.9
1.       81932.    Dave.   NW.     0.163.  91.9
2.       162.      Peter.  SD.     0.7293. 673.1
3.       7193.     Ooni    GH.     0.746.  6391
3.       6139.     Jess.   GHD.    0.8364. 81937
3.       7291.     Yeldish HD.     0.173.  1973

上面的文件将生成三个不同的 gzip 文件,因此 col_3 和 col_5 将从每个新的子文件中删除。我所做的是

#!/bin/bash
#SBATCH --partition normal
#SBATCH --mem-per-cpu 500G
#SBATCH --time 12:00:00
#SBATCH -c 1



awk -F, '{print > $1".csv.gz"}' file.csv.gz


但这并没有产生预期的结果。另外我不知道如何从新的子文件中删除 col_3 和 col_5。 就像我说的 gzip 文件是 81G,因此,我正在寻找一个有效的解决方案。我们将不胜感激。

【问题讨论】:

  • 你的字段分隔符是什么?多个空格、一个制表符还是一个逗号?
  • @Cyrus 它说“标签分隔”。
  • 标题行是真实的还是为了说明?
  • 总是制表符分隔。
  • @BenjaminW。这是为了说明目的。

标签: bash awk sbatch


【解决方案1】:

你必须解压和重新压缩;要摆脱第 3 列和第 5 列,您可以像这样使用 GNU cut

gunzip -c infile.gz \
    | cut --complement -f3,5 \
    | awk '{ print | "gzip > " $1 "csv.gz" }'

或者你可以去掉 awk 中的列:

gunzip -c infile.gz \
    | awk -v OFS='\t' '{ print $1, $2, $4, $6 | "gzip > " $1 "csv.gz" }'

【讨论】:

  • 感谢您的评论。我按照你的建议做了。但是,当我尝试上传生成的文件时,我收到以下错误:EOFError: Compressed file ends before the end-of-stream marker was reached.
  • @John 您是否可能用完了磁盘空间来保存输出文件,所以最终写入了部分文件?
  • 我打开了它创建的第一个文件。如果磁盘空间不足,则不应创建其他文件,但确实会创建其他文件。
  • @John 它在第一次遇到第 1 列中的相应值时创建每个文件。
  • 那么我该如何继续,因为我保留了 500G 来处理 gzip 文件。我原来的 gzip 文件是 81G。这就是为什么我想根据第一列将其拆分为较小的文件。
【解决方案2】:

类似

zcat input.csv.gz | cut -f1,2,4,6- | awk '{ print | ("gzip -c > " $1 "csv.gz") }'

解压缩文件,删除字段 3 和 5,根据第一列保存到相应的压缩文件中。

【讨论】:

  • 虽然我个人会使用 zstandard 而不是 gzip 来处理任何新的压缩文件。
  • 不会覆盖现有的输出文件,这样每个 n.csv.gz 最终只包含来自一个输入行的数据吗?
  • @John 不,第一次调用 gzip 的管道将保持打开状态,直到 awk 终止(或在其上调用 close()),所以 gzip 只被调用一次每个唯一 @ 987654325@,每行输入一次。
【解决方案3】:

如果文件始终按您的示例中所示的第一个字段排序,则可以使用任何 awk 进行可靠且可移植的操作:

gunzip -c infile.gz |
awk '
    { $0 = $1 OFS $2 OFS $4 OFS $6 }
    NR==1 { hdr = $0; next }
    $1 != prev { close(gzip); gzip="gzip > \047"$1".csv.gz\047"; prev=$1 }
    !seen[$1]++ { print hdr | gzip }
    { print | gzip }
'

否则:

gunzip -c infile.gz |
awk 'BEGIN{FS=OFS="\t"} {print (NR>1), NR, $0}' |
sort -k1,1n -k3,3 -k2,2n |
cut -f3- |
awk '
    { $0 = $1 OFS $2 OFS $4 OFS $6 }
    NR==1 { hdr = $0; next }
    $1 != prev { close(gzip); gzip="gzip > \047"$1".csv.gz\047"; prev=$1 }
    !seen[$1]++ { print hdr | gzip }
    { print | gzip }
'

第一个 awk 在前面添加一个数字以确保在 sort 阶段期间标题行在其余部分之前排序,并添加行号以便具有相同原始第一个字段值的行保留其原始输入顺序。然后我们按第一个字段排序,然后把第一步添加的2个字段剪掉,然后使用awk健壮可移植地创建单独的输出文件,确保每个输出文件都以一个header的副本开头。我们边走边关闭每个输出文件,这样脚本就可以使用任何 awk 处理任意数量的输出文件,并且即使使用 GNU awk 处理大量输出文件也能有效地工作。它还确保正确引用每个输出文件名以避免通配、分词和文件名扩展。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-26
    • 2020-03-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多