在文件的每一行中查找一个字符串,然后使用 bash 替换同一行上的另一个字符串

在文件的每一行中查找一个字符串,然后使用 bash 替换同一行上的另一个字符串

这是我的脚本:

#! /bin/bash

# C-band Edit
dqt='"'
str5="polarization=${dqt}2${dqt}"
for x in {3600..4200} do;
    sed -i "/$str5.*$x/s/$x/$((x-600))/" satellites.xml
done

我想做的就是在 Satellites.xml 中包含 str5 的所有行上将 3600 到 4200 之间的数字 x 替换为 x-600,但上面的脚本给了我语法错误

答案1

我只是想到你的意思不是文字字符串x-600,你的意思是“从 3600 到 4200 之间找到的每个值中减去 600”。在这种情况下,请使用 perl,而不是 sed:

echo "$str5. foo bar 3200 3964 4155 4200 4255" |
  perl -p -e "if (/$str5/) {s/3[6789]\d\d|4[01]\d\d|4200/$&-600/eg}"
polarization="2". foo bar 3200 3364 3555 3600 4255

这使用 Perl 的/e正则表达式修饰符来使右侧 ( $&-600) 成为计算为 perl 表达式$&是一个包含匹配值的 Perl 变量,因此$&-600表示从该值中减去 600。

如果这是您真正想要的,我将在下面留下我的原始答案。还因为它有有用的解释,这些解释仍然与上面的 perl 答案有些相关。

与 sed 一样,perl 具有-i就地编辑选项,因此您可以将其直接应用到您的 Satellites.xml 文件。man perlrun详情请参阅。

perl -i -p -e "if (/$str5/) {s/3[6789]\d\d|4[01]\d\d|4200/$&-600/eg}" satellites.xml

另外值得注意的是:当您处理 XML 文件时,您可能应该使用 XML 解析器。幸运的是,perl 有几个可供选择,例如XML::解析器或集合libxml-perl


我没有您实际输入的样本,因此我制作了一个示例来演示哪些内容会发生变化,哪些不会发生变化:

str5='polarization="2"'

echo "$str5. foo bar 3500 3964 4155 4200 4255" | 
  sed -e "/$str5/ {s/3[6-9][0-9][0-9]/3-600/g; s/4[01][0-9][0-9]/4-600/g; s/4200/4-600/g}"
polarization="2". foo bar 3500 3-600 4-600 4-600 4255

粗略翻译成英文,就是“如果当前行包含$str5 ( ),则对其polarization="2"应用这三个操作”。s///

笔记:

  • 无需从 3600..4200 循环。这将使所有的改变运行sed,而不是 600 运行。

  • 您希望更改 3600-4200 之间的值。这意味着您需要 3 次搜索和替换操作:

    • 一件3600-3999
    • 4000-4199 一件
    • 一套正好4200
  • 或者,这可以通过两个操作来完成s///,将最后两个操作合并为一个:

      sed -e "/$str5/ { s/3[6-9][0-9][0-9]/3-600/g; s/4[01][0-9][0-9]\|4200/4-600/g }"
    
  • 可能还有很多其他方法来优化正则表达式,但是你做得越多,将来它们就越难阅读和修改。

  • 39644155、 和4200已更改。 35004255不是,它们超出了所需的范围。

  • [0-9]不会像你预期的那样工作一些区域设置(该范围内还有其他字符)。我不知道具体是哪些区域设置,但我经常看到它被提及,因此知道[0-9]不能完全依赖它。如果这影响到您,您可以使用[[:digit:]]代替[0-9],或使用perl -p代替sed(这样您就可以使用\d代替[0-9])。同样适用于 range [6-9][6789]请改为使用。

  • 最后,要非常小心放入$str5变量中的内容。因为它被插入到sed命令中,所以很容易破坏 sed 脚本(例如,如果$str5包含 a /,则会破坏/$str5/匹配)。 sed 不知道它的脚本部分来自 shell 变量,它看到的只是指定要运行的脚本。

    此外,整个字符串将是由 sed 解释为正则表达式- 这意味着正则表达式元字符不会被解释为文字字符,除非它们用\.例如.将被解释为“任何字符”而不是文字点,除非它被转义为\.

答案2

do您的尝试原则上是正确的,您只是错过了用分号关闭列表之前,正如 frabjous 指出的那样:

for x in {3600..4200}; do

但请注意,这将使您“就地”编辑文件 601 次,实际上每次执行时都会创建和删除数百个新文件。

为了避免这种情况,你可以

  1. sed一些对数字的基本理解,一次性完成,这很有趣,但并不是很有用
  2. 使用可以计算数字的工具,例如python,perlawk
  3. 将其减少到只有七个您可以输入的实际替代品sed
    #! /bin/bash
    # C-band Edit
    dqt='"'
    str5="polarization=${dqt}2${dqt}"
    sed -Ei "/$str5$x/{"$(for x in {36..41}; do echo -n "s/$x([0-9]{2})/$((x-6))\\1/;"; done)";s/4200/3600/;}" satellites.xml

替换将产生sed脚本

s/36([0-9]{2})/30\1/;s/37([0-9]{2})/31\1/;s/38([0-9]{2})/32\1/;s/39([0-9]{2})/33\1/;s/40([0-9]{2})/34\1/;s/41([0-9]{2})/35\1/;s/4200/3600/

相关内容