如何使用sed只替换文件中的第一个匹配项?

时间:2008-09-29 12:22:34

标签: command-line sed text-processing

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

如何让sed只替换文件中第一次出现的字符串而不是替换每次出现的字符串?

如果我使用

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

它取代所有#includes。

也欢迎提供相同建议的替代建议。

23 个答案:

答案 0 :(得分:251)

编写一个sed脚本,只会用“Banana”替换第一次出现的“Apple”

示例输入:输出:

     Apple       Banana
     Orange      Orange
     Apple       Apple

这是一个简单的脚本:编者注:仅适用于 GNU sed

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

答案 1 :(得分:114)

 # 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 

Source

答案 2 :(得分:53)

sed '0,/pattern/s/pattern/replacement/' filename

这对我有用。

例如

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

编者注:两者都只使用 GNU sed

答案 3 :(得分:35)

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

此处的示例使用简化的用例:替换单词&#39; foo&#39;与&#39; bar&#39;仅在第一个匹配行中 由于使用ANSI C-quoted strings ($'...')来提供示例输入行,因此bashkshzsh被视为shell。

GNU sed

Ben Hoffstein's anwswer向我们展示了GNU向POSIX specification for sed提供扩展,允许使用以下2地址格式:0,/re/re在这里表示任意正则表达式。

0,/re/ 允许正则表达式在第一行匹配 。换句话说:这样的地址将创建从第1行到匹配re的行的范围 - 无论{1}}是出现在第1行还是后续行。

  • 将此与符合POSIX标准的表单 re 进行对比,该表单会创建一个范围,该范围从第1行开始,包括与1,/re/匹配的行>后续行;换句话说:如果第1次行发生将无法检测到re匹配的第一次出现也会阻止使用简写re 用于重用最近使用的正则表达式(参见下一点)。 [1]

如果您将//地址与使用相同正则表达式的0,/re/(替换)调用合并,则您的命令实际上只会对匹配s/.../.../的第一个行 re为重新使用最近应用的正则表达式提供了一个方便的快捷方式: 分隔符对sed

//

仅限POSIX功能$ 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 ,例如BSD(macOS)sed (也适用于 GNU sed ):

由于sed无法使用,0,/re/形式如果恰好发生在第一行(见上文),1,/re/将无法检测到re特殊处理第一行是必需的

MikhailVS's answer提到了这项技术,在这里提出了一个具体的例子:

$ 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

注意:

  • 此处使用空正则表达式//快捷方式两次:一次用于范围的端点,一次用于s调用;在这两种情况下,正则表达式foo被隐式重用,允许我们不必复制它,这使得代码更短,更易于维护。

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

1 s/foo/bar/仅在第1行替换foo,如果在那里找到的话。 如果是这样,t将分支到脚本的末尾(跳过该行上的剩余命令)。 (仅当最近的t调用执行了实际替换时,s函数才会分支到标签;如果没有标签,就像这里的情况一样,脚本的末尾被分支到)

当发生这种情况时,范围地址1,//(通常会从第2行开始发现> 匹配,范围将进行处理,因为当前行已经2时会评估地址。

相反,如果第一行没有匹配项,则会输入1,// ,并找到真正的第一场比赛。

净效果与GNU sed&#39; s 0,/re/相同:只有第一次出现被替换,无论是在第一行还是其他任何地方。

非范围方法

potong's answer演示 循环技术 绕过对范围的需求;因为他使用 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与第一行匹配,而正则表达式/foo/ - 范围的结束 - 仅在< em> next
行。因此,在这种情况下,会选择两个行,并对它们执行s/foo/bar/替换。
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed: first RE may not be empty(BSD / macOS)和sed: -e expression #1, char 0: no previous regular expression(GNU),因为在处理第1行时(由于行号1开始范围),尚未应用正则表达式,因此//没有引用任何内容。
除了GNU sed的特殊0,/re/语法之外,以行号开头的任何范围有效地排除了使用{ {1}}。 功能

答案 4 :(得分:23)

您可以使用awk做类似的事情......

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

说明:

/#include/ && !done

当行与“#include”匹配且我们尚未处理它时,在{}之间运行操作声明。

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

这打印#include“newfile.h”,我们需要转义引号。然后我们将done变量设置为1,因此我们不添加更多包含。

1;

这意味着“打印出行” - 空行动默认打印$ 0,打印出整行。一个班轮,比sed IMO更容易理解: - )

答案 5 :(得分:16)

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

答案 6 :(得分:12)

只需在最后添加出现次数:

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

答案 7 :(得分:12)

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

此脚本的工作原理:对于介于1和第一个#include之间的行(第1行之后),如果该行以#include开头,则在前面添加指定的行。

但是,如果第一个#include位于第1行,那么第1行和下一个后续#include都会预先添加该行。如果您使用的是GNU sed,则会有0,/^#include/(而不是1,)做出正确的扩展。

答案 8 :(得分:8)

可能的解决方案:

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

说明:

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

答案 9 :(得分:3)

如果有人来这里替换所有行中第一次出现的字符(比如我自己),请使用:

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,您可以仅替换所有第二个。

答案 10 :(得分:3)

我知道这是一篇旧帖子,但我有一个以前常用的解决方案:

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 /.

答案 11 :(得分:2)

作为替代建议,您可能需要查看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

答案 12 :(得分:2)

这可能适合你(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...

答案 13 :(得分:2)

使用 FreeBSD ed,如果要处理的文件中没有ed语句,请避免include的“不匹配”错误:< / p>

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

答案 14 :(得分:2)

我终于在一个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脚本中使用它。

答案 15 :(得分: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

可能很草率,我是新手。

答案 16 :(得分:0)

以下命令删除文件中第一次出现的字符串。它也删除了空行。它出现在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

答案 17 :(得分:0)

没有什么新的,但也许更具体的答案: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

答案 18 :(得分:0)

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.

答案 19 :(得分:0)

使用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|$)//

答案 20 :(得分:0)

用例可能是您的事件分布在整个文件中,但是您知道您唯一需要关注的是前10、20或100行。

然后简单地修饰这些行就可以修复问题-即使OP的措辞仅是最先考虑的。

sed '1,10s/#include/#include "newfile.h"\n#include/'

答案 21 :(得分:0)

一种可能的解决方案是告诉编译器包括头文件,而不必在源文件中提及它。在GCC中,有以下选项:

   -include file
       Process file as if "#include "file"" appeared as the first line of
       the primary source file.  However, the first directory searched for
       file is the preprocessor's working directory instead of the
       directory containing the main source file.  If not found there, it
       is searched for in the remainder of the "#include "..."" search
       chain as normal.

       If multiple -include options are given, the files are included in
       the order they appear on the command line.

   -imacros file
       Exactly like -include, except that any output produced by scanning
       file is thrown away.  Macros it defines remain defined.  This
       allows you to acquire all the macros from a header without also
       processing its declarations.

       All files specified by -imacros are processed before all files
       specified by -include.

Microsoft的编译器具有/FI(强制包含)选项。

此功能对于某些常见的标头(如平台配置)可能非常方便。 Linux内核的Makefile为此使用-include

答案 22 :(得分:-1)

sed -e 's/pattern/REPLACEMENT/1' <INPUTFILE