我的文件是这样的:
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上),因为脚本也会被其他团队成员使用。
使用sed
(使用macOS的sed和GNU sed进行测试)的一种方法是:
#!/usr/bin/env sed -Ef
/FooBarB/,/^FooBar/ {
s/(foo[[:space:]]*=[[:space:]]*).+/1new-value/
}
它的作用如下:
/FooBarB/,/^FooBar/
匹配一系列行,其中第一行匹配正则表达式/FooBarB/
,最后一行匹配正则表达式/^FooBar/
(这是下一个"组"的开始)。两个正则表达式之间的逗号是sed
中用于范围匹配的语法。s/(foo[[:space:]]*=[[:space:]]*).+/1new-value/
- [s]替换(在匹配的行范围内)任何与1new-value
匹配的正则表达式(foo[[:space:]]*=[[:space:]]*).+
,其中1
引用搜索正则表达式中的第一个捕获组。搜索正则表达式查找foo
,后面跟着可选的空格,后面跟着=
符号,然后再跟着空格,然后是其他任何东西,在您的情况下是旧值。
您可以在一行中完成所有操作,但我想展示一个更易于理解的版本(就sed
而言,在任何情况下):
sed -E '/FooBarA/,/^FooBar/s/(foo[[:space:]]*=[[:space:]]*).+/1new-value/' temp.md
编辑id>
这可能适合您(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
在任何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
作为参考,GNU awk变体:
awk -v v="newvalue" 'BEGIN{FS=OFS="n";RS=ORS="nn"}$1=="FooBarB"{$3="foo = " v}1' file
通过使用选项-v
,变量v
保存所需的字符串。
BEGIN
语句分别设置输入、输出字段分隔符,输入、输出记录分隔符为一回车和两回车。
这样,一条记录就由包含模式Foobar[ABC]
的几行块组成。
最后一条语句通过重写第三行来设置新值。