从大文件中提取数据的更快方法

Posted

技术标签:

【中文标题】从大文件中提取数据的更快方法【英文标题】:Faster way to extract data from large file 【发布时间】:2019-12-31 10:37:55 【问题描述】:

我的文件包含大约 40000 帧的 28 个原子的笛卡尔坐标。我需要从每一帧中提取原子 21 到 27 的坐标。

我尝试使用带有 for 循环的 bash 脚本。

for i in 0..39999
do
    cat $1 | grep -A 27 "frame $i " | tail -n 6 | awk 'print $2, $3, $4' >> new_coors.xyz
done

数据有以下形式:

28
-1373.82296 frame 0   xyz file generated by terachem
  Re       1.6345663991    0.9571586961    0.3920887712
   N       0.7107677071   -1.0248027788    0.5007181135
   N      -0.3626961076    1.1948218124   -0.4621264246
   C      -1.1299268126    0.0792071086   -0.5595954110
   C      -0.5157993503   -1.1509115191   -0.0469223696
   C       1.3354467762   -2.1017253883    1.0125736017
   C       0.7611763218   -3.3742177216    0.9821756556
   C      -1.1378354025   -2.4089069492   -0.1199253156
   C      -0.4944655989   -3.5108477831    0.4043826684
   C      -0.8597552614    2.3604180994   -0.9043060625
   C      -2.1340008843    2.4846545826   -1.4451933224
   C      -2.4023114639    0.1449111237   -1.0888703147
   C      -2.9292779079    1.3528434658   -1.5302429615
   H       2.3226814021   -1.9233467458    1.4602019023
   H       1.3128699342   -4.2076373780    1.3768411246
   H      -2.1105470176   -2.5059031902   -0.5582958817
   H      -0.9564415355   -4.4988963635    0.3544299401
   H      -0.1913951275    3.2219343258   -0.8231465989
   H      -2.4436044324    3.4620639189   -1.7693069306
   H      -3.0306593902   -0.7362803011   -1.1626515622
   H      -3.9523215784    1.4136948699   -1.9142814745
   C       3.3621999538    0.4972227756    1.1031860016
   O       4.3763020637    0.2022266109    1.5735343064
   C       2.2906331057    2.7428149541    0.0483795630
   O       2.6669163864    3.8206298898   -0.1683800650
   C       1.0351398442    1.4995168190    2.1137684156
   O       0.6510904387    1.8559680025    3.1601927094
  Cl       2.2433490373    0.2064711824   -1.9226174036

它有效,但需要大量时间, 将来我将使用更大的文件。有更快的方法吗?

【问题讨论】:

文件样本将不胜感激... 文件中的帧号是否按顺序排列? 此外,其他命令以及 for 循环也占用内存,如果您可以让我们知道确切的要求,那么我们可以在单个 awk 本身中执行此操作,正如 Jepessen 所提到的,请添加示例在您的帖子中,然后让我们知道。 @Adam Srut,请不要在您的帖子中以图片或附件的形式在您的帖子中发布示例,请这样做并让我们知道。 【参考方案1】:

如果文件中的帧数已经排序,例如他们按这个顺序有数字 0 - 39999,然后也许像这样的东西可以完成这项工作(未经测试,因为我们没有示例输入文件,正如 Jepessen 建议的那样):

cat $1 | grep -A 27 -E "frame [0-9]+ " | \
awk 'if ($1 == "frame") n = 0; if (n++ > 20) print $2, $3, $4' > new_coors.xyz

(上面的代码明确冗长,以便更容易理解并更接近您现有的脚本。如果您需要更紧凑的解决方案,请查看 kvantour 答案)

【讨论】:

特别是因为我们在这里讨论效率,所以在您的解决方案中删除不必要的cat。少一个要运行的子进程。 我并没有试图让我的解决方案高效,而是让它与 OP 相似,以便 OP 更容易理解。你对 cat 是正确的,grep 正则表达式也可以集成到 awk 中,最后答案可能看起来像 kvantour 的答案。我选择干净而不是高效的代码,但再次感谢!【参考方案2】:

您的程序运行缓慢的原因是您在 for 循环中一遍又一遍地重新读取输入文件。您可以通过一次读取文件来完成所有操作,并改用 awk:

awk '/frame/c=0;nextc++(c>20 && c<27) print $2,$3,$4 ' input > output 

此答案假定以下数据形式:

frame ???
??? x y z ???
??? x y z ???
...
frame ???
??? x y z ???
??? x y z ???
...

解决方案检查是否在一行中找到单词frame。如果是这样,它将原子计数器c 设置为零并跳到下一行。从那时起,如果读取新行,它将始终读取增加计数器。如果计数器在 20 和 27(不包括)之间,它将打印坐标。

您现在可以轻松地对此进行扩展:假设您想要相同的原子,但只需要从第 1000 帧到 1500 帧。您可以通过引入帧计数器fc来做到这一点

awk '/frame/fc++;c=0;nextc++(fc>=1000 && fc <=1500) && (c>20 && c<27) print $2,$3,$4 ' input > output 

【讨论】:

【参考方案3】:

您也许可以使用 2 次 grep,而不是数千次?

假设您想要每帧之后的第 21-27 行,并且您不想记录帧号本身,以下短语应该得到您想要的行,然后您可以使用 awk '整理':

grep -A27 ' frame ' | grep -B6 '-----'

如果您还想要帧号(我没有看到任何证据),或者您真的想限制帧号的范围,您可以使用 tee 和 >( grep 'frame') 来生成第二个文件然后需要重新合并。如果您将 -n 添加到 grep 中,那么您可以轻松地对行号上的文件进行合并排序。

另一种在不执行多次传递的情况下限制帧数的方法是使用更复杂的 grep 表达式来描述数字的范围(-E,因为反引号的寿命太短了):

-E ' frame (([0-9]1,4|[0-3][0-9]1,4) '

【讨论】:

以上是关于从大文件中提取数据的更快方法的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法让 zip 或其他压缩文件更快地提取?

从大表中检索聚合数据的更快方法?

Java订阅从大文件中读取新行

如何从大fasta文件中找出自己想要的序列

如何使用 nltk 从大文本语料库中仅提取英文单词?

使用 UCanAccess 从大文本文件中插入数据非常慢