使用 SED 在第一次匹配后搜索并替换整行?

使用 SED 在第一次匹配后搜索并替换整行?

我有一个使用以下格式的文件:

M104 S200
M107
M104 S250
M110
M104 S275

我正在尝试找到一种方法来匹配第二次出现的M104并将整行重写为还包含 的其他内容M104

我试过了

sed 's/^M104/s/.*/M104 S300/' file - but it replaces all instances of M104
sed '0,/M104/s//M104 S200/' file - leaves the trailing text on the line
  • 其他一些变化给我带来了意想不到的结果。有什么帮助吗? ;)

答案1

如果你有 Perl,我会这样做:

$ perl -pe 'if (/M104/) { $i++; if($i == 2) { $_ = "M104 S999\n" } } ' test.txt
M104 S200
M107
M104 S999
M110
M104 S275

即如果/M104/匹配,则递增$i,然后检查是否$i等于 2,如果是,则替换当前行(包括尾随换行符,\n

s/^M104.*/.../如果你想在那里使用类似 sed 的替换,你可以在最后一部分中使用。

Perl 具有类似 sed 的-i[extension]选项,可以在有或没有备份文件的情况下进行就地更改。

或者通过环境变量传递键值(使脚本编写更容易):

n=2 repl="S999" perl -pe 'if (/M104/) { $i++; if($i == $ENV{n}) { $_ = "M104 " . $ENV{repl} . "\n" } } ' test.txt

(或者如果你更喜欢链接而不是嵌套,比如perl -pe '/M104/ and $i++ and $i == 2 and $_ = "M104 S999\n" ' test.txt.)


或者在 awk 中:

$ awk '/M104/ { i++; if (i == 2) $0 = "M104 S999"; } 1' test.txt

但 awk 没有标准的就地选项。

答案2

您的(失败的)尝试意味着您的sed版本允许0开始一个范围(并非所有seds 都这样做)。如果是这样,请尝试

sed -rn '0,/^M104/{p;b;}; 0,/(^M104).*/ s//\1 S300/; p' file
M104 S200
M107
M104 S300
M110
M104 S275

这个想法是应用地址范围两次:第一个范围将查找并打印直到第一个匹配为止未更改的行。第二个范围将替代下一场比赛。同样,sed当第二个范围的起始地址已被第一个范围消耗时,并非所有版本都接受第二个范围的起始地址。

答案3

如果您正在使用GNU sed,您可以使用该-z选项在一个缓冲区中处理整个文件,并使用数字标志s命令的数字标志来替换n第 次出现:

sed -zE 's/(^|\n)M104[^\n]*/\1M104 S300/2'

告诉替换第二个匹配项2sed

请注意,我使用扩展正则表达式(选项-E),因此我可以匹配换行符或文件开头,并重新插入匹配 ( \1) 以保留换行符。

便携的version 不能使用该-z选项,并且不允许用于[^\n]“除换行符之外的所有内容”,因此我们使用保持缓冲区来收集行并期望行只包含可打印字符:

sed -E 'H;1h;$!d;x;s/(^|\n)M104[[:print:]]*/\1M104 S300/2'

另请注意,对于非常大的文件来说,缓冲区可能太大。

相关内容