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

发布于 2024-07-06 00:09:33 字数 285 浏览 7 评论 0 原文

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

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

如果我使用

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

它,它会替换所有#include。

也欢迎实现相同目标的替代建议。

I would like 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.

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(25

眼眸 2024-07-13 00:09:34

我将提出一个建议,这并不完全是原始问题所要求的,但对于那些还想专门替换第二次出现的匹配或任何其他专门枚举的正则表达式匹配的人来说。 使用 python 脚本和 for 循环,如果需要,可以从 bash 脚本调用它。 这对我来说是这样的,我正在替换包含字符串 --project 的特定行:

def replace_models(file_path, pixel_model, obj_model):
    # find your file --project matches
    pattern = re.compile(r'--project.*')
    new_file = ""
    with open(file_path, 'r') as f:
        match = 1
        for line in f:
            # Remove line ending before we do replacement
            line = line.strip()
            # replace first --project line match with pixel
            if match == 1:
                result = re.sub(pattern, "--project='" + pixel_model + "'", line)
            # replace second --project line match with object
            elif match == 2:
                result = re.sub(pattern, "--project='" + obj_model + "'", line)
            else:
                result = line
            # Check that a substitution was actually made
            if result is not line:
                # Add a backslash to the replaced line
                result += " \\"
                print("\nReplaced ", line, " with ", result)
                # Increment number of matches found
                match += 1
            # Add the potentially modified line to our new file
            new_file = new_file + result + "\n"
        # close file / save output
        f.close()
    fout = open(file_path, "w")
    fout.write(new_file)
    fout.close()

I will make a suggestion that is not exactly what the original question asks for, but for those who also want to specifically replace perhaps the second occurrence of a match, or any other specifically enumerated regular expression match. Use a python script, and a for loop, call it from a bash script if needed. Here's what it looked like for me, where I was replacing specific lines containing the string --project:

def replace_models(file_path, pixel_model, obj_model):
    # find your file --project matches
    pattern = re.compile(r'--project.*')
    new_file = ""
    with open(file_path, 'r') as f:
        match = 1
        for line in f:
            # Remove line ending before we do replacement
            line = line.strip()
            # replace first --project line match with pixel
            if match == 1:
                result = re.sub(pattern, "--project='" + pixel_model + "'", line)
            # replace second --project line match with object
            elif match == 2:
                result = re.sub(pattern, "--project='" + obj_model + "'", line)
            else:
                result = line
            # Check that a substitution was actually made
            if result is not line:
                # Add a backslash to the replaced line
                result += " \\"
                print("\nReplaced ", line, " with ", result)
                # Increment number of matches found
                match += 1
            # Add the potentially modified line to our new file
            new_file = new_file + result + "\n"
        # close file / save output
        f.close()
    fout = open(file_path, "w")
    fout.write(new_file)
    fout.close()
别理我 2024-07-13 00:09:34
sed -e 's/pattern/REPLACEMENT/1' <INPUTFILE
sed -e 's/pattern/REPLACEMENT/1' <INPUTFILE
暗地喜欢 2024-07-13 00:09:34

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

With GNU sed's -z option you could process the whole file as if it was only one line. That way a s/…/…/ would only replace the first match in the whole file. Remember: s/…/…/ only replaces the first match in each line, but with the -z option sed treats the whole file as a single line.

sed -z 's/#include/#include "newfile.h"\n#include'

In the general case you have to rewrite your sed expression since the pattern space now holds the whole file instead of just one line. Some examples:

  • s/text.*// can be rewritten as s/text[^\n]*//. [^\n] matches everything except the newline character. [^\n]* will match all symbols after text until a newline is reached.
  • s/^text// can be rewritten as s/(^|\n)text//.
  • s/text$// can be rewritten as s/text(\n|$)//.
命硬 2024-07-13 00:09:34

一个可能的解决方案:

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

解释:

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

A possible solution:

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

Explanation:

  • read lines until we find the #include, print these lines then start new cycle
  • insert the new include line
  • 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
吹泡泡o 2024-07-13 00:09:34

只需在末尾添加出现的次数:

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

Just add the number of occurrence at the end:

sed s/#include/#include "newfile.h"\n#include/1
黑寡妇 2024-07-13 00:09:34

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

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

基本上使用 grep 打印第一个出现的位置并在那里停止。 另外打印行号,即 5:line。 将其通过管道传输到 sed 中并删除 : 和后面的任何内容,这样您就只剩下行号了。 将其通过管道输送到 sed 中,将 s/.*/replace 添加到末尾编号,这会生成一个 1 行脚本,该脚本通过管道输送到最后一个 sed 中,作为文件上的脚本运行。

因此,如果 regex = #include 和 Replace = blah 并且 grep 找到的第一个匹配项位于第 5 行,那么通过管道传输到最后一个 sed 的数据将是 5s/。 */等等/

即使第一次出现在第一行也有效。

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 print the first occurrence and stop there. Additionally 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 number, which results in a 1 line script which is piped into the last sed to run as a script on the file.

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

Works even if first occurrence is on the first line.

一影成城 2024-07-13 00:09:34
#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

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

但是,如果第一个 #include 位于第 1 行,则第 1 行和下一个后续 #include 都将在前面添加该行。 如果您使用的是 GNU sed,它有一个扩展,其中 0,/^#include/(而不是 1,)将执行正确的操作事物。

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

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.

看海 2024-07-13 00:09:34

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

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

然后用 awk 运行它:

awk -f awkscript headerfile.h > headerfilenew.h

可能很草率,我对此很陌生。

i would do this with an awk script:

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

then run it with awk:

awk -f awkscript headerfile.h > headerfilenew.h

might be sloppy, I'm new to this.

萌吟 2024-07-13 00:09:34

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.

POSIXly (also valid in sed), Only one regex used, need memory only for one line (as usual):

sed '/\(#include\).*/!b;//{h;s//\1 "newfile.h"/;G};:1;n;b1'

Explained:

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.
落在眉间の轻吻 2024-07-13 00:09:34

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

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

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
呢古 2024-07-13 00:09:34

我终于在 Bash 脚本中实现了这一点,该脚本用于在 RSS 提要中的每个项目中插入唯一的时间戳:

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

它仅更改第一个出现的位置。

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

文件被读入,标准输出被重定向到工作文件。

按照我的理解, 1,/====RSSpermalink====/ 通过设置范围限制告诉 sed 何时停止,然后 s/====RSSpermalink= ===/${nowms}/ 是熟悉的 sed 命令,用于将第一个字符串替换为第二个字符串。

就我而言,我将命令放在双引号中,因为我在带有变量的 Bash 脚本中使用它。

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

        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.

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

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.

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

听风念你 2024-07-13 00:09:34

使用 FreeBSD ed 并避免 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

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

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
划一舟意中人 2024-07-13 00:09:34

这可能对你有用(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...

This might work for you (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...
回眸一遍 2024-07-13 00:09:34

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

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

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.

时光无声 2024-07-13 00:09:34

这里的一个可能的解决方案可能是告诉编译器包含标头,而不在源文件中提及它。 在 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 来实现此目的。

A possible solution here might be to tell the compiler to include the header without it being mentioned in the source files. IN GCC there are these options:

   -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's compiler has the /FI (forced include) option.

This feature can be handy for some common header, like platform configuration. The Linux kernel's Makefile uses -include for this.

淡紫姑娘! 2024-07-13 00:09:34

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

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

0x2200003

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

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

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:

0x2200003
孤独患者 2024-07-13 00:09:34

我需要一个同时适用于 GNU 和 BSD 的解决方案,而且我也知道第一行永远不会是我需要更新的行:

sed -e "1,/pattern/s/pattern/replacement/"

尝试使用 // 功能来不重复 < code>pattern 对我不起作用,因此需要重复它。

I needed a solution that would work both on GNU and BSD, and I also knew that the first line would never be the one I'd need to update:

sed -e "1,/pattern/s/pattern/replacement/"

Trying the // feature to not repeat the pattern did not work for me, hence needing to repeat it.

月野兔 2024-07-13 00:09:34

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

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.

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.

Command:

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

Source file (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)

<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

婴鹅 2024-07-13 00:09:34

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

然后简单地解决这些行就可以解决问题 - 即使OP的措辞仅考虑第一。

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

The use case can perhaps be that your occurences are spread throughout your file, but you know your only concern is in the first 10, 20 or 100 lines.

Then simply adressing those lines fixes the issue - even if the wording of the OP regards first only.

sed '1,10s/#include/#include "newfile.h"\n#include/'
你好,陌生人 2024-07-13 00:09:33

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

此处的示例使用简化的用例:替换单词“仅在第一个匹配行中包含 foo' 和 'bar'。
由于使用 ANSI C 引用字符串 ($' ...') 提供示例输入行,bashkshzsh 假定为shell。


GNU sed 仅:

Ben Hoffstein 的回答 向我们展示了 GNU 为 扩展 rel="noreferrer">sed 的 POSIX 规范,允许以下2-地址形式0,/re/re在这里代表任意正则表达式)。

0,/re/ 允许正则表达式在第一行也匹配。 换句话说:这样的地址将创建从第一行到(包括)匹配 re 的行的范围 - 无论 re 出现在第一行还是任何后续行上线。

  • 将此与 POSIX 兼容的形式 1,/re/ 进行对比,后者创建一个从第一行到匹配 re 的行(包括该行)的范围。 后续行; 换句话说:如果re匹配恰好出现在第一行并且,则将不会检测到第一次出现re匹配防止使用简写 // 来重用最近使用的正则表达式(请参阅下一点)。1

如果将 0,/re/ 地址与 s 组合/.../.../(替换)使用相同正则表达式的调用,您的命令将有效地仅在第一行上执行替换与 re 匹配。
sed 提供了一个方便的重用最近应用的正则表达式的快捷方式分隔符对,//

$ sed '0,/foo/ s//bar/' <<<

仅限 POSIX 功能的 sed,例如 BSD (macOS) sed(也可与 GNU 配合使用 sed):

由于无法使用0,/re/,并且1,/re/形式将无法检测到re如果它恰好发生在第一行(见上文),则需要对第一行进行特殊处理

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

$ sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<

注意:

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

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

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

发生这种情况时,通常会查找从第 2 行开始的第一个匹配项的范围地址 1,//匹配,并且范围将被处理,因为当当前行已经是2时评估地址。

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

最终效果与 GNU sed0,/re/ 相同:仅替换第一个出现的位置,无论它出现在第一行还是任何其他行。


非范围方法

potong 的答案演示了循环技术绕过对范围的需求; 由于他使用 GNU sed 语法,因此以下是符合 POSIX 的等效语法

循环技术 1:在第一次匹配时,执行替换,然后 < strong>输入一个循环,按原样打印剩余的行:

$ sed -e '/foo/ {s//bar/; ' -e ':a' -e '$!{n;ba' -e '};}' <<<

循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面

$ sed -e ':a' -e '$!{N;ba' -e '}; s/foo/bar/' <<<

1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
1st foo\nUnrelated\n2nd foo\n3rd foo' 1st bar # only 1st match of 'foo' replaced Unrelated 2nd foo 3rd foo

仅限 POSIX 功能的 sed,例如 BSD (macOS) sed(也可与 GNU 配合使用 sed):

由于无法使用0,/re/,并且1,/re/形式将无法检测到re如果它恰好发生在第一行(见上文),则需要对第一行进行特殊处理

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


注意:

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

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

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

发生这种情况时,通常会查找从第 2 行开始的第一个匹配项的范围地址 1,//匹配,并且范围将被处理,因为当当前行已经是2时评估地址。

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

最终效果与 GNU sed0,/re/ 相同:仅替换第一个出现的位置,无论它出现在第一行还是任何其他行。


非范围方法

potong 的答案演示了循环技术绕过对范围的需求; 由于他使用 GNU sed 语法,因此以下是符合 POSIX 的等效语法

循环技术 1:在第一次匹配时,执行替换,然后 < strong>输入一个循环,按原样打印剩余的行:


循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面



1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
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/ 仅替换第一行的 foo(如果在第一行找到)。
如果是这样,t 分支到脚本末尾(跳过该行的剩余命令)。 (仅当最近的 s 调用执行实际替换时,t 函数才会分支到标签;在没有标签的情况下,就像这里的情况一样,该脚本分支到)。

发生这种情况时,通常会查找从第 2 行开始的第一个匹配项的范围地址 1,//匹配,并且范围将被处理,因为当当前行已经是2时评估地址。

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

最终效果与 GNU sed0,/re/ 相同:仅替换第一个出现的位置,无论它出现在第一行还是任何其他行。


非范围方法

potong 的答案演示了循环技术绕过对范围的需求; 由于他使用 GNU sed 语法,因此以下是符合 POSIX 的等效语法

循环技术 1:在第一次匹配时,执行替换,然后 < strong>输入一个循环,按原样打印剩余的行:


循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面



1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
1st foo\nUnrelated\n2nd foo\n3rd foo' 1st bar # only 1st match of 'foo' replaced Unrelated 2nd foo 3rd foo

仅限 POSIX 功能的 sed,例如 BSD (macOS) sed(也可与 GNU 配合使用 sed):

由于无法使用0,/re/,并且1,/re/形式将无法检测到re如果它恰好发生在第一行(见上文),则需要对第一行进行特殊处理

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


注意:

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

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

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

发生这种情况时,通常会查找从第 2 行开始的第一个匹配项的范围地址 1,//匹配,并且范围将被处理,因为当当前行已经是2时评估地址。

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

最终效果与 GNU sed0,/re/ 相同:仅替换第一个出现的位置,无论它出现在第一行还是任何其他行。


非范围方法

potong 的答案演示了循环技术绕过对范围的需求; 由于他使用 GNU sed 语法,因此以下是符合 POSIX 的等效语法

循环技术 1:在第一次匹配时,执行替换,然后 < strong>输入一个循环,按原样打印剩余的行:


循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面



1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
1st foo\nUnrelated\n2nd foo\n3rd foo' 1st bar Unrelated 2nd foo 3rd foo

循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面



1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
1st foo\nUnrelated\n2nd foo\n3rd foo' 1st bar # only 1st match of 'foo' replaced Unrelated 2nd foo 3rd foo

仅限 POSIX 功能的 sed,例如 BSD (macOS) sed(也可与 GNU 配合使用 sed):

由于无法使用0,/re/,并且1,/re/形式将无法检测到re如果它恰好发生在第一行(见上文),则需要对第一行进行特殊处理

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


注意:

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

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

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

发生这种情况时,通常会查找从第 2 行开始的第一个匹配项的范围地址 1,//匹配,并且范围将被处理,因为当当前行已经是2时评估地址。

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

最终效果与 GNU sed0,/re/ 相同:仅替换第一个出现的位置,无论它出现在第一行还是任何其他行。


非范围方法

potong 的答案演示了循环技术绕过对范围的需求; 由于他使用 GNU sed 语法,因此以下是符合 POSIX 的等效语法

循环技术 1:在第一次匹配时,执行替换,然后 < strong>输入一个循环,按原样打印剩余的行:


循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面



1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
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/ 仅替换第一行的 foo(如果在第一行找到)。
如果是这样,t 分支到脚本末尾(跳过该行的剩余命令)。 (仅当最近的 s 调用执行实际替换时,t 函数才会分支到标签;在没有标签的情况下,就像这里的情况一样,该脚本分支到)。

发生这种情况时,通常会查找从第 2 行开始的第一个匹配项的范围地址 1,//匹配,并且范围将被处理,因为当当前行已经是2时评估地址。

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

最终效果与 GNU sed0,/re/ 相同:仅替换第一个出现的位置,无论它出现在第一行还是任何其他行。


非范围方法

potong 的答案演示了循环技术绕过对范围的需求; 由于他使用 GNU sed 语法,因此以下是符合 POSIX 的等效语法

循环技术 1:在第一次匹配时,执行替换,然后 < strong>输入一个循环,按原样打印剩余的行:


循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面



1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
1st foo\nUnrelated\n2nd foo\n3rd foo' 1st bar # only 1st match of 'foo' replaced Unrelated 2nd foo 3rd foo

仅限 POSIX 功能的 sed,例如 BSD (macOS) sed(也可与 GNU 配合使用 sed):

由于无法使用0,/re/,并且1,/re/形式将无法检测到re如果它恰好发生在第一行(见上文),则需要对第一行进行特殊处理

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


注意:

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

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

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

发生这种情况时,通常会查找从第 2 行开始的第一个匹配项的范围地址 1,//匹配,并且范围将被处理,因为当当前行已经是2时评估地址。

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

最终效果与 GNU sed0,/re/ 相同:仅替换第一个出现的位置,无论它出现在第一行还是任何其他行。


非范围方法

potong 的答案演示了循环技术绕过对范围的需求; 由于他使用 GNU sed 语法,因此以下是符合 POSIX 的等效语法

循环技术 1:在第一次匹配时,执行替换,然后 < strong>输入一个循环,按原样打印剩余的行:


循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面



1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
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/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
1st foo\nUnrelated\n2nd foo\n3rd foo' 1st bar # only 1st match of 'foo' replaced Unrelated 2nd foo 3rd foo

仅限 POSIX 功能的 sed,例如 BSD (macOS) sed(也可与 GNU 配合使用 sed):

由于无法使用0,/re/,并且1,/re/形式将无法检测到re如果它恰好发生在第一行(见上文),则需要对第一行进行特殊处理

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


注意:

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

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

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

发生这种情况时,通常会查找从第 2 行开始的第一个匹配项的范围地址 1,//匹配,并且范围将被处理,因为当当前行已经是2时评估地址。

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

最终效果与 GNU sed0,/re/ 相同:仅替换第一个出现的位置,无论它出现在第一行还是任何其他行。


非范围方法

potong 的答案演示了循环技术绕过对范围的需求; 由于他使用 GNU sed 语法,因此以下是符合 POSIX 的等效语法

循环技术 1:在第一次匹配时,执行替换,然后 < strong>输入一个循环,按原样打印剩余的行:


循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面



1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
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/ 仅替换第一行的 foo(如果在第一行找到)。
如果是这样,t 分支到脚本末尾(跳过该行的剩余命令)。 (仅当最近的 s 调用执行实际替换时,t 函数才会分支到标签;在没有标签的情况下,就像这里的情况一样,该脚本分支到)。

发生这种情况时,通常会查找从第 2 行开始的第一个匹配项的范围地址 1,//匹配,并且范围将被处理,因为当当前行已经是2时评估地址。

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

最终效果与 GNU sed0,/re/ 相同:仅替换第一个出现的位置,无论它出现在第一行还是任何其他行。


非范围方法

potong 的答案演示了循环技术绕过对范围的需求; 由于他使用 GNU sed 语法,因此以下是符合 POSIX 的等效语法

循环技术 1:在第一次匹配时,执行替换,然后 < strong>输入一个循环,按原样打印剩余的行:


循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面



1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
1st foo\nUnrelated\n2nd foo\n3rd foo' 1st bar # only 1st match of 'foo' replaced Unrelated 2nd foo 3rd foo

仅限 POSIX 功能的 sed,例如 BSD (macOS) sed(也可与 GNU 配合使用 sed):

由于无法使用0,/re/,并且1,/re/形式将无法检测到re如果它恰好发生在第一行(见上文),则需要对第一行进行特殊处理

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


注意:

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

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

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

发生这种情况时,通常会查找从第 2 行开始的第一个匹配项的范围地址 1,//匹配,并且范围将被处理,因为当当前行已经是2时评估地址。

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

最终效果与 GNU sed0,/re/ 相同:仅替换第一个出现的位置,无论它出现在第一行还是任何其他行。


非范围方法

potong 的答案演示了循环技术绕过对范围的需求; 由于他使用 GNU sed 语法,因此以下是符合 POSIX 的等效语法

循环技术 1:在第一次匹配时,执行替换,然后 < strong>输入一个循环,按原样打印剩余的行:


循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面



1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
1st foo\nUnrelated\n2nd foo\n3rd foo' 1st bar Unrelated 2nd foo 3rd foo

循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面



1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
1st foo\nUnrelated\n2nd foo\n3rd foo' 1st bar # only 1st match of 'foo' replaced Unrelated 2nd foo 3rd foo

仅限 POSIX 功能的 sed,例如 BSD (macOS) sed(也可与 GNU 配合使用 sed):

由于无法使用0,/re/,并且1,/re/形式将无法检测到re如果它恰好发生在第一行(见上文),则需要对第一行进行特殊处理

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


注意:

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

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

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

发生这种情况时,通常会查找从第 2 行开始的第一个匹配项的范围地址 1,//匹配,并且范围将被处理,因为当当前行已经是2时评估地址。

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

最终效果与 GNU sed0,/re/ 相同:仅替换第一个出现的位置,无论它出现在第一行还是任何其他行。


非范围方法

potong 的答案演示了循环技术绕过对范围的需求; 由于他使用 GNU sed 语法,因此以下是符合 POSIX 的等效语法

循环技术 1:在第一次匹配时,执行替换,然后 < strong>输入一个循环,按原样打印剩余的行:


循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面



1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
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/ 仅替换第一行的 foo(如果在第一行找到)。
如果是这样,t 分支到脚本末尾(跳过该行的剩余命令)。 (仅当最近的 s 调用执行实际替换时,t 函数才会分支到标签;在没有标签的情况下,就像这里的情况一样,该脚本分支到)。

发生这种情况时,通常会查找从第 2 行开始的第一个匹配项的范围地址 1,//匹配,并且范围将被处理,因为当当前行已经是2时评估地址。

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

最终效果与 GNU sed0,/re/ 相同:仅替换第一个出现的位置,无论它出现在第一行还是任何其他行。


非范围方法

potong 的答案演示了循环技术绕过对范围的需求; 由于他使用 GNU sed 语法,因此以下是符合 POSIX 的等效语法

循环技术 1:在第一次匹配时,执行替换,然后 < strong>输入一个循环,按原样打印剩余的行:


循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面



1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。
1st foo\nUnrelated\n2nd foo\n3rd foo' 1st bar # only 1st match of 'foo' replaced Unrelated 2nd foo 3rd foo

仅限 POSIX 功能的 sed,例如 BSD (macOS) sed(也可与 GNU 配合使用 sed):

由于无法使用0,/re/,并且1,/re/形式将无法检测到re如果它恰好发生在第一行(见上文),则需要对第一行进行特殊处理

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


注意:

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

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

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

发生这种情况时,通常会查找从第 2 行开始的第一个匹配项的范围地址 1,//匹配,并且范围将被处理,因为当当前行已经是2时评估地址。

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

最终效果与 GNU sed0,/re/ 相同:仅替换第一个出现的位置,无论它出现在第一行还是任何其他行。


非范围方法

potong 的答案演示了循环技术绕过对范围的需求; 由于他使用 GNU sed 语法,因此以下是符合 POSIX 的等效语法

循环技术 1:在第一次匹配时,执行替换,然后 < strong>输入一个循环,按原样打印剩余的行:


循环技术 2,仅适用于小文件将整个输入读入内存,然后执行一次替换就在上面



1 1.61803 提供了 1,/re/ 发生情况的示例,无论是否有后续 s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' 产生 $'1bar\n2bar' ; 即,两行都已更新,因为行号 1 与第一行匹配,并且正则表达式 /foo/ - 范围的结尾 - 是然后只寻找从下一行开始。 因此,在这种情况下,两行都被选中,并且对它们都执行s/foo/bar/替换。
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失败:使用sed:首先RE 可能不为空 (BSD/macOS) 和 sed: -e expression #1, char 0: 没有先前的正则表达式 (GNU),因为当时第一行是正在处理(由于行号 1 开始范围),尚未应用正则表达式,因此 // 不引用任何内容。
    除了 GNU sed 的特殊 0,/re/ 语法之外,以行号开头的任何范围em> 有效地阻止了 // 的使用。

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.


GNU sed only:

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

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.

  • 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

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

$ sed '0,/foo/ s//bar/' <<<

A POSIX-features-only sed such as BSD (macOS) sed (will also work with 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.

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

$ sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<

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.

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

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

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.

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

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.


NON-range approaches

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:

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

$ sed -e '/foo/ {s//bar/; ' -e ':a' -e '$!{n;ba' -e '};}' <<<

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

$ sed -e ':a' -e '$!{N;ba' -e '}; s/foo/bar/' <<<

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 //.
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 (macOS) sed (will also work with 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.

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


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.

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

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

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.

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

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.


NON-range approaches

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:

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


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



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

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

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

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.

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

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.


NON-range approaches

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:

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


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



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 //.
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 (macOS) sed (will also work with 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.

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


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.

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

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

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.

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

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.


NON-range approaches

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:

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


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



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



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 //.
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 (macOS) sed (will also work with 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.

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


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.

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

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

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.

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

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.


NON-range approaches

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:

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


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



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

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

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

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.

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

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.


NON-range approaches

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:

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


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



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 //.
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 (macOS) sed (will also work with 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.

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


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.

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

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

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.

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

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.


NON-range approaches

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:

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


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



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 //.
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 //.
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 (macOS) sed (will also work with 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.

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


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.

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

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

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.

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

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.


NON-range approaches

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:

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


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



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

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

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

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.

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

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.


NON-range approaches

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:

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


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



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 //.
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 (macOS) sed (will also work with 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.

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


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.

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

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

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.

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

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.


NON-range approaches

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:

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


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



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



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 //.
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 (macOS) sed (will also work with 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.

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


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.

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

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

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.

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

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.


NON-range approaches

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:

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


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



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

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

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

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.

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

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.


NON-range approaches

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:

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


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



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 //.
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 (macOS) sed (will also work with 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.

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


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.

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

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

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.

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

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.


NON-range approaches

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:

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


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



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 //.
输什么也不输骨气 2024-07-13 00:09:33
 # 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,/foo/s//bar/' file 

来源

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

sed '0,/foo/s//bar/' file 

Source

相权↑美人 2024-07-13 00:09:33

一个 sed 脚本,仅将第一次出现的“Apple”替换为“Banana”

示例

     Input:      Output:

     Apple       Banana
     Apple       Apple
     Orange      Orange
     Apple       Apple

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

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

前两个参数 0/Apple/ 是范围说明符。 s/Apple/Banana/ 是在该范围内执行的内容。 因此,在这种情况下,“在开头 (0) 到 Apple 的第一个实例的范围内,将 Apple 替换为 Banana 只会替换第一个 Apple

背景:在传统的 sed 中,范围说明符是 还有“从这里开始”和“从这里结束”(包含)。但是最低的“开始”是第一行(第 1 行) ),如果“end here”是一个正则表达式,那么它只会尝试与“begin”之后的下一行进行匹配,因此最早可能的结束是第2行。因此,由于范围包含在内,因此最小的可能范围是“ 2 行”,最小起始范围是第 1 行和第 2 行(即,如果第 1 行出现了一个事件,则第 2 行出现的事件也将被更改,这在这种情况下是不需要的)。GNU sed 添加其自己的扩展,允许将开始指定为“伪”line 0,以便范围的结尾可以是line 1,允许其范围为“仅第一行”如果正则表达式与第一行匹配。

或者一个简化版本(像 // 这样的空 RE 意味着重新使用之前指定的 RE,因此这是等效的):

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

花括号是 对于 s 命令来说是可选的,所以这也是等价的:

sed '0,/Apple/s//Banana/' input_filename

所有这些都在 GNU 上工作仅sed

您还可以使用 homebrew brew install gnu-sed 在 OS X 上安装 GNU sed。

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

Example

     Input:      Output:

     Apple       Banana
     Apple       Apple
     Orange      Orange
     Apple       Apple

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

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

The first two parameters 0 and /Apple/ are the range specifier. The s/Apple/Banana/ is what is executed within that range. So in this case "within the range of the beginning (0) up to the first instance of Apple, replace Apple with Banana. Only the first Apple will be replaced.

Background: In traditional sed the range specifier is also "begin here" and "end here" (inclusive). However the lowest "begin" is the first line (line 1), and if the "end here" is a regex, then it is only attempted to match against on the next line after "begin", so the earliest possible end is line 2. So since range is inclusive, smallest possible range is "2 lines" and smallest starting range is both lines 1 and 2 (i.e. if there's an occurrence on line 1, occurrences on line 2 will also be changed, not desired in this case). GNU sed adds its own extension of allowing specifying start as the "pseudo" line 0 so that the end of the range can be line 1, allowing it a range of "only the first line" if the regex matches the first line.

Or a simplified version (an empty RE like // means to re-use the one specified before it, so this is equivalent):

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

And the curly braces are optional for the s command, so this is also equivalent:

sed '0,/Apple/s//Banana/' input_filename

All of these work on GNU sed only.

You can also install GNU sed on OS X using homebrew brew install gnu-sed.

梦忆晨望 2024-07-13 00:09:33
sed '0,/pattern/s/pattern/replacement/' filename

这对我有用。

示例

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

编者注:两者均仅适用于 GNU sed

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.

以可爱出名 2024-07-13 00:09:33

您可以使用 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”,我们需要转义引号。 然后我们将 did 变量设置为 1,这样我们就不会添加更多包含内容。

1;

这意味着“打印出该行”——空操作默认打印 $0,它会打印出整行。 在我看来,单行代码比 sed 更容易理解:-)

You could use awk to do something similar..

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.

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

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 :-)

菊凝晚露 2024-07-13 00:09:33

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

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

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

in non-GNU version will have to be

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

However, this version won't work with 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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文