从文件中 sed 模式并使用字符串变量在另一个文件中替换新模式

从文件中 sed 模式并使用字符串变量在另一个文件中替换新模式

我有几个文件,其中有以下模式:

  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。我猜这里的主要问题是如何将正确数量的空格发送给变量patternpattern2

我认为我已经增加了过多的复杂性,但理论上应该可行......

我感谢任何帮助。

如果有人想尝试,我在下面粘贴了文件 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”,等等。在这种情况下,一旦转换了一行,它就不再与模式匹配,因此这种情况不会发生(另外,我按照可以防止链接的顺序排列规则)。

另一个选择是切换到另一个程序,例如awkawk通常比 更强大sed,但在理念上也有所不同:它更像是一种真正的编程语言,并且倾向于将文本行视为字段(在输入行中,$1将是“t=”,$2例如“9.90000”等),尽管您也可以使用整行(调用awk$0awk也懂数学,所以转换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

顺便说一句,你确实备份了所有这些文件,对吧?以防万一出现严重问题?

相关内容