【问题标题】:Replace every nth occurrence of a string替换每第 n 次出现的字符串
【发布时间】:2018-08-11 17:18:39
【问题描述】:

假设我有一个文本文件。我想使用 sed 替换文本文件中每第 n 次出现的字符串。如果我有一个字符串:

is this just real life or is this just fantasy or is it just me

对于每 3 次出现的 'is',替换为 'hat',将给出输出

is this just real life or is this just fantasy or hat it just me

我尝试使用其他 StackOverflow 问题中提供的答案,但没有一个可以替换所有问题。

【问题讨论】:

  • 是的,抱歉,问题不清楚。

标签: bash unix awk sed replace


【解决方案1】:

在其他答案中确实被过度考虑了。 Sed 能够做到这一点,而且不是那么冗长。

给定一个字符串:

"foo foo foo foo foo"

我们可以通过管道输入以下 sed 表达式,针对确切的出现:

sed -e 's/foo/bar/3'

第三个匹配项被替换:

echo "foo foo foo foo foo" | sed -e 's/foo/bar/3'
foo foo bar foo foo

您可以通过添加反向表达式作为结束范围和全局替换 (g) 来定位范围。例如,替换第二个到第四个:

echo "foo foo foo foo foo" | sed -e 's/foo/bar/g2' |sed -e 's/bar/foo/g4'
foo bar bar bar foo

现在这有点草率,可以用 '-r' 开关浓缩成一个多表达式 sed 语句:

echo "foo foo foo foo foo" | sed -re 's/foo/bar/g2' -e 's/bar/foo/g4'
foo bar bar bar foo

第一个表达式将所有出现的“foo”替换为“bar”,从第二次出现的“foo”开始。第二个表达式将所有出现的 'bar' 替换为 'foo',从第四次出现的 'bar' 开始。

版本: GNU sed 4.2.1 版

【讨论】:

    【解决方案2】:

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

    sed -r 's/is/\n&/g;/\n/!b;G;:a;;s/$/#/;s/#{3}$//;/\n$/s/\nis/\nhat/;s/\n//;/\n.*\n/ba;P;s/^.*\n//;h;d' file
    

    我对这个答案不以为然。详细解释见here

    【讨论】:

      【解决方案3】:

      你可以试试这个 gnu sed

      sed -E ':A;s/\bis/hat\n/3;x;G;h;s/(.*)\n.*/\1/;x;s/.*\n//;/\bis/bA;x;G;s/\n//g' infile
      

      【讨论】:

        【解决方案4】:

        如果您使用sed 扩展正则表达式(-E 而不是-e),您可以将问题重新表述如下。而不是匹配“每三次出现的is”,而是认为您正在处理更长的字符串匹配

        echo "is this just real life or is this just fantasy or is it just me" | sed -E 's/(is)(.*)(is)(.*)(is)/\1\2\3\4\hat/'

        这适用于您的示例,但也说明您的问题不完整;你想跨行匹配,还是只在行内匹配?您是否要将输入缓冲区视为一条长行并替换其中的每三个“is”,还是要替换每行上的第三个“is”?所以这个例子是说明性的,但并不完整,sed 的完整答案只会有一些实际代码会尽量避免的其他有趣之处。

        正则表达式方法会产生疯狂且难以阅读的代码。如果由于某种原因您无法运行awk,您可能也没有-E 扩展正则表达式标志。如果是我,我会使用 awk。

        【讨论】:

        • 那个正则表达式不起作用,因为.* 是贪婪的(并且会匹配is)。所以is.*is.*is 将从一行上的第一个is 匹配到最后一个,只要至少有三个,因此只会更改最后一个is,而不是每隔三个。 Sed 没有实现非贪婪匹配,afaik。
        【解决方案5】:

        使用 awk

        $ awk '{for(i=1; i<=NF; i++) if($i=="is") if(++count%3==0) $i="hat"}1' file
        is this just real life or is this just fantasy or hat it just me
        

        【讨论】:

        • 有什么方法可以使用sed
        【解决方案6】:

        awk解决方案:

        awk -v RS='[[:blank:]]+' 'NR % 3 == 0{ $0 = toupper($0) }
                                 { printf "%s%s", (NR == 1? "": OFS), $0 }' file
        

        输出:

        this this THIS this this THIS this
        

        【讨论】:

          猜你喜欢
          • 2018-03-24
          • 2016-05-07
          • 2018-09-08
          • 1970-01-01
          • 2013-02-20
          • 1970-01-01
          • 2021-12-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多