【问题标题】:Splitting one large file into many if the text in a column doesn't match the text in the one before it如果一列中的文本与前一列中的文本不匹配,则将一个大文件拆分为多个文件
【发布时间】:2018-05-30 17:52:22
【问题描述】:

我搜索了一段时间,找不到对此的回应。我有一个格式如下的标准 tsv 文件:

1    100    101    350    A
1    101    102    300    A
1    102    103    180    A
1    800    801    60     B
1    801    802    70     B
1    802    803    82     B
1    975    976    105    C
1    976    977    108    C

等等。这持续了几百万行,第 5 列(A、B、C)中有 1000 个不同的区域。就行数而言,这些区域的大小都不同。我想遍历文件并将每个区域拆分为自己的文件。

文件A.txt

1    100    101    350    A
1    101    102    300    A
1    102    103    180    A

文件B.txt

1    800    801    60     B
1    801    802    70     B
1    802    803    82     B

文件C.txt

1    975    976    105    C
1    976    977    108    C

【问题讨论】:

  • 鉴于您添加的标签 - 看起来您正在为任何人寻找一个只需提供代码的解决方案。但是,您是否尝试过这些语言或工具中的任何东西,并发现了任何可以/不可行的东西?
  • 假设你想要 python。标签总是在 char long 上吗?
  • 你探索过csplit吗?看到这个帖子:splitting textfiles according to a regular expression.
  • Stack Overflow 不是代码编写服务。请出示您的代码。由于 Stack Overflow 向您隐藏了关闭原因:寻求调试帮助的问题(“为什么这段代码不起作用?”)必须包括所需的行为、特定问题或错误以及在问题本身。没有明确问题陈述的问题对其他读者没有用处。请参阅:How to create a Minimal, Complete, and Verifiable example
  • @MegaIng 是的,他做到了。每次他对这个问题发表评论或任何其他负面评论时,他都会这样做。我现在再次投票以补偿他们。

标签: python-3.x csv awk sed


【解决方案1】:

使用 awk

awk '{out = "File" $NF ".txt"; print >> out; close(out)}' file

更高效,不会在每一行之后关闭目标文件:

awk '
    $NF != dest {if (out) close(out); dest = $NF; out = "File" dest ".txt"} 
    {print >> out}
' file

【讨论】:

  • 1000 个打开的文件在大多数系统上都不是问题,所以{ print > "File" $NF } 应该足够了
  • 是的。这是相关的:unix.stackexchange.com/q/36841/4667 -- 对于我的系统,ulimit -n 是 1024,所以 1000 非常接近。
  • 您实际上可以只写 close(out) 而不是 if (out) close(out) 因为将空字符串传递给 close() 或尝试关闭未通过重定向打开的文件是无操作并关闭() 只会返回成功。如果我还没有对每个答案都投赞成票来补偿@jww 对他们都投了反对票,那么我真的会投赞成票!
【解决方案2】:

所以脚本单通低内存逐行方法:

while IFS=" " read -r value1 value2 value3 value4 value5 remainder
do
  echo $value1 $value2 $value3 $value4 $value5 $remainder >> output.${value5}.txt
done < "input.txt"

当然,您需要确保没有预先存在的输出文件,但这可以通过多种方式有效地实现。

【讨论】:

  • 而不是硬编码所需的变量数量,bash 允许您读入数组:while read -ra values; do ...,然后您可以使用last=${values[-1]} 获取最后一个字段,或者获取第 5 个字段fifth=${values[4]}
  • 确实如此,但这取决于 d_k 的要求。两者都遭受您丢失列的确切间距布局的痛苦,尽管我不知道 d_k 是否关心这一点。有很多改进。
  • 确实如此。你可以做IFS= read -r line; do [[ $line =~ ([^[:blank:]]+)[[:blank:]]*$ ]]; last=${BASH_REMATCH[1],现在你有了原始形式的行和最后一个字段。
【解决方案3】:

所以使用 grep,类似于:

for L in `grep -oE '[A-Z]+$'|uniq|sort|uniq`
do
grep -E ${L}'$' > file.${L}.txt
done 

短语grep -oE '[A-Z]+$'|uniq|sort|uniq 应该找到所有唯一键,然后您可以使用它们多次重新解析文件。序列 uniq|sort|uniq 是为了减少输入进行排序。

如果您真的需要一次性完成,那么您可以处理每一行,并立即将其附加到相应的输出文件中。

【讨论】:

  • 这非常低效,尤其是因为输入文件很大并且有数千个模式要循环。
  • 然后做第二件事。这也具有低内存开销。也许我会写下来。
【解决方案4】:

这是一个使用groupbystr.rpartition的python小解决方案:

from itertools import groupby

with open("in_file.txt") as f_in:
for name,lines in groupby(f_in.readlines(),key=lambda x:x.rpartition(" ")[2].strip()):
        with open(f"out_{name}.txt","w") as f_out:
            f_out.writelines(lines)

【讨论】:

  • 谢谢,标签是可变长度的。下面的 awk 解决方案效果很好。感谢您的帮助。
猜你喜欢
  • 2021-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-27
  • 1970-01-01
  • 2017-10-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多