【问题标题】:Replace text in file if previous line matches another text如果前一行与另一个文本匹配,则替换文件中的文本
【发布时间】:2021-09-30 08:24:24
【问题描述】:

我的文件如下所示:

FooBarA
foo bar
foo = bar
FooBarB
foo bar
foo = bar
FooBarC
foo bar
foo = bar
...

我想做的是编写一个脚本来替换foo = bar 中的bar,但前提是它属于FooBarB。所以在上面的例子中,应该只替换所有foo = bar 行中的第二个bar

我玩过sed,但我就是做不好。我还想避免安装任何不一定预先安装在系统上的工具(我在 Mac OS 上),因为其他团队成员也会使用该脚本。

【问题讨论】:

  • 我怀疑你会更有可能通过添加awk标签并删除replacelookbehind来获得解决方案。你也可以考虑Perl

标签: macos shell awk sed


【解决方案1】:

这可能对你有用(GNU sed):

sed '/FooBarB/{:a;n;/^$/b;/foo = bar/!ba;s//foo = baz/}' file

匹配字符串FooBarB并开始循环。

取出下一行并研究它。

如果该行为空,则该节完成,因此跳出循环。

如果该行不包含字符串foo = bar,则取出下一行并继续循环。

否则,用新值替换bar 并结束循环。


替代方案(可能适用于 macOS 用户?):

sed -e '/FooBarB/{:a' -e 'n;/^$/b;/foo = bar/!ba;s//foo = baz/;}' file

由于 OP 将输入数据更改为问题的另一个解决方案:

sed '/FooBar/h;G;/FooBarB/s/foo = bar/foo = baz/;P;d' file

【讨论】:

  • 好主意,但 GNU sed 可能需要在 macos 上通过 homebrew 进行安装。跨度>
  • @MarkSetchell 我在上面没有使用任何 GNU 特定术语,但我知道 sed macos 使用确实会出现一些异常,即使是这样的通用解决方案。
  • 是的,macOS 上的sed 要求标签后跟换行符,所以这不起作用。 (另外,组右大括号前面有一个分号:;}
  • @potong macOS 坚持使用换行符,它也不适用于单独的 -e 命令。 bash 用户的一种可能解决方案是使用 $'' 字符串并在正确的位置放置换行符 (\n):sed $'/FooBarB/{:a\n n;/^$/b \n /foo = bar/!ba\n s//foo = baz/; }' temp.md
【解决方案2】:

在每个 Unix 机器上的任何 shell 中使用任何 awk:

$ awk -v tgt='FooBarB' -v val='whatever'  '
    NF==1{tag=$0} (NF>1) && (tag==tgt) && sub(/=.*/,"= "){$0=$0 val}
1' file
FooBarA
foo bar
foo = bar
FooBarB
foo bar
foo = whatever
FooBarC
foo bar
foo = bar

【讨论】:

    【解决方案3】:

    使用sed(使用 macOS 的 sed 和 GNU sed 测试)的一种方法是:

    replace.sed
    #!/usr/bin/env sed -Ef
    
    /FooBarB/,/^FooBar/ {
      s/(foo[[:space:]]*=[[:space:]]*).+/\1new-value/
    }
    

    它的作用如下:

    1. /FooBarB/,/^FooBar/ 匹配一系列行,其中第一行匹配正则表达式/FooBarB/,最后一行匹配正则表达式/^FooBar/(这是下一个“组”的开始)。两个正则表达式之间的逗号是sed中范围匹配的语法。

    2. s/(foo[[:space:]]*=[[:space:]]*).+/\1new-value/ — [s] 替换(在匹配的行范围内)匹配正则表达式 (foo[[:space:]]*=[[:space:]]*).+\1new-value,其中 \1 引用搜索正则表达式中的第一个捕获组。搜索正则表达式查找 foo 后跟可选空格,然后是 = 符号,然后是空格,然后是其他任何内容,在您的情况下是旧值。

    您可以在一行中完成所有操作,但我想展示一个更易于理解的版本(就sed 而言,无论如何):

    sed -E '/FooBarA/,/^FooBar/s/(foo[[:space:]]*=[[:space:]]*).+/\1new-value/' temp.md 
    

    【讨论】:

    • 这基本上是我需要的。有没有办法告诉sed 只替换foo = bar 的第一个匹配项?目前它替换了从需要替换的实际foo = bar 到文件中最后一个的所有内容
    • @ProtocolGuy 不确定我是否理解。您可以编辑问题中的示例输入吗?在FooBarB 组中是否有不止一行以foo = 开头?
    • 对不起,我搞砸了。两者之间实际上没有新线......
    • @ProtocolGuy 那么您需要做的就是调整范围末尾的正则表达式;而不是空行,它应该是以FooBar 开头的行。我会编辑答案。
    【解决方案4】:

    作为参考,GNU awk 变体:

    awk -v v="newvalue" 'BEGIN{FS=OFS="\n";RS=ORS="\n\n"}$1=="FooBarB"{$3="foo = " v}1' file
    

    通过使用选项-v,变量v 保存想要的字符串。

    BEGIN 语句将输入、输出字段分隔符、输入和输出记录分隔符分别设置为一个和两个回车。
    这样,一条记录就由包含模式Foobar[ABC] 的多行块组成。

    最后一条语句通过重写第三行来设置新值。

    【讨论】:

    • 唯一特定于 gawk 的是RS="\n\n"。如果您将RS=ORS="\n\n" 更改为RS=""; ORS="\n\n",那么它在任何awk 中的行为方式都相同。话虽如此,用户示例输入中的数据行之间没有空行(可能在您回答后发生了变化,idk),因此脚本将无法运行。
    猜你喜欢
    • 1970-01-01
    • 2013-06-17
    • 2018-03-29
    • 2019-03-06
    • 1970-01-01
    • 2013-06-23
    • 2018-09-13
    • 2015-08-18
    • 2013-06-23
    相关资源
    最近更新 更多