如何使用sed仅替换第一次出现在一个文件?
-
02-07-2019 - |
题
我想要更新的大量C++源文件包括一个额外的指令》之前的任何现有的#包括。这种任务时,我通常使用一个小小的庆典脚本sed重新写的文件。
我怎么得到的 sed
来取代仅仅是第一个出现的串在一个文件,而不是取代每次发生?
如果我用
sed s/#include/#include "newfile.h"\n#include/
它取代了所有#包括。
替代建议,以实现同样的事情,也是受欢迎的。
解决方案
# 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脚本,它只会替换第一次出现的<!>“Apple <!>”;通过<!>“香蕉<!>”
示例输入:输出:
Apple Banana
Orange Orange
Apple Apple
这是一个简单的脚本:编者注:仅适用于 GNU sed
。
sed '0,/Apple/{s/Apple/Banana/}' filename
sed '0,/pattern/s/pattern/replacement/' filename
这对我有用。
例如
sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt
编者注:两者都只使用 GNU sed
。
一个 概述 许多有用的 现有的答案, ,辅以 解释:
该例子在这里使用的简化使用情况:词取代'foo'与'bar'在第一匹配线只。
由于使用 ANSI C-quoted strings($'...'
) 提供的样本输入线, bash
, ksh
, 或 zsh
是假定为外壳。
GNU sed
只有:
本Hoffstein的anwswer 向我们显示,GNU提供了一个 扩展 来的 POSIX规范 sed
允许以下2-地址的形式: 0,/re/
(re
表示一种任意的经常表达这里)。
0,/re/
允许regex 匹配 在第一个也行.换句话说:这样一个地址将创建一个范围从1行,包括线路相匹配 re
-是否 re
发生在1线或在任何随后的路线。
- 与此形成对比的POSIX符合标准的形式
1,/re/
, ,它创建了一个范围相匹配,从第1行,包括线路相匹配re
上 随后 线;换句话说:此 会检测不到先生re
匹配如果发生这种情况发生的 1 线 并且还 防止使用的缩写//
对于重复使用的最近使用regex(见下一点)。[1]
如果你把一个 0,/re/
有一个地址 s/.../.../
(取代)电话使用 同 经常表达,你的命令将有效地仅仅执行替换的 第一 线路相匹配 re
.
sed
提供一个方便 快捷方式重新使用的最近施加的经常表达:一个 空 分隔的一对, //
.
$ 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-拥有-只有 sed
如BSD(mac os) sed
(还将与 GNU sed
):
由于 0,/re/
不能使用和形式 1,/re/
会检测不到 re
如果发生这种情况发生在第一线(见上文), 特别处理第1行是必需的.
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
注:
空regex
//
快捷方式是采用两次在这里:一旦为终端的范围,并且一旦在s
话;在这两种情况下,regexfoo
是隐含地重复使用,使我们不得不重复,这使得既可用于较短的和更易于维护的代码。POSIX
sed
需求的实际内容的行后的某些功能,例如后名称的标签,甚至省略,因为这种情况t
在这里;战略性地分成多个脚本-e
选择是可以替代使用的一个实际内容的行:每个结束-e
脚本块在一个新行通常需要去。
1 s/foo/bar/
替换 foo
在第1行,如果发现存在。如果是这样, t
分支机构,以结束脚本(跳过其余的命令在线)。(的 t
功能分支的标签只有如果最近的 s
呼吁执行一个实际的替代;在没有一个标签,因为是这里的情况,结束脚本是支)。
当发生这种情况,范围内的地址 1,//
, 通常认为第一次出现 从2号线, 将 不 匹配,和范围 不 被处理,因为该地址是评价当前线已经 2
.
相反,如果没有匹配在第1行, 1,//
将 可以进入,并将找到真正的第一场比赛。
净效果是相同的作用GNU sed
's 0,/re/
:只有第一个发生替换为,无论它发生在1线路或任何其他。
非范围的办法
波东的答案 演示了 循环 技术 那 绕过需要一个范围;因为他使用 GNU sed
语法,这里的 POSIX符合当量:
循环技术的1:在第一场比赛,执行替换,那么 进入一个循环,只需打印的剩余行为-是:
$ 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, 短小文件只: 阅读整个输入存储器,然后执行一个单一的替代它.
$ 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
相匹配的第1行,并regex /foo/
-结束的范围,然后只找起 下 线。因此, 既 线的选择在这种情况下,和 s/foo/bar/
取代执行它们两个。
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo'
失败:与 sed: first RE may not be empty
(BSD/mac os)和 sed: -e expression #1, char 0: no previous regular expression
(GNU),因为,在1号线正在处理的(由于线路的数量 1
开始的范围内),没有regex已经应用,所以 //
不是指什么。
除GNU sed
's特别 0,/re/
语法, 任何 范围内,开始 线数量 有效地排除使用 //
.
您可以使用awk做类似的事情......
awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c
说明:
/#include/ && !done
当行匹配<!>时,在{}之间运行操作语句; #include <!> quot;我们还没有处理过它。
{print "#include \"newfile.h\""; done=1;}
这打印#include <!> quot; newfile.h <!> quot;,我们需要转义引号。然后我们将done变量设置为1,因此我们不添加更多包含。
1;
这意味着<!>“打印出<!>行; - 空操作默认打印$ 0,打印出整行。一个班轮,比sed IMO更容易理解: - )
linuxtopia sed FAQ 的全面答案。它还强调了人们提供的一些答案不适用于非GNU版本的sed,例如
sed '0,/RE/s//to_that/' file
非GNU版本中的必须是
sed -e '1s/RE/to_that/;t' -e '1,/RE/s//to_that/'
但是,此版本不适用于gnu sed。
这是一个适用于两者的版本:
-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'
例如:
sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename
只需在最后添加出现次数:
sed s/#include/#include "newfile.h"\n#include/1
#!/bin/sed -f
1,/^#include/ {
/^#include/i\
#include "newfile.h"
}
此脚本的工作原理:对于1和第一个#include
之间的行(第1行之后),如果行以sed
开头,则在前面添加指定的行。
但是,如果第一个0,/^#include/
在第1行,那么第1行和下一个后续1,
都将前面加上该行。如果您使用的是GNU <=>,则它有一个扩展名,其中<=>(而不是<=>)将做正确的事。
一个可能的解决方案:
/#include/!{p;d;}
i\
#include "newfile.h"
:
n
b
说明:
- 读线,直到我们找到#包括打印这些行,然后开始新的周期
- 插入新的包括线
- 进入一个循环,只是读取行(通过默认sed也将打印这些行),我们不会回到第一部分脚本来自这里
我知道这是一篇旧帖子,但我有一个以前常用的解决方案:
grep -E -m 1 -n 'old' file | sed 's/:.*$//' - | sed 's/$/s\/old\/new\//' - | sed -f - file
基本上使用grep找到第一次出现并停在那里。还打印行号,即5行。管道进入sed并删除:以及之后的所有内容,只需要留下行号。管道进入sed,它将s /.*/替换为末尾,它给出一个1行脚本,该脚本通过管道传输到最后一个sed作为文件脚本运行。
所以如果regex = #include和replace = blah并且grep第一次出现在第5行,那么通过管道传输到最后一个sed的数据将是5s /.*/ blah /.
如果有人来这里替换所有行中第一次出现的字符(比如我自己),请使用:
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,您可以仅替换所有第二个。
我会用awk脚本执行此操作:
BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}
END {}
然后用awk运行它:
awk -f awkscript headerfile.h > headerfilenew.h
可能很草率,我是新手。
作为替代建议,您可能需要查看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重定向到工作文件。
我理解它的方式,1,/====RSSpermalink====/
通过设置范围限制告诉sed何时停止,然后s/====RSSpermalink====/${nowms}/
是用第二个替换第一个字符串的熟悉的sed命令。
在我的情况下,我将命令放在双引号中,因为我在带变量的Bash脚本中使用它。
使用 FreeBSD ed
并避免include
的<!>;不匹配<!>如果要处理的文件中没有<=>语句,则会出错:
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...
与GNU sed的 -z
选择你可能处理整个文件作为如果这只是一线。这样一个 s/…/…/
只会替代第一场比赛在整个文件。请记住: s/…/…/
只取代了第一场比赛在各行,但用 -z
选项 sed
将整个文件作为一个单一的线。
sed -z 's/#include/#include "newfile.h"\n#include'
在一般情况下你要重写你的sed表达由于该模式的空间,现在拥有整个文件而不是一个线。一些例子:
s/text.*//
可以改写为s/text[^\n]*//
.[^\n]
匹配的一切 除了 换行符。[^\n]*
将符合所有的符号后text
直到一个新行为止。s/^text//
可以改写为s/(^|\n)text//
.s/text$//
可以改写为s/text(\n|$)//
.
以下命令删除文件中第一次出现的字符串。它也删除了空行。它出现在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产生:
0x2200003
POSIXly(在sed中也有效),只使用一个正则表达式,只需要一行内存(像往常一样):
sed '/\(#include\).*/!b;//{h;s//\1 "newfile.h"/;G};:1;n;b1'
说明:
sed '
/\(#include\).*/!b # Only one regex used. On lines not matching
# the text `#include` **yet**,
# branch to end, cause the default print. Re-start.
//{ # On first line matching previous regex.
h # hold the line.
s//\1 "newfile.h"/ # append ` "newfile.h"` to the `#include` matched.
G # append a newline.
} # end of replacement.
:1 # Once **one** replacement got done (the first match)
n # Loop continually reading a line each time
b1 # and printing it by default.
' # end of sed script.