【问题标题】:Fast ways to make new multiple files from one file matching multiple patterns从一个匹配多个模式的文件中创建新的多个文件的快速方法
【发布时间】:2016-08-18 05:48:25
【问题描述】:

我有一个名为 uniq.txt 的文件(20,000 行)。

head uniq.txt 
1 
103 
10357 
1124 
1126 

我还有一个名为 all.txt 的文件(106,371,111 行)

head all.txt
cg0001  ?   1   -0.394991215660192 
cg0001  AB  103 -0.502535661820095 
cg0002  A   10357   -0.563632386999913 
cg0003  ?   1   -0.394991215660444 
cg0004  ?   1   -0.502535661820095 
cg0004  A   10357   -0.563632386999913 
cg0003  AB  103 -0.64926706504459 

我想从 all.txt 创建新的 20,000 个文件,以匹配 uniq.txt 的每个行模式。例如,

head 1.newfile.txt 
cg0001  ?   1   -0.394991215660192 
cg0003  ?   1   -0.394991215660444 
cg0004  ?   1   -0.502535661820095 

head 103.newfile.txt 
cg0001  AB  103 -0.502535661820095 
cg0003  AB  103 -0.64926706504459 

head 10357.newfile.txt 
cg0002  A   10357   -0.563632386999913 
cg0004  A   10357   -0.563632386999913 

有什么方法可以真正快速地制作新的 20,000 个文件? 我当前的脚本需要 1 分钟来制作一个新文件。我猜它每次创建新文件时都会扫描 all.txt 文件。

【问题讨论】:

  • 为什么还需要uniq.txt?看来您可以仅从 all.txt... 生成文件...
  • 有什么方法可以快速从 all.txt 生成新文件?能否请你帮忙?我是初学者>,
  • 如果我的数学计算正确,那么它应该需要超过 333 小时 20 分钟。

标签: linux bash file scripting makefile


【解决方案1】:

你可以用 awk 试试。理想情况下,您不需要在 awk 中使用 >>,但由于您已声明将有 20,000 个文件,因此我们不想通过打开太多文件来耗尽系统资源。

awk '
    NR==FNR { names[$0]++; next }
    ($3 in names) { file=$3".newfile.txt"; print $0 >>(file); close (file) }
' uniq.txt all.txt

这将首先将 uniq.txt 文件扫描到内存中,创建一个查找表。然后它将通读 all.txt 文件并开始将条目插入相应的文件中。

【讨论】:

  • 去 Jaypal 的路。 @sony 这将是解决您问题的非常有效的方法。祝大家好运;-)!
【解决方案2】:

这使用了while 循环——这可能是也可能不是最快的方法,尽管试一试:

lines_to_files.sh

#!/bin/bash

while IFS='' read -r line || [[ -n "$line" ]]; do
    num=$(echo "$line" | awk '{print $3}') 
    echo "$line" >> /path/to/save/${num}_newfile.txt
done < "$1"

用法:

$ ./lines_to_files.sh all.txt

这应该根据第三列为all.txt 文件中的每一行创建一个新文件。当它读取每一行时,它将把它添加到适当的文件中。请记住,如果您连续运行脚本,它将附加每个文件已经存在的数据。

可以在此处找到上面用于读取苍蝇的 while 循环的说明:

https://stackoverflow.com/a/10929511/499581

【讨论】:

  • 感谢您的帮助,@I'L'l
【解决方案3】:

您可以将每一行读入一个 Bash 数组,然后附加到以第三列中的数字命名的文件(数组索引 2):

#!/bin/bash

while read -ra arr; do
    echo "${arr[@]}" >> "${arr[2]}".newfile.txt
done < all.txt

这会创建空格分隔的输出。如果您更喜欢制表符分隔,这在一定程度上取决于您的输入数据:如果它也是制表符分隔的,您只需将IFS 设置为制表符即可获得制表符分隔的输出:

IFS=$'\t'
while read -ra arr; do
    echo "${arr[*]}" >> "${arr[2]}".newfile.txt
done < all.txt

注意打印数组的变化,* 现在实际上是必需的。

或者,如果输入数据不是制表符分隔的(或者我们不知道),我们可以在每个循环的子shell中设置IFS

while read -ra arr; do
    ( IFS=$'\t'; echo "${arr[*]}" >> "${arr[2]}".newfile.txt )
done < all.txt

我不确定哪个更昂贵,生成一个子shell 或一些参数分配,但我觉得这是子shell - 为了避免生成它,我们可以在每个循环中设置和重置IFS

while read -ra arr; do
    old_ifs="$IFS"
    IFS=$'\t'
    echo "${arr[*]}" >> "${arr[2]}".newfile.txt
    IFS="$old_ifs"
done < all.txt

【讨论】:

  • 感谢您的建议。你的方法当然行得通,但事实证明 awk 是最快的@jaypal singh
【解决方案4】:

OP 要求 快速 方式。这是我发现的最快的。

sort -S 4G -k3,3 all.txt |
  awk '{if(last!=$3){close(file); file=$3".newfile.txt"; last=$3} print $0 > file}'

总时间为 2 分 4.910 秒,而亚军则为 10 分 4.058 秒。请注意,它使用 4 GB 内存(如果更多可能会更快,如果更少肯定会更慢)并且它会忽略 uniq.txt

全尺寸输入文件的结果(100,000,000 行 all.txt、20,000 行 uniq.txt):

sort awk write             me  ~800,000 input lines/second
awk append      @jaypal-singh  ~200,000 input lines/second
bash append       @benjamin-w   ~15,000 input lines/second
bash append + extra awk  @lll     ~2000 input lines/second

这是我创建测试文件的方式:

seq 1 20000 | sort -R | sed 's/.*/cg0001\tAB\t&\t-0.502535661820095/' > tmp.txt
seq 1 5000 | while read i; do cat tmp.txt; done > all.txt
seq 1 20000 | sort -R > uniq.txt

PS:对我最初的测试设置中的缺陷表示歉意。

【讨论】:

  • 感谢您推荐@webb。但是,我无法使用您的方式使其工作。我在阅读 -ra arr 时尝试了以下操作; do > old_ifs="$IFS" > IFS=$'\t' > grep "${arr[*]}" all.txt > "${arr[2]}".newfile.txt > done
  • @sony,抱歉,有错字。固定的。也更新为使用制表符分隔的all.txt。它可以按原样在我的测试文件上运行,没有arrs。
  • 我真的很惊讶 awk 只比 Bash while 循环快 30%。您的解决方案处理 1,000,000 行长 all.txt 20,000 次 - grep 快吗?还是在 awk 中关闭文件句柄如此昂贵?
  • 感谢您引导我发现我如何设置我的第一个测试的错误!
猜你喜欢
  • 2020-12-08
  • 2022-01-07
  • 1970-01-01
  • 2017-06-23
  • 1970-01-01
  • 2021-04-02
  • 2018-07-26
  • 2017-12-15
  • 1970-01-01
相关资源
最近更新 更多