想要只用sed代替首次出现


26

原始文件

claudio
antonio
claudio
michele

我只想将“ claudio”的第一次出现更改为“ claudia”,因此文件结果

claudia
antonio
claudio
michele

我试过了

sed -e '1,/claudio/s/claudio/claudia/' nomi

但是执行全局替换。为什么?


看看这里linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/...info sed:(0,/REGEXP/:0行号可以像一个地址规范使用0,/REGEXP/,这样sed会尽量在第一行输入匹配REGEXP太换句话说,。0,/REGEXP/是与相似1,/REGEXP/,不同之处在于,如果ADDR2匹配输入的第一行,则0,/ REGEXP /格式将认为它结束了范围,而1,/ REGEXP /格式将匹配其范围的开始并因此使范围跨度直到正则表达式的第二次出现)
jimmij 2015年


awk '/claudio/ && !ok { sub(/claudio/,"claudia"); ok=1 } 1' nomi应该做的事
亚当·卡兹

Answers:


23

如果您使用的是GNU sed,请尝试:

sed -e '0,/claudio/ s/claudio/claudia/' nomi

sed开始该范围的行之后,才开始检查该范围的正则表达式。

man sed(POSIX联机帮助页,重点是我的):

具有两个地址的编辑命令应选择包含范围
从与第一个地址匹配的第一个模式空间与第二个匹配的
下一个模式空间

使用 awk

awk工作范围超出您的预期:

$ awk 'NR==1,/claudio/{sub(/claudio/, "claudia")} 1' nomi
claudia
antonio
claudio
michele

说明:

  • NR==1,/claudio/

    此范围从第1行开始,到第一次出现结束claudio

  • sub(/claudio/, "claudia")

    当我们在范围内时,将执行此替代命令。

  • 1

    这个awk的神秘缩写,用于打印行。


1
那是假设GNU sed
斯特凡Chazelas

@StéphaneChazelas如果设置了POSIXLY_CORRECT,它也可以工作,但是我想这并不意味着我想要的那么多。答案已更新(我缺少BSD测试机)。
John1024

IMO,awk可以使用布尔状态变量来简化:awk '!r && /claudio/ {sub(/claudio/,"claudia"); r=1} 1'
glenn jackman 2015年

@glennjackman或awk !x{x=sub(/claudio/,"claudia")}1

在第一部分中,我也无法成功使用其他定界符:0,/claudio/
Pat Myron

4

这是sed的另外2种编程工作:它们都将整个文件读入单个字符串,然后搜索将仅替换第一个。

sed -n ':a;N;$bb;ba;:b;s/\(claudi\)o/\1a/;p' file
sed -n '1h;1!H;${g;s/\(claudi\)o/\1a/;p;}' file

有评论:

sed -n '                # don't implicitly print input
  :a                    # label "a"
  N                     # append next line to pattern space
  $bb                   # at the last line, goto "b"
  ba                    # goto "a"
  :b                    # label "b"
  s/\(claudi\)o/\1a/    # replace
  p                     # and print
' file
sed -n '                # don't implicitly print input
  1h                    # put line 1 in the hold space
  1!H                   # for subsequent lines, append to hold space
  ${                    # on the last line
    g                     # put the hold space in pattern space
    s/\(claudi\)o/\1a/    # replace
    p                     # print
  }
' file

3

新版本的GNU sed支持该-z选项。

通常,sed通过读取字符串直到行尾字符(换行或回车)来读取行。
GNU版本的sed在版本4.2.2中添加了一个功能,以改为使用“ NULL”字符。如果您有使用NULL作为记录分隔符的文件,这将很有用。一些GNU实用程序可以生成使用NULL而不是换行的输出,例如“ find。-print0”或“ grep -lZ”。

当您要sed处理不同的行时,可以使用此选项。

echo 'claudio
antonio
claudio
michele' | sed -z 's/claudio/claudia/'

退货

claudia
antonio
claudio
michele

1

您可以使用awk标志来了解替换是否已经完成。如果没有,请继续:

$ awk '!f && /claudio/ {$0="claudia"; f=1}1' file
claudia
antonio
claudio
michele

1

如果您设置一些延迟,这实际上非常容易-无需去接触不可靠的扩展程序:

sed '$H;x;1,/claudio/s/claudio/claudia/;1d' <<\IN
claudio
antonio
claudio
michele
IN

这只是将第一行延迟到第二行,将第二行延迟到第三行,依此类推。

它打印:

claudia
antonio
claudio
michele

1

还有一个选择

sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/claudio/claudia/;}" -- nomi

优点是它使用双引号,因此您可以在内部使用变量,即。

export chngFrom=claudio
export chngTo=claudia
sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/${chngFrom}/${chngTo}/;}" -- nomi

1
是啊,你说得对。总体思路是相同的。但是,请尝试将单引号直接替换为双引号,然后看看是否可行。魔鬼在于细节。在此示例中,这些是空格和一个转义符。我相信先前答案的这种延续可以节省某人的时间。这就是我决定发布该帖子的原因。
utom

1

这也可以在没有保持空间且没有将所有行都缩进模式空间的情况下完成:

sed -n '/claudio/{s/o/a/;bx};p;b;:x;p;n;bx' nomi

说明:我们试图找到“ claudio”,如果这样做,我们将跳到:x和之间的小打印加载循环中bx。否则,我们将打印并在下一行重新启动脚本。

sed -n '      # do not print lines by default
  /claudio/ { # on lines that match "claudio" do ...
    s/o/a/    # replace "o" with "a"
    bx        # goto label x
  }           # end of do block
  p           # print the pattern space
  b           # go to the end of the script, continue with next line
  :x          # the label x for goto commands
  p           # print the pattern space
  n           # load the next line in the pattern space (clearing old contents)
  bx          # goto the label x
  ' nomi

1
sed -n '/claudia/{p;Q}'

sed -n '           # don't print input
    /claudia/      # regex search
    {              # when match is found do
    p;             # print line
    Q              # quit sed, don't print last buffered line
    {              # end do block

1
你烦了看问题吗?
don_crissti

1

苏玛莉

GNU语法:

sed '/claudio/{s//claudia/;:p;n;bp}' file

甚至(仅使用一次要替换的单词:

sed '/\(claudi\)o/{s//\1a/;:p;n;bp}' file

或者,使用POSIX语法:

sed -e '/claudio/{s//claudia/;:p' -e 'n;bp' -e '}' file

可在任何sed上运行,处理所需的行即可找到第一行claudio,即使claudio在第一行中也可运行,并且由于仅使用一个regex字符串而更短。

详情

要仅更改一行,您只需要选择一行。

使用1,/claudio/(从您的问题中)选择:

  • 从第一行开始(无条件)
  • 到包含字符串的下一claudio
$ cat file
claudio 1
antonio 2
claudio 3
michele 4

$ sed -n '1,/claudio/{p}' file
claudio 1
antonio 2
claudio 3

要选择包含的任何claudio,请使用:

$ sed -n `/claudio/{p}` file
claudio 1
claudio 3

要仅选择文件中的第一个 claudio,请使用:

sed -n '/claudio/{p;q}' file
claudio 1

然后,您只能在该行上进行替换:

sed '/claudio/{s/claudio/claudia/;q}' file
claudia 1

即使只有第一行的正则表达式匹配项在匹配正则表达式的第一行中发生变化,它也只会更改该行的第一个匹配项。

当然,/claudio/正则表达式可以简化为:

$ sed '/claudio/{s//claudia/;q}' file
claudia 1

然后,唯一缺少的是打印所有其他未修改的行:

sed '/claudio/{s//claudia/;:p;n;bp}' file
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.