从大文件中提取数据的更快方法
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) '
【讨论】:
以上是关于从大文件中提取数据的更快方法的主要内容,如果未能解决你的问题,请参考以下文章