我有几个文件,其中有以下模式:
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.1 为间隔从 0 到 200 进行循环。
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
谢谢
贡萨尔维斯岛
答案1
我无法复制您的结果。当我尝试时,3.out 看起来是正确的,但命令sed
不起作用,因为变量没有在单引号中替换(您复制的答案完全是错误的,并且有一条注释)。只使用双引号,如sed -i "s/${pattern}/${pattern2}/" output.xyz
,就可以了。
但正如您所说,它过于复杂。您使用的是初学者中常见的反模式:逐个查找需要更改的内容,然后将更改应用于整个文件(并希望它实际上只更改这一项)。这既低效(因为它每次都要处理整个文件),又有风险(因为更改可能会以意想不到的方式应用于文件中不相关的位置)。
你实际上做了两次这样的事情,首先扫描整个文件以查找需要将“...9.90000...”更改为“...9.9...”的地方(因此你扫描整个文件以查找每个数字),然后为找到的行创建替换模式并应用那到整个文件(再次,处理整个文件以更改一行)。如果您以 .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 个模式来执行此操作,一个模式[15]
在列中匹配“1”或“5”(以正则表达式表示)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
顺便说一句,你确实备份了所有这些文件,对吧?以防万一出现严重问题?