【问题标题】:Using sed/awk to remove string from subsections使用 sed/awk 从小节中删除字符串
【发布时间】:2019-04-01 05:36:48
【问题描述】:

我有一个如下所示的文件:

bar
barfo
barfoo
barfooo
barfoooo

sample
sampleText1
sampleText2
sampleText3

prefix
prefixFooBar
prefixBarFoo

我想要 sed(或 awk)做的是从它的所有内容中删除引入一个部分的字符串,以便我最终得到:

bar
fo
foo
fooo
foooo

sample
Text1
Text2
Text3

prefix
FooBar
BarFoo

我尝试过使用

sed -e -i '/([[:alpha:]]+)/,/^$/ s/\1//g' file

但是由于“无效的反向引用”而失败。

【问题讨论】:

    标签: regex perl awk sed


    【解决方案1】:
    $ awk '{$0=substr($0,idx)} !idx{idx=length($0)+1} !NF{idx=0} 1' file
    bar
    fo
    foo
    fooo
    foooo
    
    sample
    Text1
    Text2
    Text3
    
    prefix
    FooBar
    BarFoo
    

    【讨论】:

    • 公平地说:就像 karakfa 做出假设一样,您也可以这样做。此解决方案仅在所有段落字符串都以主题词开头时才有效(这与示例一致,但与问题文本不一致 - karakfa 的答案也是如此)。如果不是这种情况,您的 awk 将不会删除 all 单词,甚至不会删除一次,而是会删除其他内容。 karakfa 处理得更好。
    • 有合理的假设,也有不合理的假设。当样本输入总是在特定位置包含像“sample”这样的通用字符串时,假设字符串“sample”总是出现在该位置是合理的。假设发布者希望将其视为字符串而不是正则表达式也是合理的,因此如果“sample”包含 RE 元字符,除非另有说明,否则它们应该按字面意思对待。假设“sample”总是字面意思是“sample”这个词,甚至总是包含非 RE 元字符是不合理的
    • 我同意它应该被视为字符串,但我不同意位置和匹配数。为什么?由于两个迹象:(1)问题文本本身清楚地说明了它,(2)他的sed 表达式包含g。过去,您一直热切地鼓励用户添加有关可能存在的缺陷的提示。 (事实上​​这就是我的sed 解决方案中第二句话的原因。)所以我想你应该在这里添加一个注释。
    • 他的 sed 表达是完全错误的。请澄清“问题文本本身清楚地说明了它” - 说明什么和在哪里?
    • 好吧,我同意,你不应该提。
    【解决方案2】:

    另一个awk

    $ awk '{sub(pre,"")}1; !NF{pre=""} !pre{pre=$1}' file
    
    bar
    fo
    foo
    fooo
    foooo
    
    sample
    Text1
    Text2
    Text3
    
    prefix
    FooBar
    BarFoo
    

    【讨论】:

    • 如果块中的第一个字符串包含 RE 元字符或数值等于 0,则会失败。
    • 如果第一个字符串包含空格,这也不起作用。
    【解决方案3】:
    perl -ple'
       if (!length($_)) { $re = "" }
       elsif (!length($re)) { $re = $_ }
       else { s/^\Q$re// }
    '
    

    注意事项:

    • 使用s/\Q$re//g 删除行中的任何位置,而不仅仅是删除前缀。
    • 即使标题行包含特殊字符(例如 \.*)也可以使用。
    • 即使一行中有多个空行,这也有效。
    • 请参阅Specifying file to process to Perl one-liner 了解完整用法。
    • 代码中的换行符是可选的(即可以删除)。

    【讨论】:

      【解决方案4】:

      一个 sed 解决方案,主要是为了说明 sed 可能不是这样做的最佳选择:

      $sed -E '1{h;b};/^$/{n;h;b};G;s/^(.*)(.*)\n\1$/\2/' infile
      bar
      fo
      foo
      fooo
      foooo
      
      sample
      Text1
      Text2
      Text3
      
      prefix
      FooBar
      BarFoo
      

      这是它的工作原理:

      1 {                   # on the first line
        h                   # copy pattern buffer to hold buffer
        b                   # skip to end of cycle
      }
      /^$/ {                # if line is empty
        n                   # get next line into pattern buffer
        h                   # copy pattern buffer to hold buffer
        b                   # skip to end of cycle
      }
      G                     # append hold buffer to pattern buffer
      s/^(.*)(.*)\n\1$/\2/  # substitute
      

      复杂的部分在替换中。在替换之前,模式缓冲区包含如下内容:

      prefixFooBar\nprefix
      

      现在替换匹配两个捕获组,第一个由\n 和字符串结尾之间的内容引用 - 我们从保持缓冲区中获取的前缀。

      然后替换为原始行的其余部分,去掉前缀。

      备注:

      • 这适用于 GNU sed;较旧的 GNU sed 版本可能需要 -r 而不是 -E
      • -E 只是为了方便;没有它,替换看起来像

        s/^\(.*\)\(.*\)\n\1$/\2/
        

        但仍然有效。

      • 对于 macOS sed,它适用于命令之间的文字换行符:

        sed -E '1{
        h
        b
        }
        /^$/{
        n
        h
        b
        }
        G
        s/^(.*)(.*)\n\2$/\2/' infile
        

      【讨论】:

        【解决方案5】:

        这是另一个sed 解决方案。仅当段落中的所有字符串都以主题行开始时才有效。

        sed -e '1{h;b};/^$/{n;h;b};H;g;s/\(.*\)\n\1//;p;g;s/\n.*//;h;d' file
        
        • 1 第一行:h 复制以保留空间,b 打印并继续下一行
        • /^$/ 空行:n 打印并阅读下一行,h 复制以保留空间,b 打印并继续
        • 所有(其他)行:
          • H 追加以使用换行符保留空格
          • g 复制保持空间到模式空间
          • s/\(.*\)\n\1// 从模式空间中删除第一行和第二行的内容
          • p打印图案空间
          • g 将保留空间复制到模式空间,以便从 H 中删除新内容
          • /\n.*//删除新内容
          • h 复制回以保留空间
          • d删除模式空间

        sed 对这些东西没用。

        您得到“无效的反向引用”,因为在 s 的搜索模式中没有组。

        【讨论】:

          【解决方案6】:

          awk 中的另一个:

          $ awk '{if(p&&match($0,"^" p))$0=substr($0,RLENGTH+1);else p=$0}1' file
          

          输出:

          bar
          fo
          foo
          fooo
          foooo
          
          sample
          Text1
          Text2
          Text3
          
          prefix
          FooBar
          BarFoo
          

          【讨论】:

            【解决方案7】:

            这是另一个 awk 解决方案:

            awk '{gsub(s,"")}1; s==""||!NF{s=$0}' file
            

            优点:

            • 匹配被替换,无论它们在哪里
            • 所有匹配被替换
            • 标题可能评估为0/false
            • 标题行可能包含空格

            缺点:

            • 标题行不能包含正则表达式元字符

            【讨论】:

              【解决方案8】:

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

              sed 'G;s/^\(.\+\)\(.*\)\n\1$/\2/;t;s/\n.*//;h' file
              

              将前一个键(如果是第一行,则不附加)附加到当前行。如果匹配,则删除键和上一个键,打印当前行并重复。否则密钥不匹配,删除旧的附加密钥,将新密钥存储在保留空间并打印新密钥。

              【讨论】:

                猜你喜欢
                • 2012-04-09
                • 2011-12-21
                • 2018-04-11
                • 2021-03-22
                • 2011-04-01
                • 2017-05-28
                • 2019-10-20
                • 1970-01-01
                • 2013-01-07
                相关资源
                最近更新 更多