我有几个文件,其中有以下模式:
t= 9.90000 2 2
t= 10.00000 1 1
现在,t 的值(例如 100.00000)以及 (2 2) 的值正在发生变化。我想像下面这样重写它:
t = 9.9 fs st=2
t = 10.0 fs st=1
现在,我有很多困难要做到这一点。在检查此链接后,我正在尝试以下内容:
for i in {99..100};do
printf t="%*.5f\n" 16 $(($i))e-1 > 1.out
x=$(grep -h -f 1.out output.xyz | cut -c 25-25)
printf t="%*.1f fs st=$x\n" 6 $i > 2.out
grep -h -f 1.out output.xyz > 3.out
while read pattern; do
while read pattern2; do
sed -i 's/"${pattern}"/"${pattern2}"/' output.xyz
done < 2.out
done < 3.out
done
这里的问题是我用要正确替换的模式创建了文件 3.out,但是当我将它读入时pattern
,它会带走几个空格。
而不是存在t= 9.90000 2 2
,它是t= 9.90000 2 2
。我想这里的主要问题是如何将正确数量的空格发送到变量pattern
和pattern2
.
我想我已经增加了过多的复杂性,但从理论上讲,应该有效......
我很感激任何帮助。
如果有人想尝试,我会将文件 output.xyz 的示例粘贴到下面。在这里,我在 90 和 100 之间循环,但最终我会在 0 到 200 之间执行 0.1 的循环。
t= 9.90000 2 2
H -0.036930458 0.778649616 1.520488735
C 0.027100908 0.020521063 0.815485702
H -0.114216621 -1.115678468 1.549274509
C -0.028047550 0.011852199 -0.815234987
H 0.117999971 -1.007943999 -1.373022932
H 0.044427848 0.883548719 -1.649093142
6
t= 10.00000 1 1
H -0.038617790 0.777486447 1.520614461
C 0.027651801 0.020640376 0.817860457
H -0.116497310 -1.116177809 1.544694024
C -0.028248486 0.012015286 -0.816858295
H 0.118760018 -1.012065106 -1.371494658
H 0.043469061 0.885969826 -1.655114073
谢谢
贡萨尔维斯岛
我无法复制你的结果。当我尝试它时 3.out 看起来是正确的,但该sed
命令不起作用,因为变量没有被单引号替换(您从中复制的答案完全错误,并且对此效果有评论)。只使用双引号,比如sed -i "s/${pattern}/${pattern2}/" output.xyz
,就可以了。
但正如你所说,它过于复杂。您正在使用一种在初级脚本编写者中很常见的反模式:查找需要一项一项更改的内容,然后将更改应用于整个文件(并希望它实际上只更改那一项)。这既低效(因为它每次都处理整个文件)并且有风险(因为更改可能会以意想不到的方式应用于文件中不相关的位置)。
你实际上做了两次这样的事情,首先扫描整个文件的位置,例如“...9.90000...”需要更改为“...9.9...”(所以你扫描整个文件每个数字),然后为您找到的行创建一个替换模式并将其应用于整个文件(再次处理整个文件以更改一行)。如果您以 0.1 为增量在 0 到 200 之间执行此操作,则意味着您将搜索文件 2,001 次,并最多对其进行多次编辑 - 即整个文件的 4,002 次!如果文件中实际上有那么多条目(并且每个条目后跟 6 行其他数据,如您的示例所示)... 4,002 次通过 2001*7 行,总共处理了 56,056,014 行。
如果文件中有两行具有相同的 t= 编号,这也会奇怪地失败,因为它会尝试用所有重新格式化的版本替换所有匹配的行(除第一个之外的所有行都是乱码),而无需付费注意哪个替换与哪个原始。如果一个数字没有匹配项,它也会表现得很奇怪,尽管在这种情况下它不是破坏性的。
(注意:可能永远不会有多个相同的 t= 数字,在这种情况下,这种方法可能会起作用。可能。但它仍然是一种糟糕的做事方式。)
做这样的事情的更好方法是处理文件一次,使用一种方法来处理它正在处理的行所需的所有逻辑,因为它正在处理它。你可以在sed
没有太多麻烦的情况下做到这一点。如果数字总是保证在第一个小数位后有零,这应该有效:
sed -Ei 's/^ t= +([ 0-9]{3}[.][0-9])0000 +([0-9]) +[0-9]$/ t= \1 fs st=\2/' output.xyz
在这里,-E
选项sed
告诉它使用“扩展”正则表达式语法,( )
模式中是“捕获组”,可以在替换字符串中用作\1
(第一个)\2
(第二个)。在正则表达式中,[ 0-9]{3}
意思是“三个全是空格和/或数字的字符”,以及后面跟着的各种空格的+
意思是“一个或多个空格”(如果您愿意,可以用正确数量的空格替换它们)通缉)。
所以基本上,它匹配整行(如果它采用需要更改的格式),捕获重要部分,并用重新格式化的版本替换该行,并在其位置捕获数据位。不符合该格式的行将不会被匹配,因此它们将保持不变。
(我也不确定输出格式是否完全正确,因为您拥有的内容不一致。输出中的“t”之前是否应该有空格?第一个“=”周围是否应该有空格? 您可能需要在我的命令中调整替换字符串。)
编辑:要转换st
值,最好再次添加逻辑以一次性完成。您可以sed
通过使用 4 种模式来做到这一点,一种匹配列中的“1”或“5”([15]
正则表达式表示法)st
并在替换中产生“0”,一种匹配“2”或“6”等。您可以-e
在每条规则之前使用,也可以通过将它们分开来将它们组合成一个长参数;
。这是-e
版本(为了可读性分成单独的行):
sed -Ei \
-e 's/^ t= +([ 0-9]{3}[.][0-9])0000 +([15]) +[0-9]$/ t= \1 fs st=0/' \
-e 's/^ t= +([ 0-9]{3}[.][0-9])0000 +([26]) +[0-9]$/ t= \1 fs st=1/' \
-e 's/^ t= +([ 0-9]{3}[.][0-9])0000 +([37]) +[0-9]$/ t= \1 fs st=2/' \
-e 's/^ t= +([ 0-9]{3}[.][0-9])0000 +([48]) +[0-9]$/ t= \1 fs st=3/' \
output.xyz
使用这种方法,您必须小心编辑规则不会链接(除非您希望它们链接)。也就是说,您不希望一条规则将st
值从“4”更改为“3”,然后另一条规则将“3”更改为“2”等。在这种情况下,一旦转换了一行它不再匹配模式,所以这不会发生(另外我将规则放在一个顺序中,无论如何都可以防止链接)。
另一种选择是切换到另一个程序,例如awk
. awk
通常比 更强大sed
,但在哲学上也有些不同:它更像是一种真正的编程语言,并且倾向于将文本行视为字段(在输入行中,$1
将是“t=”,$2
例如“9.90000”等),尽管您也可以使用整行(awk
调用$0
)。awk
也知道数学,所以转换st
值只是减一并减少模 4 的问题。一个缺点是大多数版本awk
不支持就地编辑(如sed -i
),因此您需要将输出发送到临时文件和然后用它来替换输入文件。
awk '{if ($0 ~ / t= +[ 0-9]{3}[.][0-9]0000 +[1-8] +[0-9]$/) printf " t=%6.1f fs st=%d\n", $2, ($3-1)%4; else print $0}' output.xyz >output.tmp &&
mv output.tmp output.xyz
顺便说一句,你确实有所有这些文件的良好备份,对吗?以防万一出现可怕的错误?
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句