如何使用sed来replace文件中的第一个事件?

我想在任何现有的#includes之前用额外的include指令更新大量的C ++源文件。 对于这种任务,我通常使用sed的一个小bash脚本来重写这个文件。

如何让sedreplace文件中第一次出现的string,而不是replace每一个出现的地方?

如果我使用

sed s/#include/#include "newfile.h"\n#include/ 

它将取代所有#includes。

实现同样目标的替代build议也是受欢迎的。

  # sed script to change "foo" to "bar" only on the first occurrence 1{x;s/^/first/;x;} 1,/foo/{x;/first/s///;x;s/foo/bar/;} #---end of script--- 

或者,如果您愿意: 编者注:仅适用于GNU sed

 sed '0,/RE/s//to_that/' file 

资源

写一个sed脚本,只会用“Banana”来代替第一个“Apple”

示例input:输出:

  Apple Banana Orange Orange Apple Apple 

这是一个简单的脚本: 编者按:只能用于GNU sed

 0,/Apple/{s/Apple/Banana/} 
 sed '0,/pattern/s/pattern/replacement/' filename 

这对我工作。

 sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt 

你可以使用awk来做类似的事情。

 awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c 

说明:

 /#include/ && !done 

当行匹配“#include”并且我们还没有处理它时,在{}之间运行action语句。

 {print "#include \"newfile.h\""; done=1;} 

这打印#include“newfile.h”,我们需要转义引号。 然后我们将donevariables设置为1,所以我们不添加更多的包含。

 1; 

这意味着“打印行” – 一个空的行动默认打印$ 0,打印出整条线。 比sed更容易理解IMO 🙂

概述了许多有用的现有答案 ,辅以解释

这里的例子使用一个简单的用例:只在第一个匹配行中用'bar'replace单词'foo'。
由于使用ANSI C引号的string( $'...' )来提供示例input行,所以假设bashkshzsh作为shell。


GNU sed only:

本·霍夫斯坦(Ben Hoffstein)的观点告诉我们,GNU提供了对sed的POSIX规范的扩展 ,允许以下2地址forms: 0,/re/re表示一个任意的正则expression式)。

0,/re/允许正则expression式在第一行也匹配 。 换句话说,这样一个地址将创build从第一行到包括匹配的行的范围 – 是否在第一行或后续行上发生。

  • 将其与符合POSIX的表单1,/re/ ,该表单创build一个范围,匹配从第一行到后续行中与re相匹配的行。 换句话说: 如果碰巧发生在第一那么将不会检测到第一次匹配的情况,并且还可以避免使用简写forms//重用最近使用的正则expression式(请参阅下一点)。 [1]

如果将0,/re/ address与使用相同正则expression式的s/.../.../ )调用相结合,则您的命令将只在与re匹配的第一行执行replace。
sed 为重用最近应用的正则expression式提供了一个方便的捷径 :一个空的分隔符对, //

 $ sed '0,/foo/ s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo' 1st bar # only 1st match of 'foo' replaced Unrelated 2nd foo 3rd foo 

一个POSIX-features-only sed比如BSD(OS X) sed (也可以用于GNU sed ):

由于0,/re/不能用于forms1,/re/如果碰巧发生在第一行(见上面),则不会检测到re ,因此需要对第一行进行特殊处理

MikhailVS的回答提到了这个技术,在这里把一个具体的例子:

 $ sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo' 1st bar # only 1st match of 'foo' replaced Unrelated 2nd foo 3rd foo 

注意:

  • 空的正则expression式//捷径在这里被使用两次:一次是范围的终点,一次是在s调用中; 在这两种情况下,正则expression式foo是隐式重用,使我们不必复制它,这使得两个更短,更可维护的代码。

  • POSIX sed在某些function之后需要实际的换行符,例如在标签的名称之后甚至是省略之后,就像这里的情况一样; 战略性地将脚本分成多个-e选项是使用实际换行符的替代方法:结束每个换行符通常需要的-e脚本块。

1 s/foo/bar/只会在第一行代替foo ,如果在那find的话。 如果是这样, t分支到脚本的结尾(跳过线上的其余命令)。 ( t函数只有当最近的s调用进行实际的replace时才转移到标签;如果没有标签,就像在这里一样,脚本的末尾被分支到)。

当发生这种情况时,通常从第2行开始的第一次出现的范围地址1,//匹配,并且范围将被处理,因为当前行已经是2时计算地址。

相反,如果第一行没有匹配1,// 则会input1,//将会find真正的第一个匹配项。

净效果与GNU sed0,/re/ :只有第一次出现被replace,无论是出现在第1行还是其他出现。


非范围的方法

potong的答案演示了循环技术绕过了一个范围的需要 ; 因为他使用GNU sed语法,所以这里是POSIX兼容的等价物

循环技术1:在首次匹配时,执行replace,然后input一个循环,直接打印剩余的行

 $ sed -e '/foo/ {s//bar/; ' -e ':a' -e '$!{n;ba' -e '};}' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo' 1st bar Unrelated 2nd foo 3rd foo 

循环技术2, 适用于小文件将整个input读入内存,然后对其进行单一replace

 $ sed -e ':a' -e '$!{N;ba' -e '}; s/foo/bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo' 1st bar Unrelated 2nd foo 3rd foo 

[1] 1.61803提供了1,/re/发生了什么,有和没有后续s//例子:
sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'产生$'1bar\n2bar' ; 行都被更新了,因为行号1与第一行匹配,而regex /foo/ – 范围的末尾 – 只能在下一行开始查找。 因此,在这种情况下select行,并且在它们两个上执行s/foo/bar/replace。
sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' :with sed: first RE may not be empty (BSD / macOS)和sed: -e expression #1, char 0: no previous regular expression (GNU),因为在第一行正在处理的时候(由于行号1开始范围),所以没有应用正则expression式,所以//不涉及任何内容。
除了GNU sed的特殊的0,/re/ syntax之外, 任何行号开头的范围都会有效地排除//使用。

只需在最后添加发生次数:

 sed s/#include/#include "newfile.h"\n#include/1 
 #!/bin/sed -f 1,/^#include/ { /^#include/i\ #include "newfile.h" } 

这个脚本如何工作:对于1和第一个#include之间的行(第一行之后),如果行以#include开始,那么在前面加上指定的行。

但是,如果第一个#include在第一行,那么第一行和下一个#include都会有行前置的。 如果你正在使用GNU sed ,它有一个扩展名, 0,/^#include/ (而不是1, )将会做正确的事情。

很多关于linuxtopia sed常见问题的答案。 它还强调,一些人提供的答案将不适用于非GNU版本的sed,例如

 sed '0,/RE/s//to_that/' file 

在非GNU版本将不得不

 sed -e '1s/RE/to_that/;t' -e '1,/RE/s//to_that/' 

可能的解决scheme:

  /#include/!{p;d;} i\ #include "newfile.h" : n b 

说明:

  • 读取行,直到find#include,打印这些行然后开始新的循环
  • 插入新的包含行
  • 进入一个只读取行的循环(默认sed也会打印这些行),我们不会从这里回到脚本的第一部分

我会用awk脚本来做到这一点:

 BEGIN {i=0} (i==0) && /#include/ {print "#include \"newfile.h\""; i=1} {print $0} END {} 

然后用awk运行它:

 awk -f awkscript headerfile.h > headerfilenew.h 

可能是马虎,我是新来的。

作为另一种build议,您可能需要查看ed命令。

 man 1 ed teststr=' #include <stdio.h> #include <stdlib.h> #include <inttypes.h> ' # for in-place file editing use "ed -s file" and replace ",p" with "w" # cf. http://wiki.bash-hackers.org/howto/edit-ed cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr") H /# *include/i #include "newfile.h" . ,p q EOF 

我终于得到了这个工作在一个Bash脚本中,用于在RSS提要中的每个项目中插入一个唯一的时间戳:

  sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" \ production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter 

它仅改变第一次出现。

${nowms}是Perl脚本设置的时间(以毫秒为单位), $counter是一个用于脚本内循环控制的计数器, \允许命令在下一行继续。

该文件被读入,stdout被redirect到一个工作文件。

我的理解方式是, 1,/====RSSpermalink====/通过设置范围限制告诉sed什么时候停止,然后s/====RSSpermalink====/${nowms}/ is熟悉的sed命令用第二个stringreplace第一个string。

在我的情况下,我把这个命令放在双引号,因为我在variables的Bash脚本中使用它。

如果在要处理的文件中没有include语句,则使用FreeBSD ed和避免ed的“不匹配”错误:

 teststr=' #include <stdio.h> #include <stdlib.h> #include <inttypes.h> ' # using FreeBSD ed # to avoid ed's "no match" error, see # *emphasized text*http://codesnippets.joyent.com/posts/show/11917 cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr") H ,g/# *include/u\ u\ i\ #include "newfile.h"\ . ,p q EOF 

这可能适用于你(GNU sed):

 sed -si '/#include/{s//& "newfile.h\n&/;:a;$!{n;ba}}' file1 file2 file.... 

或者如果内存不是问题:

 sed -si ':a;$!{N;ba};s/#include/& "newfile.h\n&/' file1 file2 file... 

我知道这是一个旧的职位,但我有一个解决scheme,我曾经使用:

 grep -E -m 1 -n 'old' file | sed 's/:.*$//' - | sed 's/$/s\/old\/new\//' - | sed -f - file 

基本上使用grep来find第一个发生并停在那里。 还打印行号,即5:行。 把它塞进sed中,然后删除:之后的任何东西,只剩下行号。 把它塞进sed中,把s /.*/replace成一个1行脚本,这个脚本被传送到最后一个sed,作为脚本文件运行。

所以如果regex = #include和replace = blah,并且第一个发生grep发现在第5行,那么传送到最后一个sed的数据将是5s /.*/ blah /。

如果有人来这里replace所有行中首次出现的字符(比如我自己),请使用以下命令:

 sed '/old/s/old/new/1' file -bash-4.2$ cat file 123a456a789a 12a34a56 a12 -bash-4.2$ sed '/a/s/a/b/1' file 123b456a789a 12b34a56 b12 

例如,通过更改1到2,您可以replace所有的第二个a。

以下命令删除文件中第一个出现的string。 它也删除了空行。 它被呈现在一个XML文件,但它将适用于任何文件。

如果您使用xml文件并且想要移除标签,那么这很有用。 在这个例子中,它删除了第一个“isTag”标签。

命令:

 sed -e 0,/'<isTag>false<\/isTag>'/{s/'<isTag>false<\/isTag>'//} -e 's/ *$//' -e '/^$/d' source.txt > output.txt 

源文件(source.txt)

 <xml> <testdata> <canUseUpdate>true</canUseUpdate> <isTag>false</isTag> <moduleLocations> <module>esa_jee6</module> <isTag>false</isTag> </moduleLocations> <node> <isTag>false</isTag> </node> </testdata> </xml> 

结果文件(output.txt)

 <xml> <testdata> <canUseUpdate>true</canUseUpdate> <moduleLocations> <module>esa_jee6</module> <isTag>false</isTag> </moduleLocations> <node> <isTag>false</isTag> </node> </testdata> </xml> 

ps:它在Solaris SunOS 5.10(相当老)上不适用于我,但它适用于Linux 2.6,sed版本4.1.5

没有新的,但也许更具体的答案: sed -rn '0,/foo(bar).*/ s%%\1%p'

例如: xwininfo -name unity-launcher产生如下输出:

 xwininfo: Window id: 0x2200003 "unity-launcher" Absolute upper-left X: -2980 Absolute upper-left Y: -198 Relative upper-left X: 0 Relative upper-left Y: 0 Width: 2880 Height: 98 Depth: 24 Visual: 0x21 Visual Class: TrueColor Border width: 0 Class: InputOutput Colormap: 0x20 (installed) Bit Gravity State: ForgetGravity Window Gravity State: NorthWestGravity Backing Store State: NotUseful Save Under State: no Map State: IsViewable Override Redirect State: no Corners: +-2980+-198 -2980+-198 -2980-1900 +-2980-1900 -geometry 2880x98+-2980+-198 

使用xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p'提取窗口ID xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p'产生:

 0x2200003 

sed有一个非常简单的语法,'-i'是交互式的(不需要newfile)。 只replace第一个实例:

 sed -i 's/foo/bar/' file 

全球取代您将使用

 sed -i 's/foo/bar/g' file 

在你的例子中,我会使用(^和$分别是开始和结束行)

 sed -i 's/^#include/#include\n#include/' file