BSD sed(Mac)从第n次出现到行尾如何替换?

时间:2019-05-06 03:53:23

标签: sed bsd

GNU sed中将是这样

's/foo/bar/3g' <<< "foofoofoofoofoo"

Output: "foofoobarbarbar"

BSD sed中的同一命令给我以下错误

sed: 1: "s/foo/bar/3g": more than one number or 'g' in substitute flags

如何在BSD sed上实现它?

我搜索了SO,发现this,但所有答案都是针对GNU的。 我读过这个人,但是很难弄清楚。

6 个答案:

答案 0 :(得分:3)

一个选项是使用标签和t命令实现循环:

$ sed -e ':l' -e 's/foo/bar/3' -e 'tl' <<< 'foofoofoofoofoo'
foofoobarbarbar

请小心,因为如果替换文本与原始RE(例如s/f.x/fox/)匹配,那么您将陷入无限循环,并且如果替换后生成原始文本,则会出乎意料结果,例如:

$ sed 's/foo/oo/3g' <<< 'foofoofffoo'
foofooffoo
$ sed -e ':l' -e 's/foo/oo/3' -e 'tl' <<< 'foofoofffoo'
foofoooo

请注意,第一个版本之所以有效,是因为它在一次文本传递中进行了所有替换,因此,先前的替换不被视为当前替换过程的字符串的一部分。

答案 1 :(得分:3)

如果它不是简单的s / old / new,则只需使用awk而不是sed。在任何UNIX盒子上的任何外壳中都有任何awk:

$ cat tst.awk
{
    head = ""
    tail = $0
    cnt  = 0
    while ( match(tail,old) ) {
        tgt = substr(tail,RSTART,RLENGTH)
        if ( ++cnt >= beg ) {
            tgt = new
        }
        head = head tgt
        tail = substr(tail,RSTART+RLENGTH)
    }
    print head tail
}

$ awk -v old='foo' -v new='bar' -v beg=3 -f tst.awk <<< "foofoofoofoofoo"
foofoobarbarbar

当然,这是几行代码,但是它是解决许多问题的极为通用的代码,所以很高兴知道它,很容易看到它在做什么,并且很容易进行修改以执行其他任何操作。

如果您希望简洁而不是清晰度和效率,可以将其简化为:

$ cat tst.awk
{
    head = ""
    cnt  = 0
    while ( match($0,old) ) {
        head = head (++cnt < beg ? substr($0,RSTART,RLENGTH) : new)
        $0 = substr($0,RSTART+RLENGTH)
    }
    print head $0
}

甚至是可怕的“单线”:

awk -v o='foo' -v n='bar' -v b=3 '{h="";c=0;while(s=match($0,o)){h=h (++c<b?substr($0,s,RLENGTH):n);$0=substr($0,s+RLENGTH)}$0=h$0}1' <<< "foofoofoofoofoo"
foofoobarbarbar

答案 2 :(得分:2)

awk *中的另一个用于单行处理:

$ echo foofoofoofoofoo | 
  awk -v n=3 'BEGIN{RS="foo"}{ORS=NR<n?RS:"bar"}1'
foofoobarbarbar

*已在gawk,mawk和Busybox awk上成功测试。在awk-20121220上失败。

答案 3 :(得分:2)

如果perl没问题:

$ echo 'foofoofoofoofoo' | perl -pe '$c=0; s/foo/++$c<3 ? $& : "bar"/ge'
foofoobarbarbar
  • $c=0对于每一行输入,初始化计数器
  • e修饰符用于允许Perl代码而不是替换部分中的字符串
  • ++$c<3 ? $& : "bar"根据计数器,保留或替换匹配的文本

答案 4 :(得分:2)

这可能对您有用:

sed -e ':a' -e 's/foo/\'$'\n/2' -e 'ta' -e 's/\'$'\n/bar/g' file

为第n个事件设置一个循环(在此示例中为2),并将其替换为唯一的字符/字符串(在此示例中为换行符)。如果循环失败,请用预期的字符串全局替换唯一的字符/字符串。

答案 5 :(得分:1)

您不能没有任何困难。

GNU sed手册中所述:

  

g

     

将替换项应用于所有与 regexp 匹配的匹配项,而不仅仅是第一个匹配项。

     

号码

     

仅替换 regexp number 个匹配项。

     s命令中的

交互注意:POSIX标准未指定当您混合使用g number 修饰符时应该发生的情况,并且目前尚无广泛共识。在sed实现中的含义。对于GNU sed,交互定义为:忽略 number 之前的匹配项,然后匹配并替换 number th以后的所有匹配项。)

但是,在Mac OS X上,该方法有效:

▶ sed 's/foo/bar/3' <<< 'foofoofoofoofoo'          
foofoobarfoofoo

这样做:

▶ sed 's/foo/bar/g' <<< 'foofoofoofoofoo'  
barbarbarbarbar

但是,如果将它们一起使用,则会发出问题中指出的错误。

@oguzismail提供了一个聪明而简单的解决方案,我添加了此附加说明,因为我认为这会有所帮助。 1 他的答案的较早版本显示了这一点,令人困惑的是,在测试时什么也没做:

▶ sed ':a; s/foo/bar/3; ta' <<< 'foofoofoofoofoo'                                                                                                                      
foofoofoofoofoo

与此同时,BSD手册也未提供任何解释。但是,POSIX手册指出:

  

记录了b,t和:命令以忽略前导空白,但没有提及尾随空白。

因此,这可行:

▶ sed -e :a -e s/foo/bar/3 -e ta <<< 'foofoofoofoofoo'
foofoobarbarbar

这也有效:

▶ sed '
    :a
    s/foo/bar/3
    ta
  ' <<< 'foofoofoofoofoo'
foofoobarbarbar

在任何情况下,脚本都在循环执行用foo替换第三个bar的操作,直到替换失败为止,脚本结束。请注意,t(测试)的使用仅在上一个s///命令替换了某些内容后才会分支。

要了解脚本在其每次循环迭代中的作用,这将很有帮助:

▶ sed -n -e :a -e s/foo/bar/3p -e ta <<< 'foofoofoofoofoo'
foofoobarfoofoo
foofoobarbarfoo
foofoobarbarbar

1 该答案的原始版本没有任何解释,尽管现在已经扩展了很多。奥古兹(Oguz)表示,他的偏好是让我在单独的答案中添加此信息。