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

时间:2022-01-21 22:00:39

I want to update a large number of C++ source files with an extra include directive before any existing #includes. For this sort of task I normally use a small bash script with sed to re-write the file.

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

How do I get sed to replace just the first occurrence of a string in a file rather than replacing the every occurrence?

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

If I use

如果我使用

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

it replaces all #includes.

它取代所有#包括。

Alternative suggestions to achieve the same thing are also welcome.

也欢迎其他的建议来达到同样的目的。

19 个解决方案

#1


95  

 # 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---

or, if you prefer: Editor's note: works with GNU sed only.

或者,如果您喜欢:编辑器的注释:只使用GNU sed。

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

Source

#2


204  

Write a sed script that will only replace the first occurrence of "Apple" by "Banana"

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

Example Input: Output:

输入:输出示例:

     Apple       Banana
     Orange      Orange
     Apple       Apple

This is the simple script: Editor's note: works with GNU sed only.

这是一个简单的脚本:编辑器的注释:只处理GNU sed。

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

#3


48  

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

this worked for me.

这为我工作。

example

例子

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

Editor's note: both work with GNU sed only.

编者按:这两种方法都只使用GNU sed。

#4


24  

An overview of the many helpful existing answers, complemented with explanations:

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

The examples here use a simplified use case: replace the word 'foo' with 'bar' in the first matching line only.
Due to use of ANSI C-quoted strings ($'...') to provide the sample input lines, bash, ksh, or zsh is assumed as the shell.

这里的示例使用一个简化的用例:只在第一个匹配行中替换“foo”和“bar”。由于使用ANSI c引用的字符串($“…”)来提供样例输入行,bash、ksh或zsh被假定为shell。


GNU sed only:

GNU sed只:

Ben Hoffstein's anwswer shows us that GNU provides an extension to the POSIX specification for sed that allows the the following 2-address form: 0,/re/ (re represents an arbitrary regular expression here).

Ben Hoffstein的anwswer向我们展示了GNU为sed提供了一个对POSIX规范的扩展,它允许以下2个地址形式:0,/re/ (re表示这里的任意正则表达式)。

0,/re/ allows the regex to match on the very first line also. In other words: such an address will create a range from the 1st line up to and including the line that matches re - whether re occurs on the 1st line or on any subsequent line.

0,/re/允许regex在第一行上匹配。换句话说:这样的地址将创建从第一行到第一行的范围,包括在第一行或任何后续行中发生的重新出现的行。

  • Contrast this with the POSIX-compliant form 1,/re/, which creates a range that matches from the 1st line up to and including the line that matches re on subsequent lines; in other words: this will not detect the first occurrence of an re match if it happens to occur on the 1st line and also prevents the use of shorthand // for reuse of the most recently used regex (see next point).[1]
  • 与posix兼容的表单1 /re/进行对比,该表单创建了从第一行到第一行的范围,并包括在后续行中匹配的行;换句话说:如果发生在第一行的情况下,这将无法检测到第一次重新匹配的情况,而且还防止了对最近使用的regex的重用(见下一个点)的使用。

If you combine a 0,/re/ address with an s/.../.../ (substitution) call that uses the same regular expression, your command will effectively only perform the substitution on the first line that matches re.
sed provides a convenient shortcut for reusing the most recently applied regular expression: an empty delimiter pair, //.

如果你把一个0,/re/地址和s/……/(替换)调用使用相同的正则表达式,您的命令将有效地仅在匹配的第一行上执行替换。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

A POSIX-features-only sed such as BSD (OS X) sed (will also work with GNU sed):

一个posix特性——只使用了BSD (OS X) sed(也适用于GNU sed):

Since 0,/re/ cannot be used and the form 1,/re/ will not detect re if it happens to occur on the very first line (see above), special handling for the 1st line is required.

由于0,/re/不能使用,表格1,/re/将无法检测到,如果发生在第一行(见上文),则需要对第一行进行特殊处理。

MikhailVS's answer mentions the technique, put into a concrete example here:

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

Note:

注意:

  • The empty regex // shortcut is employed twice here: once for the endpoint of the range, and once in the s call; in both cases, regex foo is implicitly reused, allowing us not to have to duplicate it, which makes both for shorter and more maintainable code.

    空的regex //快捷方式在这里使用了两次:一次用于范围的端点,一次在s调用中;在这两种情况下,regex foo都被隐式重用,允许我们不需要复制它,这使得代码更短更容易维护。

  • POSIX sed needs actual newlines after certain functions, such as after the name of a label or even its omission, as is the case with t here; strategically splitting the script into multiple -e options is an alternative to using an actual newlines: end each -e script chunk where a newline would normally need to go.

    POSIX sed在某些功能之后需要实际的换行,比如在标签的名称或者它的省略之后,就像t在这里一样;将脚本战略性地分割成多个-e选项是使用实际换行的另一种选择:在通常需要换行的地方结束每个-e脚本块。

1 s/foo/bar/ replaces foo on the 1st line only, if found there. If so, t branches to the end of the script (skips remaining commands on the line). (The t function branches to a label only if the most recent s call performed an actual substitution; in the absence of a label, as is the case here, the end of the script is branched to).

1s /foo/bar/替代foo在第一行,如果发现。如果是这样,则t分支到脚本的末尾(跳过该行剩余的命令)。(只有在最近的s调用执行了实际的替换时,t函数分支才会被标记为一个标签;在没有标签的情况下,就像这里的情况一样,脚本的结尾是分支到的。

When that happens, range address 1,//, which normally finds the first occurrence starting from line 2, will not match, and the range will not be processed, because the address is evaluated when the current line is already 2.

当发生这种情况时,通常会发现从第2行开始出现第一个事件的range address 1 //,将不匹配,并且该范围将不被处理,因为在当前行已经为2的情况下计算地址。

Conversely, if there's no match on the 1st line, 1,// will be entered, and will find the true first match.

相反,如果第一行没有匹配,就输入1,//将进入,并将找到真正的第一匹配。

The net effect is the same as with GNU sed's 0,/re/: only the first occurrence is replaced, whether it occurs on the 1st line or any other.

净效果与GNU sed的0,/re/:只有第一个出现被替换,无论是在第一行还是任何其他。


NON-range approaches

NON-range方法

potong's answer demonstrates loop techniques that bypass the need for a range; since he uses GNU sed syntax, here are the POSIX-compliant equivalents:

potong的回答演示了绕过需求范围的循环技术;由于他使用的是GNU sed语法,这里是与posix兼容的等效项:

Loop technique 1: On first match, perform the substitution, then enter a loop that simply prints the remaining lines as-is:

循环技术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

Loop technique 2, for smallish files only: read the entire input into memory, then perform a single substitution on it.

循环技术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 provides examples of what happens with 1,/re/, with and without a subsequent s//:
- sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' yields $'1bar\n2bar'; i.e., both lines were updated, because line number 1 matches the 1st line, and regex /foo/ - the end of the range - is then only looked for starting on the next line. Therefore, both lines are selected in this case, and the s/foo/bar/ substitution is performed on both of them.
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' fails: with sed: first RE may not be empty (BSD/macOS) and sed: -e expression #1, char 0: no previous regular expression (GNU), because, at the time the 1st line is being processed (due to line number 1 starting the range), no regex has been applied yet, so // doesn't refer to anything.
With the exception of GNU sed's special 0,/re/ syntax, any range that starts with a line number effectively precludes use of //.

[1]1.61803提供了1、/re/、with和没有后续s//的例子:- sed '1,/foo/ s/foo/bar/' <<$' ' $' $' ' $'1bar\n2bar';即。这两行都被更新了,因为行号1匹配第一行,而regex /foo/ - - -范围的结束-则只需要从下一行开始。因此,在本例中选择了这两行,并对它们执行了s/foo/bar/替换。- sed的1 / foo / s / /酒吧/ ' < < < $ 1 foo \ n2foo \ n3foo的失败:sed:第一再保险不得空(BSD / macOS)和sed:# 1 - e表达式,字符0:没有前一个正则表达式(GNU),因为,当时正在处理的1号线(1号线启动范围),没有应用,正则表达式/ /不引用任何东西。除了GNU sed的特殊0,/re/语法之外,任何以行号开头的范围都有效地阻止了//。

#5


21  

You could use awk to do something similar..

你可以用awk做一些类似的事情。

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

Explanation:

解释:

/#include/ && !done

Runs the action statement between {} when the line matches "#include" and we haven't already processed it.

当行匹配“#include”时,在{}之间运行该操作语句,而我们还没有处理它。

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

This prints #include "newfile.h", we need to escape the quotes. Then we set the done variable to 1, so we don't add more includes.

这个打印# include”newfile中。h,我们需要避开引号。然后我们将done变量设置为1,因此我们不添加更多的include。

1;

This means "print out the line" - an empty action defaults to print $0, which prints out the whole line. A one liner and easier to understand than sed IMO :-)

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

#6


13  

Quite a comprehensive collection of answers on linuxtopia sed FAQ. It also highlights that some answers people provided won't work with non-GNU version of sed, eg

关于linuxtopia sed FAQ的完整答案集。它还强调了一些人提供的答案与非gnu版本的sed(如:)不兼容。

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

in non-GNU version will have to be

在非gnu版本中必须是这样。

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

However, this version won't work with gnu sed.

但是,这个版本不会使用gnu sed。

Here's a version that works with both:

这里有一个版本可以同时使用:

-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'

ex:

例:

sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename

#7


12  

Just add the number of occurrence at the end:

只需要增加结尾的次数:

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

#8


12  

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

How this script works: For lines between 1 and the first #include (after line 1), if the line starts with #include, then prepend the specified line.

这个脚本是如何工作的:在1和第一个#之间的行(在第1行之后),如果行从#include开始,然后prepend指定的行。

However, if the first #include is in line 1, then both line 1 and the next subsequent #include will have the line prepended. If you are using GNU sed, it has an extension where 0,/^#include/ (instead of 1,) will do the right thing.

但是,如果第一个#include在第1行,那么第1行和下一个后续的#include将会使行提前。如果您正在使用GNU sed,延长0,/ ^ # include /(而不是1)将做正确的事。

#9


7  

A possible solution:

一个可能的解决方案:

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

Explanation:

解释:

  • read lines until we find the #include, print these lines then start new cycle
  • 读行直到找到#include,打印这些行然后开始新的循环。
  • insert the new include line
  • 插入新的include行。
  • enter a loop that just reads lines (by default sed will also print these lines), we won't get back to the first part of the script from here
  • 输入一个只读取行(默认sed也会打印这些行)的循环,我们不会从这里返回到脚本的第一部分。

#10


2  

i would do this with an awk script:

我会用awk脚本做这个:

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

then run it with awk:

然后用awk运行它:

awk -f awkscript headerfile.h > headerfilenew.h

might be sloppy, I'm new to this.

可能是草率的,我是新手。

#11


2  

As an alternative suggestion you may want to look at the ed command.

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

I finally got this to work in a Bash script used to insert a unique timestamp in each item in an RSS feed:

最后,我在一个Bash脚本中完成了这个工作,该脚本用于在RSS提要中为每个条目插入一个惟一的时间戳:

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

It changes the first occurrence only.

它只会改变第一次出现的情况。

${nowms} is the time in milliseconds set by a Perl script, $counter is a counter used for loop control within the script, \ allows the command to be continued on the next line.

${nowms}是一个Perl脚本设置的毫秒数,$counter是一个用于在脚本中循环控制的计数器,\允许在下一行继续执行命令。

The file is read in and stdout is redirected to a work file.

该文件被读入,并且stdout被重定向到一个工作文件。

The way I understand it, 1,/====RSSpermalink====/ tells sed when to stop by setting a range limitation, and then s/====RSSpermalink====/${nowms}/ is the familiar sed command to replace the first string with the second.

我理解它的方式,1,/=== RSSpermalink==== =,通过设置范围限制来告诉sed何时停止,然后s/=== RSSpermalink====/${nowms}/是熟悉的sed命令,以替换第一个字符串。

In my case I put the command in double quotation marks becauase I am using it in a Bash script with variables.

在我的例子中,我将命令放在双引号中,因为我在一个带有变量的Bash脚本中使用它。

#13


2  

Using FreeBSD ed and avoid ed's "no match" error in case there is no include statement in a file to be processed:

使用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

#14


2  

This might work for you (GNU sed):

这可能对你有用(GNU sed):

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

or if memory is not a problem:

或者,如果记忆不是问题:

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

#15


2  

I know this is an old post but I had a solution that I used to use:

我知道这是一个旧的帖子,但我有一个我曾经使用过的解决方案:

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

Basically use grep to find the first occurence and stop there. Also print line number ie 5:line. Pipe that into sed and remove the : and anything after so you are just left with a line number. Pipe that into sed which adds s/.*/replace to the end which gives the a 1 line script which is piped into the last sed to run as a script on file.

主要使用grep查找第一次出现并停止。也打印行号ie 5:行。将其放入sed并删除:之后,您就只剩下行号了。进入sed的管道,添加了s/。*/replace to the end,它提供了一个1行脚本,该脚本被输入到最后一个sed中,以作为文件的脚本运行。

so if regex = #include and replace = blah and the first occurrance grep finds is on line 5 then the data piped to the last sed would be 5s/.*/blah/.

因此,如果regex = #include和replace = blah,第一次发生的grep发现在第5行,那么最后sed的数据将是5s/.*/blah/。

#16


1  

If anyone came here to replace a character for the first occurrence in all lines (like myself), use this:

如果有人来这里是为了替换所有行中第一次出现的字符(像我自己),请使用以下方法:

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

By changing 1 to 2 for example, you can replace all the second a's only instead.

例如,通过将1改为2,您可以替换所有的第二个a。

#17


0  

The following command removes the first occurrence of a string, within a file. It removes the empty line too. It is presented on an xml file, but it would work with any file.

下面的命令删除了文件中第一个字符串的出现。它也去掉了空行。它是在一个xml文件上显示的,但是它可以与任何文件一起工作。

Useful if you work with xml files and you want to remove a tag. In this example it removes the first occurrence of the "isTag" tag.

如果您使用xml文件,并且想要删除一个标记,那么这很有用。在本例中,它删除了第一次出现的“isTag”标记。

Command:

命令:

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

Source file (source.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>

Result file (output.txt)

结果文件(output.txt)

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

ps: it didn't work for me on Solaris SunOS 5.10 (quite old), but it works on Linux 2.6, sed version 4.1.5

ps:在Solaris SunOS 5.10(相当老的版本)上,它并没有为我工作,但是它在Linux 2.6版本上运行,sed版本4.1.5。

#18


0  

Nothing new but perhaps a little more concrete answer: sed -rn '0,/foo(bar).*/ s%%\1%p'

没有什么新的,但可能有一个更具体的答案:sed -rn '0,/foo(bar)。* / % % \ 1% p '

Example: xwininfo -name unity-launcher produces output like:

示例:xwininfo -name unitylauncher产生如下输出:

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

Extracting window ID with xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p' produces:

提取窗口ID与xwininfo - name unity面板| sed rn”0 / ^ xwininfo:窗口ID:(0 x[0-9a-fA-F]+)。* p / s % % \ 1%的产生:

0x2200003

#19


-3  

sed has a very simple syntax for this, '-i' is interactive (no need for newfile). To replace only the first instance:

sed有一个非常简单的语法,“-i”是交互式的(不需要newfile)。仅替换第一个实例:

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

to replace globally you would use

要取代全球,你将会使用。

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

In your example I would use (^ and $ are begin and end of line respectively)

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

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

#1


95  

 # 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---

or, if you prefer: Editor's note: works with GNU sed only.

或者,如果您喜欢:编辑器的注释:只使用GNU sed。

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

Source

#2


204  

Write a sed script that will only replace the first occurrence of "Apple" by "Banana"

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

Example Input: Output:

输入:输出示例:

     Apple       Banana
     Orange      Orange
     Apple       Apple

This is the simple script: Editor's note: works with GNU sed only.

这是一个简单的脚本:编辑器的注释:只处理GNU sed。

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

#3


48  

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

this worked for me.

这为我工作。

example

例子

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

Editor's note: both work with GNU sed only.

编者按:这两种方法都只使用GNU sed。

#4


24  

An overview of the many helpful existing answers, complemented with explanations:

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

The examples here use a simplified use case: replace the word 'foo' with 'bar' in the first matching line only.
Due to use of ANSI C-quoted strings ($'...') to provide the sample input lines, bash, ksh, or zsh is assumed as the shell.

这里的示例使用一个简化的用例:只在第一个匹配行中替换“foo”和“bar”。由于使用ANSI c引用的字符串($“…”)来提供样例输入行,bash、ksh或zsh被假定为shell。


GNU sed only:

GNU sed只:

Ben Hoffstein's anwswer shows us that GNU provides an extension to the POSIX specification for sed that allows the the following 2-address form: 0,/re/ (re represents an arbitrary regular expression here).

Ben Hoffstein的anwswer向我们展示了GNU为sed提供了一个对POSIX规范的扩展,它允许以下2个地址形式:0,/re/ (re表示这里的任意正则表达式)。

0,/re/ allows the regex to match on the very first line also. In other words: such an address will create a range from the 1st line up to and including the line that matches re - whether re occurs on the 1st line or on any subsequent line.

0,/re/允许regex在第一行上匹配。换句话说:这样的地址将创建从第一行到第一行的范围,包括在第一行或任何后续行中发生的重新出现的行。

  • Contrast this with the POSIX-compliant form 1,/re/, which creates a range that matches from the 1st line up to and including the line that matches re on subsequent lines; in other words: this will not detect the first occurrence of an re match if it happens to occur on the 1st line and also prevents the use of shorthand // for reuse of the most recently used regex (see next point).[1]
  • 与posix兼容的表单1 /re/进行对比,该表单创建了从第一行到第一行的范围,并包括在后续行中匹配的行;换句话说:如果发生在第一行的情况下,这将无法检测到第一次重新匹配的情况,而且还防止了对最近使用的regex的重用(见下一个点)的使用。

If you combine a 0,/re/ address with an s/.../.../ (substitution) call that uses the same regular expression, your command will effectively only perform the substitution on the first line that matches re.
sed provides a convenient shortcut for reusing the most recently applied regular expression: an empty delimiter pair, //.

如果你把一个0,/re/地址和s/……/(替换)调用使用相同的正则表达式,您的命令将有效地仅在匹配的第一行上执行替换。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

A POSIX-features-only sed such as BSD (OS X) sed (will also work with GNU sed):

一个posix特性——只使用了BSD (OS X) sed(也适用于GNU sed):

Since 0,/re/ cannot be used and the form 1,/re/ will not detect re if it happens to occur on the very first line (see above), special handling for the 1st line is required.

由于0,/re/不能使用,表格1,/re/将无法检测到,如果发生在第一行(见上文),则需要对第一行进行特殊处理。

MikhailVS's answer mentions the technique, put into a concrete example here:

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

Note:

注意:

  • The empty regex // shortcut is employed twice here: once for the endpoint of the range, and once in the s call; in both cases, regex foo is implicitly reused, allowing us not to have to duplicate it, which makes both for shorter and more maintainable code.

    空的regex //快捷方式在这里使用了两次:一次用于范围的端点,一次在s调用中;在这两种情况下,regex foo都被隐式重用,允许我们不需要复制它,这使得代码更短更容易维护。

  • POSIX sed needs actual newlines after certain functions, such as after the name of a label or even its omission, as is the case with t here; strategically splitting the script into multiple -e options is an alternative to using an actual newlines: end each -e script chunk where a newline would normally need to go.

    POSIX sed在某些功能之后需要实际的换行,比如在标签的名称或者它的省略之后,就像t在这里一样;将脚本战略性地分割成多个-e选项是使用实际换行的另一种选择:在通常需要换行的地方结束每个-e脚本块。

1 s/foo/bar/ replaces foo on the 1st line only, if found there. If so, t branches to the end of the script (skips remaining commands on the line). (The t function branches to a label only if the most recent s call performed an actual substitution; in the absence of a label, as is the case here, the end of the script is branched to).

1s /foo/bar/替代foo在第一行,如果发现。如果是这样,则t分支到脚本的末尾(跳过该行剩余的命令)。(只有在最近的s调用执行了实际的替换时,t函数分支才会被标记为一个标签;在没有标签的情况下,就像这里的情况一样,脚本的结尾是分支到的。

When that happens, range address 1,//, which normally finds the first occurrence starting from line 2, will not match, and the range will not be processed, because the address is evaluated when the current line is already 2.

当发生这种情况时,通常会发现从第2行开始出现第一个事件的range address 1 //,将不匹配,并且该范围将不被处理,因为在当前行已经为2的情况下计算地址。

Conversely, if there's no match on the 1st line, 1,// will be entered, and will find the true first match.

相反,如果第一行没有匹配,就输入1,//将进入,并将找到真正的第一匹配。

The net effect is the same as with GNU sed's 0,/re/: only the first occurrence is replaced, whether it occurs on the 1st line or any other.

净效果与GNU sed的0,/re/:只有第一个出现被替换,无论是在第一行还是任何其他。


NON-range approaches

NON-range方法

potong's answer demonstrates loop techniques that bypass the need for a range; since he uses GNU sed syntax, here are the POSIX-compliant equivalents:

potong的回答演示了绕过需求范围的循环技术;由于他使用的是GNU sed语法,这里是与posix兼容的等效项:

Loop technique 1: On first match, perform the substitution, then enter a loop that simply prints the remaining lines as-is:

循环技术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

Loop technique 2, for smallish files only: read the entire input into memory, then perform a single substitution on it.

循环技术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 provides examples of what happens with 1,/re/, with and without a subsequent s//:
- sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' yields $'1bar\n2bar'; i.e., both lines were updated, because line number 1 matches the 1st line, and regex /foo/ - the end of the range - is then only looked for starting on the next line. Therefore, both lines are selected in this case, and the s/foo/bar/ substitution is performed on both of them.
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' fails: with sed: first RE may not be empty (BSD/macOS) and sed: -e expression #1, char 0: no previous regular expression (GNU), because, at the time the 1st line is being processed (due to line number 1 starting the range), no regex has been applied yet, so // doesn't refer to anything.
With the exception of GNU sed's special 0,/re/ syntax, any range that starts with a line number effectively precludes use of //.

[1]1.61803提供了1、/re/、with和没有后续s//的例子:- sed '1,/foo/ s/foo/bar/' <<$' ' $' $' ' $'1bar\n2bar';即。这两行都被更新了,因为行号1匹配第一行,而regex /foo/ - - -范围的结束-则只需要从下一行开始。因此,在本例中选择了这两行,并对它们执行了s/foo/bar/替换。- sed的1 / foo / s / /酒吧/ ' < < < $ 1 foo \ n2foo \ n3foo的失败:sed:第一再保险不得空(BSD / macOS)和sed:# 1 - e表达式,字符0:没有前一个正则表达式(GNU),因为,当时正在处理的1号线(1号线启动范围),没有应用,正则表达式/ /不引用任何东西。除了GNU sed的特殊0,/re/语法之外,任何以行号开头的范围都有效地阻止了//。

#5


21  

You could use awk to do something similar..

你可以用awk做一些类似的事情。

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

Explanation:

解释:

/#include/ && !done

Runs the action statement between {} when the line matches "#include" and we haven't already processed it.

当行匹配“#include”时,在{}之间运行该操作语句,而我们还没有处理它。

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

This prints #include "newfile.h", we need to escape the quotes. Then we set the done variable to 1, so we don't add more includes.

这个打印# include”newfile中。h,我们需要避开引号。然后我们将done变量设置为1,因此我们不添加更多的include。

1;

This means "print out the line" - an empty action defaults to print $0, which prints out the whole line. A one liner and easier to understand than sed IMO :-)

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

#6


13  

Quite a comprehensive collection of answers on linuxtopia sed FAQ. It also highlights that some answers people provided won't work with non-GNU version of sed, eg

关于linuxtopia sed FAQ的完整答案集。它还强调了一些人提供的答案与非gnu版本的sed(如:)不兼容。

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

in non-GNU version will have to be

在非gnu版本中必须是这样。

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

However, this version won't work with gnu sed.

但是,这个版本不会使用gnu sed。

Here's a version that works with both:

这里有一个版本可以同时使用:

-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'

ex:

例:

sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename

#7


12  

Just add the number of occurrence at the end:

只需要增加结尾的次数:

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

#8


12  

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

How this script works: For lines between 1 and the first #include (after line 1), if the line starts with #include, then prepend the specified line.

这个脚本是如何工作的:在1和第一个#之间的行(在第1行之后),如果行从#include开始,然后prepend指定的行。

However, if the first #include is in line 1, then both line 1 and the next subsequent #include will have the line prepended. If you are using GNU sed, it has an extension where 0,/^#include/ (instead of 1,) will do the right thing.

但是,如果第一个#include在第1行,那么第1行和下一个后续的#include将会使行提前。如果您正在使用GNU sed,延长0,/ ^ # include /(而不是1)将做正确的事。

#9


7  

A possible solution:

一个可能的解决方案:

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

Explanation:

解释:

  • read lines until we find the #include, print these lines then start new cycle
  • 读行直到找到#include,打印这些行然后开始新的循环。
  • insert the new include line
  • 插入新的include行。
  • enter a loop that just reads lines (by default sed will also print these lines), we won't get back to the first part of the script from here
  • 输入一个只读取行(默认sed也会打印这些行)的循环,我们不会从这里返回到脚本的第一部分。

#10


2  

i would do this with an awk script:

我会用awk脚本做这个:

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

then run it with awk:

然后用awk运行它:

awk -f awkscript headerfile.h > headerfilenew.h

might be sloppy, I'm new to this.

可能是草率的,我是新手。

#11


2  

As an alternative suggestion you may want to look at the ed command.

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

I finally got this to work in a Bash script used to insert a unique timestamp in each item in an RSS feed:

最后,我在一个Bash脚本中完成了这个工作,该脚本用于在RSS提要中为每个条目插入一个惟一的时间戳:

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

It changes the first occurrence only.

它只会改变第一次出现的情况。

${nowms} is the time in milliseconds set by a Perl script, $counter is a counter used for loop control within the script, \ allows the command to be continued on the next line.

${nowms}是一个Perl脚本设置的毫秒数,$counter是一个用于在脚本中循环控制的计数器,\允许在下一行继续执行命令。

The file is read in and stdout is redirected to a work file.

该文件被读入,并且stdout被重定向到一个工作文件。

The way I understand it, 1,/====RSSpermalink====/ tells sed when to stop by setting a range limitation, and then s/====RSSpermalink====/${nowms}/ is the familiar sed command to replace the first string with the second.

我理解它的方式,1,/=== RSSpermalink==== =,通过设置范围限制来告诉sed何时停止,然后s/=== RSSpermalink====/${nowms}/是熟悉的sed命令,以替换第一个字符串。

In my case I put the command in double quotation marks becauase I am using it in a Bash script with variables.

在我的例子中,我将命令放在双引号中,因为我在一个带有变量的Bash脚本中使用它。

#13


2  

Using FreeBSD ed and avoid ed's "no match" error in case there is no include statement in a file to be processed:

使用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

#14


2  

This might work for you (GNU sed):

这可能对你有用(GNU sed):

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

or if memory is not a problem:

或者,如果记忆不是问题:

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

#15


2  

I know this is an old post but I had a solution that I used to use:

我知道这是一个旧的帖子,但我有一个我曾经使用过的解决方案:

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

Basically use grep to find the first occurence and stop there. Also print line number ie 5:line. Pipe that into sed and remove the : and anything after so you are just left with a line number. Pipe that into sed which adds s/.*/replace to the end which gives the a 1 line script which is piped into the last sed to run as a script on file.

主要使用grep查找第一次出现并停止。也打印行号ie 5:行。将其放入sed并删除:之后,您就只剩下行号了。进入sed的管道,添加了s/。*/replace to the end,它提供了一个1行脚本,该脚本被输入到最后一个sed中,以作为文件的脚本运行。

so if regex = #include and replace = blah and the first occurrance grep finds is on line 5 then the data piped to the last sed would be 5s/.*/blah/.

因此,如果regex = #include和replace = blah,第一次发生的grep发现在第5行,那么最后sed的数据将是5s/.*/blah/。

#16


1  

If anyone came here to replace a character for the first occurrence in all lines (like myself), use this:

如果有人来这里是为了替换所有行中第一次出现的字符(像我自己),请使用以下方法:

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

By changing 1 to 2 for example, you can replace all the second a's only instead.

例如,通过将1改为2,您可以替换所有的第二个a。

#17


0  

The following command removes the first occurrence of a string, within a file. It removes the empty line too. It is presented on an xml file, but it would work with any file.

下面的命令删除了文件中第一个字符串的出现。它也去掉了空行。它是在一个xml文件上显示的,但是它可以与任何文件一起工作。

Useful if you work with xml files and you want to remove a tag. In this example it removes the first occurrence of the "isTag" tag.

如果您使用xml文件,并且想要删除一个标记,那么这很有用。在本例中,它删除了第一次出现的“isTag”标记。

Command:

命令:

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

Source file (source.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>

Result file (output.txt)

结果文件(output.txt)

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

ps: it didn't work for me on Solaris SunOS 5.10 (quite old), but it works on Linux 2.6, sed version 4.1.5

ps:在Solaris SunOS 5.10(相当老的版本)上,它并没有为我工作,但是它在Linux 2.6版本上运行,sed版本4.1.5。

#18


0  

Nothing new but perhaps a little more concrete answer: sed -rn '0,/foo(bar).*/ s%%\1%p'

没有什么新的,但可能有一个更具体的答案:sed -rn '0,/foo(bar)。* / % % \ 1% p '

Example: xwininfo -name unity-launcher produces output like:

示例:xwininfo -name unitylauncher产生如下输出:

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

Extracting window ID with xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p' produces:

提取窗口ID与xwininfo - name unity面板| sed rn”0 / ^ xwininfo:窗口ID:(0 x[0-9a-fA-F]+)。* p / s % % \ 1%的产生:

0x2200003

#19


-3  

sed has a very simple syntax for this, '-i' is interactive (no need for newfile). To replace only the first instance:

sed有一个非常简单的语法,“-i”是交互式的(不需要newfile)。仅替换第一个实例:

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

to replace globally you would use

要取代全球,你将会使用。

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

In your example I would use (^ and $ are begin and end of line respectively)

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

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