如何使用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行,所以假设bash
, ksh
或zsh
作为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 sed
的0,/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