我有一个使用以下格式的文件:
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
开始一个范围(并非所有sed
s 都这样做)。如果是这样,请尝试
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'
告诉替换第二个匹配项2
。sed
请注意,我使用扩展正则表达式(选项-E
),因此我可以匹配换行符或文件开头,并重新插入匹配 ( \1
) 以保留换行符。
这便携的version 不能使用该-z
选项,并且不允许用于[^\n]
“除换行符之外的所有内容”,因此我们使用保持缓冲区来收集行并期望行只包含可打印字符:
sed -E 'H;1h;$!d;x;s/(^|\n)M104[[:print:]]*/\1M104 S300/2'
另请注意,对于非常大的文件来说,缓冲区可能太大。