【问题标题】:Substitute even or odd string matches替换偶数或奇数字符串匹配
【发布时间】:2019-06-07 23:53:29
【问题描述】:

直截了当:这不仅仅是匹配数字。

在文档中有很多**,我需要用12替换它们。

示例输入

**Lorem ipsum dolor **sit amet, consectetur adipisicing elit. ** 
Ad velit delectus ** quidem itaque eum **accusamus reprehenderit**
illo culpa **** praesentium** ea fugit ****hic in vel officiis, 
expedita sit **** et harum enim quaerat, **** ab corporis quo 
atque perspiciatis. Minima odit obcaecati** ** reiciendis, sed 
rerum ** labore. In fuga, ** aspernatur earum aliquid ** ****** 
**commodi delectus?

期望的输出

1Lorem ipsum dolor 2sit amet, consectetur adipisicing elit. 1 
Ad velit delectus 2 quidem itaque eum 1accusamus reprehenderit2
illo culpa 12 praesentium1 ea fugit 21hic in vel officiis, 
expedita sit 21 et harum enim quaerat, 21 ab corporis quo 
atque perspiciatis. Minima odit obcaecati2 1 reiciendis, sed 
rerum 2 labore. In fuga, 1 aspernatur earum aliquid 2 121 
2commodi delectus?

我唯一想到的是做一个循环替换找到的第一个匹配项 (s/\*{2}/1/),然后替换第二个匹配项 (s/\*{2}/2/),冲洗并重复,但因为我仍然不知道如何把它翻译成 shellscript,我想知道这是否可以通过几个管道 sed 命令来实现。

【问题讨论】:

  • echo "** ** ** ** ** ** **" | sed -E 's/\*\* \*\*/1 2/g; s/\*\*/1/'
  • 当然这个例子很模糊,但你没有抓住重点。我已经提到我有一个文档,其中散落着许多星号**。我需要更换那些。
  • 使用cat file | perl -0pe 's/\*\*(.*?)\*\*/1${1}2/gs' | sed 's/\*\*/1/'
  • @Fabián - 让它不那么模糊,那么。 stackoverflow.com/help/how-to-ask
  • @Cyrus 添加了更好的样本输入和所需的输出

标签: linux bash sed terminal pipe


【解决方案1】:

使用 GNU awk 进行多字符 RS 和 RT:

$ awk -v RS='[*][*]' '{ORS=(RT ? (ORS%2+1) : "")}1' file
1Lorem ipsum dolor 2sit amet, consectetur adipisicing elit. 1
Ad velit delectus 2 quidem itaque eum 1accusamus reprehenderit2
illo culpa 12 praesentium1 ea fugit 21hic in vel officiis,
expedita sit 21 et harum enim quaerat, 21 ab corporis quo
atque perspiciatis. Minima odit obcaecati2 1 reiciendis, sed
rerum 2 labore. In fuga, 1 aspernatur earum aliquid 2 121
2commodi delectus?

【讨论】:

    【解决方案2】:

    给定:

    $ cat file
    **Lorem ipsum dolor **sit amet, consectetur adipisicing elit. ** 
    Ad velit delectus ** quidem itaque eum **accusamus reprehenderit**
    illo culpa **** praesentium** ea fugit ****hic in vel officiis, 
    expedita sit **** et harum enim quaerat, **** ab corporis quo 
    atque perspiciatis. Minima odit obcaecati** ** reiciendis, sed 
    rerum ** labore. In fuga, ** aspernatur earum aliquid ** ****** 
    **commodi delectus?
    

    您最好的选择是perlawk

    $ perl -lpE 's/\*\*/$cnt++%2+1/ge' file
    

    这使用了 perl 的能力来评估带有替换的表达式。

    方法:

    1. 增加一个计数器$cnt++ 以获得(0,1,2,3,...)s/\*\*/$cnt++%2+1/ge 中的每个替换;
    2. 对该系列取模%2 得到(0,1,0,1,0...) 然后添加1 得到(1,2,1,2...) 用于每个交替替换。

    通过同样的方法,您可以将awkwhile 循环一起使用:

    $ awk '{while (sub(/\*\*/,cnt++%2+1))}1' file
    

    或者,您可以slurp整个文件(使用-0777),然后对**[stuff between maybe on multiline]** 进行全局替换并替换为1[stuff between maybe on multiline]2

    $ perl -0777 -lnE '$s=$_; $s=~s/\*\*([\s\S]*?)\*\*/1${1}2/g; 
                       END{$s=~s/\*\*/1/; say $s;}' file
    

    注意最后的$s=~s/\*\*/1/;,以防替换的总数为奇数。

    所有三种情况,打印:

    1Lorem ipsum dolor 2sit amet, consectetur adipisicing elit. 1 
    Ad velit delectus 2 quidem itaque eum 1accusamus reprehenderit2
    illo culpa 12 praesentium1 ea fugit 21hic in vel officiis, 
    expedita sit 21 et harum enim quaerat, 21 ab corporis quo 
    atque perspiciatis. Minima odit obcaecati2 1 reiciendis, sed 
    rerum 2 labore. In fuga, 1 aspernatur earum aliquid 2 121 
    2commodi delectus?
    

    【讨论】:

      【解决方案3】:

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

      sed -zE 's/(\*\*)([^*]*(\*[^*]+)*)\1/1\22/g' file
      

      Slurp 文件到内存中。在整个文件中全局匹配 **...** 并将前面的 ** 替换为 1 并将后面的 ** 替换为 2。

      【讨论】:

      • 如果总数为奇数,则需要添加最终替换以获得最终的**
      • @dawg 从 OP 中不清楚是否应该在前一个 1...2 中包含奇数个 * 或下一个/最后一个中间块的开始。该解决方案符合所提供的测试数据。
      【解决方案4】:
      tr '\n' $'\x1' |
      sed 's/\*\*/\x2/g' |
      sed 's/\x2\([^\x2]*\)\x2/1\12/g; s/\x02/1/' |
      tr $'\x1' '\n'
      
      1. 第一个 tr 将换行符替换为等于十六进制数 0x01 的不可读字符。
      2. 然后第一个sed 用两个** 替换十六进制0x02
      3. 然后任何模式0x02<anything>0x02 被替换为1<anything>2
      4. 最后一个未匹配的\x021 替换。
      5. 然后0x01 被换行符替换回来。

      tutorialspoint 的实时版本。

      * 替换 0x02 是因为,我们不能沿着 sed 's/**\(^**\)**/... 做某事,即。贪婪匹配一个字符串,直到找到多字符模式(或者我不知道该怎么做)。因此,我只需将两个字符 ** 替换为一个不可编辑的字符并处理它。这样我就可以正确处理单个*,例如**Lor*em ip*sum**

      如果您有带有-z 选项的 GNU sed,则不需要替换换行符。

      【讨论】:

        猜你喜欢
        • 2022-12-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多