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

Faster way to extract data from large file

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

我尝试使用 bash 脚本和 for-loop。

for i in {0..39999}
do
    cat  | grep -A 27 "frame $i " | tail -n 6 | awk '{print , , }' >> 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

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

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

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

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

您的程序运行缓慢的原因是您在 for 循环中不断地重新读取输入文件。你可以通过一次读取你的文件来完成所有的事情,然后使用 awk 代替:

awk '/frame/{c=0;next}{c++}(c>20 && c<27){ print ,, }' 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;next}{c++}(fc>=1000 && fc <=1500) && (c>20 && c<27){ print ,, }' input > output 

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

假设您想要在每一帧之后显示第 21-27 行,并且您不想记录帧编号本身,那么以下短语应该会得到您想要的行,然后您可以 'tidy' 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}) '