【问题标题】:Non greedy (reluctant) regex matching in sed?sed中的非贪婪(不情愿)正则表达式匹配?
【发布时间】:2010-11-09 08:14:28
【问题描述】:

我正在尝试使用 sed 清理 URL 行以仅提取域。

所以来自:

http://www.suepearson.co.uk/product/174/71/3816/

我想要:

http://www.suepearson.co.uk/

(无论有无斜杠,都无所谓)

我试过了:

 sed 's|\(http:\/\/.*?\/\).*|\1|'

and(转义非贪婪量词)

sed 's|\(http:\/\/.*\?\/\).*|\1|'

但我似乎无法让非贪婪量词 (?) 工作,所以它总是匹配整个字符串。

【问题讨论】:

  • 附注:如果用“|”分隔正则表达式,则无需转义“/”。事实上,大多数人用“|”分隔而不是“/”以避免“栅栏”。
  • @AttishOculus 在 sed 的替代表达式中,“s”之后的第一个字符是分隔符。因此 's^foo^bar^' 或 's!foo!bar!'也可以工作
  • 对于扩展的正则表达式,使用sed -E 's...。尽管如此,没有不情愿的运营商。
  • 不回答问题标题,但在这种特定情况下,简单的cut -d'/' -f1-3 有效。

标签: regex sed pcre greedy regex-greedy


【解决方案1】:

无论是基本的还是扩展的 Posix/GNU 正则表达式都不能识别非贪婪量词;你需要一个以后的正则表达式。幸运的是,这种上下文的 Perl 正则表达式很容易获得:

perl -pe 's|(http://.*?/).*|\1|'

【讨论】:

  • 就地使用选项-pi -e
  • Holy smokes I can't believe that works :-) 唯一糟糕的是我的脚本现在依赖于 Perl :-( 从好的方面来说,几乎每个 Linux 发行版都已经有了 Perl,所以可能没有一个问题:-)
  • @Freedom_Ben: IIRC perl 是 POSIX 必需的
  • @dolphus333:“无论是基本的还是扩展的 Posix/GNU 正则表达式都不能识别非贪婪量词”的意思是“你不能在 sed 中使用非贪婪量词”。
  • @Sérgio 这就是你做请求的方式,这在sed 中是不可能的,使用与sed 基本相同的语法
【解决方案2】:

在这种特定情况下,您可以在不使用非贪婪正则表达式的情况下完成工作。

试试这个非贪婪的正则表达式[^/]* 而不是.*?

sed 's|\(http://[^/]*/\).*|\1|g'

【讨论】:

  • 如何使用这种技术使 sed 匹配非贪婪的短语?
  • 不幸的是你不能;见chaos’s answer
  • 非常感谢...因为 perl 在许多 Linux 发行版的默认安装基础中不再存在!
  • @DanielH 实际上,可以根据要求使用此技术非贪婪地匹配短语。以足够的精度编写任一模式可能需要一些痛苦。例如。在解析 URL 查询中的键值分配时,可能需要使用 ([^&=#]+)=([^&#]*) 来查找分配。有些情况肯定不会以这种方式工作,例如当解析其主机部分的 URL 和带有最后斜杠的路径名时,假定从捕获中排除可选:^(http:\/\/.+?)/?$
【解决方案3】:

使用 sed,我通常通过搜索除分隔符之外的任何内容直到分隔符来实现非贪婪搜索:

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*\)/.*;\1;p'

输出:

http://www.suon.co.uk

这是:

  • 不输出-n
  • 搜索、匹配模式、替换和打印s/<pattern>/<replace>/p
  • 使用; 搜索命令分隔符而不是/ 以便于键入s;<pattern>;<replace>;p
  • 记住括号之间的匹配 \( ... \),稍后可使用 \1,\2...
  • 匹配http://
  • 后跟括号中的任何内容[][ab/] 表示ab/
  • [] 中的第一个 ^ 表示 not,所以后面是 [] 中的内容以外的任何内容
  • 所以[^/] 表示除/ 之外的任何字符
  • * 是重复上一组,所以[^/]* 表示除/ 之外的字符。
  • 到目前为止sed -n 's;\(http://[^/]*\) 表示搜索并记住http://,后跟除/ 之外的任何字符并记住您找到的内容
  • 我们要搜索到域的末尾,所以在下一个 / 上停止,所以在末尾添加另一个 /sed -n 's;\(http://[^/]*\)/' 但我们想要匹配域之后的其余行,所以添加 @987654353 @
  • 现在在第 1 组 (\1) 中记住的匹配是域,因此将匹配的行替换为保存在组 \1 中的内容并打印:sed -n 's;\(http://[^/]*\)/.*;\1;p'

如果您还想在域之后包含反斜杠,请在组中再添加一个反斜杠以记住:

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*/\).*;\1;p'

输出:

http://www.suon.co.uk/

【讨论】:

  • 关于最近的编辑:括号是一种括号字符,因此将它们称为括号并没有错,特别是如果您像作者那样在单词后面加上实际字符。此外,它是某些文化中的首选用法,因此将其替换为您自己文化中的首选用法似乎有点粗鲁,尽管我确信这不是编辑的本意。就个人而言,我认为最好使用纯描述性的名称,如圆括号方括号尖括号
  • 是否可以用字符串替换分隔符?
【解决方案4】:

sed 中模拟惰性(非贪婪)量词

以及所有其他正则表达式风格!

  1. 查找第一次出现的表达式:

    • POSIX ERE(使用-r 选项)

      正则表达式:

        (EXPRESSION).*|.
      

      Sed:

        sed -r ‍'s/(EXPRESSION).*|./\1/g' # Global `g` modifier should be on
      

      示例(查找第一个数字序列)Live demo

        $ sed -r 's/([0-9]+).*|./\1/g' <<< 'foo 12 bar 34'
      
        12
      

      它是如何工作的

      这个正则表达式受益于|。在每个位置,引擎都尝试选择最长的匹配项(这是一个 POSIX 标准,随后还有几个其他引擎),这意味着它与 . 匹配,直到找到与 ([0-9]+).* 的匹配项。但是顺序也很重要。

      由于设置了全局标志,引擎会尝试逐个字符地继续匹配,直到输入字符串的末尾或我们的目标。只要第一个也是唯一一个交替左侧的捕获组匹配(EXPRESSION),其余行也会立即消耗.*。我们现在在第一个捕获组中保留我们的值。

    • POSIX BRE

      正则表达式:

        \(\(\(EXPRESSION\).*\)*.\)*
      

      Sed:

        sed 's/\(\(\(EXPRESSION\).*\)*.\)*/\3/'
      

      示例(查找第一个数字序列):

        $ sed 's/\(\(\([0-9]\{1,\}\).*\)*.\)*/\3/' <<< 'foo 12 bar 34'
      
        12
      

      这个类似于 ERE 版本,但不涉及交替。就这样。在每个位置,引擎都会尝试匹配一个数字。

      如果找到,则消耗并捕获其他后续数字,否则将立即匹配行的其余部分,因为* 表示 更多或零它跳过第二个捕获组 \(\([0-9]\{1,\}\).*\)* 并到达一个点 . 以匹配单个字符,然后此过程继续。

  2. 查找第一次出现的分隔表达式:

    这种方法将匹配第一次出现的分隔字符串。我们可以称之为字符串块。

    sed 's/\(END-DELIMITER-EXPRESSION\).*/\1/; \
         s/\(\(START-DELIMITER-EXPRESSION.*\)*.\)*/\1/g'
    

    输入字符串:

    foobar start block #1 end barfoo start block #2 end
    

    -EDE:end

    -SDE:start

    $ sed 's/\(end\).*/\1/; s/\(\(start.*\)*.\)*/\1/g'
    

    输出:

    start block #1 end
    

    第一个正则表达式 \(end\).* 匹配并捕获第一个结束分隔符 end 和替换都与最近捕获的字符匹配 是结束分隔符。在这个阶段我们的输出是:foobar start block #1 end

    然后将结果传递给与上述 POSIX BRE 版本相同的第二个正则表达式 \(\(start.*\)*.\)*。它匹配单个字符 如果起始分隔符start 不匹配,否则它匹配并捕获起始分隔符并匹配其余字符。


直接回答你的问题

使用方法#2(分隔表达式),您应该选择两个适当的表达式:

  • EDE:[^:/]\/

  • SDE:http:

用法:

$ sed 's/\([^:/]\/\).*/\1/g; s/\(\(http:.*\)*.\)*/\1/' <<< 'http://www.suepearson.co.uk/product/174/71/3816/'

输出:

http://www.suepearson.co.uk/

注意:这不适用于相同的分隔符。

【讨论】:

  • 3) 在推荐 regex101 之类的站点进行演示时,请添加注意,由于语法和功能差异,它并不总是适合 cli 工具
  • @Sundeep 谢谢。我把所有这些引号都变成了单引号。我还考虑了要提到的最左边最长的匹配规则。然而,在sed 和所有其他遵循相同标准的引擎中,顺序确实很重要。所以echo 'foo 1' | sed -r 's/.|([0-9]+).*/\1/g' 没有匹配但echo 'foo 1' | sed -r 's/([0-9]+).*|./\1/g' 有。
  • @Sundeep 分隔表达式的解决方法也不适用于我添加了注释的 相同 开始和结束分隔符。
  • 关于当不同的交替从同一位置开始并具有相同长度时会发生什么的重要一点,猜测它会像其他引擎一样遵循左右顺序..如果手册中有描述,需要查找/跨度>
  • 这里有一个奇怪的案例:stackoverflow.com/questions/59683820/…
【解决方案5】:

sed 不支持“非贪婪”运算符。

您必须使用“[]”运算符将“/”从匹配项中排除。

sed 's,\(http://[^/]*\)/.*,\1,'

附:不需要反斜杠“/”。

【讨论】:

  • 并非如此。如果分隔符可能是许多可能的字符之一(比如一串数字),那么您的否定匹配可能会变得越来越复杂。这很好,但有一个选项让 .* 不贪婪肯定会很好
  • 这个问题比较笼统。这些解决方案适用于 URL,但不适用于(例如)我去除尾随零的用例。 s/([[:digit:]]\.[[1-9]]*)0*/\1/ 显然不适用于 1.20300。不过,由于最初的问题是关于 URL 的,因此应该在接受的答案中提及它们。
【解决方案6】:

sed - non greedy matching by Christoph Sieghart

在 sed 中获得非贪婪匹配的技巧是匹配所有字符,不包括终止匹配的字符。我知道,很简单,但我在这上面浪费了宝贵的时间,而且 shell 脚本毕竟应该是快速和简单的。所以万一其他人可能需要它:

贪心匹配

% echo "<b>foo</b>bar" | sed 's/<.*>//g'
bar

非贪心匹配

% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar

【讨论】:

    【解决方案7】:

    多个字符的非贪婪解决方案

    这个帖子真的很老了,但我认为人们仍然需要它。 假设您想杀死所有东西,直到第一次出现HELLO。你不能说[^HELLO]...

    因此,一个不错的解决方案涉及两个步骤,假设您可以在输入中保留一个您不期望出现的唯一单词,例如 top_sekrit

    在这种情况下,我们可以:

    s/HELLO/top_sekrit/     #will only replace the very first occurrence
    s/.*top_sekrit//        #kill everything till end of the first HELLO
    

    当然,通过更简单的输入,您可以使用更小的单词,甚至可以使用单个字符。

    HTH!

    【讨论】:

    • 为了使它更好,在你不能指望不使用的字符的情况下有用:1. 用真正未使用的 WORD 替换那个特殊字符,2. 用特殊字符替换结束序列,3. 做以特殊字符结尾的搜索, 4. 将特殊字符替换回来, 5. 将特殊 WORD 替换回来。例如,您希望 之间有一个贪心运算符:
    • 这里的例子:echo "Find:fir~st
      yes
      sec~ond" | sed -e "s,~,VERYSPECIAL,g" -e "s,,~,g" -e "s,.*查找:([^~]*).*,\1 ," -e "s,\~,," -e "s,VERYSPECIAL,~,"
    • 我同意。很好的解决方案。我将评论改写为:如果您不能依赖 ~ 未被使用,请先使用 s/~/VERYspeciaL/g 替换其当前出现,然后执行上述技巧,然后使用 s/VERYspeciaL/~/g 返回原始 ~
    • 我倾向于喜欢使用稀有的“变量”来处理这类事情,所以我会使用&lt;$$&gt; 而不是`(因为$$ 在shell 中扩展为您的进程ID ,尽管您必须使用双引号而不是单引号,这可能会破坏正则表达式的其他部分),或者,如果 unicode 可用,则类似于 &lt;∈∋&gt;
    • 在某些时候你必须问自己为什么你不只是使用perlpython 或其他一些语言。 perl 在一行中以一种不那么脆弱的方式做到这一点......
    【解决方案8】:

    这可以使用 cut 来完成:

    echo "http://www.suepearson.co.uk/product/174/71/3816/" | cut -d'/' -f1-3
    

    【讨论】:

      【解决方案9】:

      另一种不使用正则表达式的方法是使用字段/分隔符方法,例如

      string="http://www.suepearson.co.uk/product/174/71/3816/"
      echo $string | awk -F"/" '{print $1,$2,$3}' OFS="/"
      

      【讨论】:

        【解决方案10】:

        sed 当然有它的位置,但这不是其中之一!

        正如 Dee 所指出的:只需使用 cut。在这种情况下,它更简单、更安全。这是一个示例,我们使用 Bash 语法从 URL 中提取各种组件:

        url="http://www.suepearson.co.uk/product/174/71/3816/"
        
        protocol=$(echo "$url" | cut -d':' -f1)
        host=$(echo "$url" | cut -d'/' -f3)
        urlhost=$(echo "$url" | cut -d'/' -f1-3)
        urlpath=$(echo "$url" | cut -d'/' -f4-)
        

        给你:

        protocol = "http"
        host = "www.suepearson.co.uk"
        urlhost = "http://www.suepearson.co.uk"
        urlpath = "product/174/71/3816/"
        

        如您所见,这是一种更灵活的方法。

        (全部归功于 Dee)

        【讨论】:

          【解决方案11】:
          sed 's|(http:\/\/[^\/]+\/).*|\1|'
          

          【讨论】:

          • 如果你使用“|”作为您的分隔符,无需转义“/”。
          【解决方案12】:

          sed -E 将正则表达式解释为扩展(现代)正则表达式

          更新:MacOS X 上的 -E,GNU sed 上的 -r。

          【讨论】:

          • 不,它没有...至少不是 GNU sed。
          • 更广泛地说,-E 是 BSD sed 和 OS X 独有的。指向手册页的链接。 @stephancheg 的更正中指出,-r 确实为GNU sed 带来了扩展的正则表达式。在 'nix 发行版中使用已知可变性的命令时要小心。我很难学到这一点。
          • 如果你想使用sed,这是正确的答案,并且最适用于最初的问题。
          • GNU sed 的-r 选项仅更改转义规则,根据信息文件的Appendix A Extended regular expressions 和一些快速测试;它实际上并没有添加非贪婪限定符(至少从 GNU sed version 4.2.1 开始。)
          • GNU sed 在一段时间内将 -E 识别为未记录的选项,但在 release 4.2.2.177 中,文档已更新以反映这一点,因此 -E 现在对两者都很好。
          【解决方案13】:

          仍然有希望使用纯 (GNU) sed 来解决这个问题。尽管这在某些情况下不是通用解决方案,但您可以使用“循环”来消除字符串中所有不必要的部分,如下所示:

          sed -r -e ":loop" -e 's|(http://.+)/.*|\1|' -e "t loop"
          
          • -r:使用扩展正则表达式(用于 + 和非转义括号)
          • “:loop”:定义一个名为“loop”的新标签
          • -e:向 sed 添加命令
          • “t loop”:如果替换成功,则跳转回标签“loop”

          这里唯一的问题是它还会剪切最后一个分隔符('/'),但如果你真的需要它,你仍然可以在“循环”完成后简单地把它放回去,只需在末尾附加这个额外的命令上一条命令行:

          -e "s,$,/,"
          

          【讨论】:

            【解决方案14】:

            因为您特别声明您正在尝试使用 sed(而不是 perl、cut 等),所以请尝试分组。这规避了可能无法识别的非贪婪标识符。第一组是协议(即“http://”、“https://”、“tcp://”等)。第二组是域:

            回声“http://www.suon.co.uk/product/1/7/3/”| sed "s|^\(.*//\)\([^/]*\).*$|\1\2|"

            如果您不熟悉分组,请开始here

            【讨论】:

              【解决方案15】:

              我知道这是一个旧条目,但有人可能会觉得它很有用。 由于完整的域名总长度不得超过 253 个字符,请将 .* 替换为 .\{1, 255\}

              【讨论】:

                【解决方案16】:

                这是如何使用 sed 稳健地执行多字符串的非贪婪匹配。假设您想将每个foo...bar 更改为&lt;foo...bar&gt;,例如这个输入:

                $ cat file
                ABC foo DEF bar GHI foo KLM bar NOP foo QRS bar TUV
                

                应该变成这样的输出:

                ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV
                

                为此,您将 foo 和 bar 转换为单个字符,然后在它们之间使用这些字符的否定:

                $ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/g; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
                ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV
                

                在上面:

                1. s/@/@A/g; s/{/@B/g; s/}/@C/g 正在将 {} 转换为输入中不存在的占位符字符串,因此这些字符可用于将 foobar 转换为。
                2. s/foo/{/g; s/bar/}/g 正在将 foobar 分别转换为 {}
                3. s/{[^{}]*}/&lt;&amp;&gt;/g 正在执行我们想要的操作 - 将 foo...bar 转换为 &lt;foo...bar&gt;
                4. s/}/bar/g; s/{/foo/g 正在将 {} 转换回 foobar
                5. s/@C/}/g; s/@B/{/g; s/@A/@/g 正在将占位符字符串转换回其原始字符。

                请注意,上述内容不依赖于输入中不存在的任何特定字符串,因为它在第一步中生成此类字符串,也不关心您要匹配的任何特定正则表达式的出现,因为您可以使用@987654345 @ 在表达式中根据需要多次以隔离您想要的实际匹配和/或使用 seds 数字匹配运算符,例如只替换第二次出现:

                $ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/2; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
                ABC foo DEF bar GHI <foo KLM bar> NOP foo QRS bar TUV
                

                【讨论】:

                  【解决方案17】:

                  还没有看到这个答案,所以你可以通过vivim 做到这一点:

                  vi -c '%s/\(http:\/\/.\{-}\/\).*/\1/ge | wq' file &>/dev/null
                  

                  这将在全局范围内运行vi:%s 替换(尾随g),如果未找到模式(e)则避免引发错误,然后将生成的更改保存到磁盘并退出。 &amp;&gt;/dev/null 可防止 GUI 在屏幕上短暂闪烁,这可能很烦人。

                  我喜欢有时将vi 用于超级复杂的正则表达式,因为 (1) perl dead 即将死去,(2) vim 有一个 非常 高级的正则表达式引擎,并且(3) 在我的日常使用编辑文档中,我已经非常熟悉vi 正则表达式。

                  【讨论】:

                    【解决方案18】:

                    由于 PCRE 也被标记在这里,我们可以通过在正则表达式 .*? 中使用非惰性匹配来使用 GNU grep,这将匹配与 .* 相对的第一个最近匹配(这真的很贪婪,直到最后一次出现匹配)。

                    grep -oP '^http[s]?:\/\/.*?/' Input_file
                    

                    说明: 在此处使用grepoP 选项,其中-P 负责在此处启用PCRE 正则表达式。在grep 的主程序中提到正则表达式,它匹配从http/https 开始,然后是://,直到下一次出现/,因为我们使用了.*?,它会在(http/https:/ /)。它只会打印匹配的部分。

                    【讨论】:

                      【解决方案19】:
                      echo "/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|'
                      

                      别打扰,我在另一个论坛上找到的:)

                      【讨论】:

                      • 所以你得到贪婪匹配:/home/one/two/three/,如果你添加另一个/,比如/home/one/two/three/four/myfile.txt,你也会贪婪匹配four/home/one/two/three/four,问题是关于非贪婪
                      【解决方案20】:

                      sed 's|\(http:\/\/www\.[a-z.0-9]*\/\).*|\1| 也可以使用

                      【讨论】:

                        【解决方案21】:

                        您可以使用两步方法和 awk 来完成以下操作:

                        A=http://www.suepearson.co.uk/product/174/71/3816/  
                        echo $A|awk '  
                        {  
                          var=gensub(///,"||",3,$0) ;  
                          sub(/\|\|.*/,"",var);  
                          print var  
                        }'  
                        

                        输出: http://www.suepearson.co.uk

                        希望有帮助!

                        【讨论】:

                          【解决方案22】:

                          另一个 sed 版本:

                          sed 's|/[:alnum:].*||' file.txt
                          

                          它匹配/ 后跟一个字母数字字符(所以不是另一个正斜杠)以及直到行尾的其余字符。之后它什么都没有替换它(即删除它。)

                          【讨论】:

                          • 我猜应该是"[[:alnum:]]",而不是"[:alphanum:]"
                          【解决方案23】:

                          @Daniel H(关于你对 andcoz 回答的评论,虽然很久以前):删除尾随零适用于

                          s,([[:digit:]]\.[[:digit:]]*[1-9])[0]*$,\1,g

                          这是关于明确定义匹配条件...

                          【讨论】:

                            【解决方案24】:

                            您还应该考虑没有匹配分隔符的情况。您是否要输出该行。如果没有匹配,我这里的示例不会输出任何内容。

                            您需要最多第 3 个 / 的前缀,因此选择两次不包含 / 和跟随 / 的任意长度的字符串,然后选择不包含 / 的任意长度的字符串,然后匹配 / 跟随任意字符串,然后打印选择。这个想法适用于任何单个字符分隔符。

                            echo http://www.suepearson.co.uk/product/174/71/3816/ | \
                              sed -nr 's,(([^/]*/){2}[^/]*)/.*,\1,p'
                            

                            使用 sed 命令,您可以快速删除前缀或选择分隔符,例如:

                            echo 'aaa @cee: { "foo":" @cee: " }' | \
                              sed -r 't x;s/ @cee: /\n/;D;:x'
                            

                            这比一次吃炭要快得多。

                            如果之前匹配成功则跳转到标签。在第一个分隔符之前的 / 处添加 \n。最多删除第一个\n。如果添加了\n,则跳转到末尾并打印。

                            如果有 start 和 end delims,删除 end delims 很容易,直到你到达你想要的第 n-2 个元素然后做 D 技巧,在 end delim 之后删除,如果不匹配则跳转到删除,在 start 之前删除delim 和打印。这仅在开始/结束分隔符成对出现时才有效。

                            echo 'foobar start block #1 end barfoo start block #2 end bazfoo start block #3 end goo start block #4 end faa' | \
                              sed -r 't x;s/end//;s/end/\n/;D;:x;s/(end).*/\1/;T y;s/.*(start)/\1/;p;:y;d'
                            

                            【讨论】:

                              【解决方案25】:

                              如果你可以使用 gnu grep,那么可以使用 perl 正则表达式:

                              grep -Po '^https?://([^/]+)(?=)' <<< 'http://www.suepearson.co.uk/product/174/71/3816/'
                              http://www.suepearson.co.uk
                              

                              或者,在域使用之后获取所有内容

                              grep -Po '^https?://([^/]+)\K.*' <<< 'http://www.suepearson.co.uk/product/174/71/3816/'
                              /product/174/71/3816/
                              

                              【讨论】:

                                【解决方案26】:

                                以下解决方案适用于匹配/使用多重存在(链式;串联;复合)HTML 或其他标签。例如,我想编辑 HTML 代码以删除串联出现的 &lt;span&gt; 标记。

                                问题:正则sed正则表达式贪婪地匹配从第一个到最后一个的所有标签。

                                解决方案: 非贪婪模式匹配(根据本主题其他地方的讨论;例如 https://stackoverflow.com/a/46719361/1904943)。

                                示例:

                                echo '<span>Will</span>This <span>remove</span>will <span>this.</span>remain.' | \
                                sed 's/<span>[^>]*>//g' ; echo
                                
                                This will remain.
                                

                                说明:

                                • s/&lt;span&gt;:找到&lt;span&gt;
                                • [^&gt;] :后跟任何不是&gt;
                                • *&gt; :直到找到 &gt;
                                • //g :将任何此类字符串替换为空。

                                附录

                                我试图清理 URL,但我遇到了匹配/排除单词的困难 - href - 使用上述方法。我简要地查看了负面环视 (Regular expression to match a line that doesn't contain a word),但这种方法似乎过于复杂,并没有提供令人满意的解决方案。

                                我决定将href 替换为`(反引号),进行正则表达式替换,然后将` 替换为href

                                示例(为便于阅读在此处格式化):

                                printf '\n
                                <a aaa h href="apple">apple</a>
                                <a bbb "c=ccc" href="banana">banana</a>
                                <a class="gtm-content-click"
                                   data-vars-link-text="nope"
                                   data-vars-click-url="https://blablabla"
                                   data-vars-event-category="story"
                                   data-vars-sub-category="story"
                                   data-vars-item="in_content_link"
                                   data-vars-link-text
                                   href="https:example.com">Example.com</a>\n\n' |
                                sed 's/href/`/g ;
                                     s/<a[^`]*`/\n<a href/g'
                                
                                <a href="apple">apple</a> 
                                <a href="banana">banana</a> 
                                <a href="https:example.com">Example.com</a>
                                

                                解释:基本同上。这里,

                                • s/href/` :将 href 替换为 `(反引号)
                                • s/&lt;a : 查找 URL 的开头
                                • [^`] :后跟任何不是 ` 的内容(反引号)
                                • *` :直到找到 `
                                • /&lt;a href/g :将找到的每个替换为 &lt;a href

                                【讨论】:

                                  【解决方案27】:

                                  不幸的是,如前所述,这在 sed 中不受支持。 为了克服这个问题,我建议使用次优(实际上甚至更好),使用类似 vim sed 的功能。

                                  .bash-profile中定义

                                  vimdo() { vim $2 --not-a-term -c "$1"  -es +"w >> /dev/stdout" -cq!  ; }
                                  

                                  这将创建无头 vim 来执行命令。

                                  现在你可以做例如:

                                  echo $PATH | vimdo "%s_\c:[a-zA-Z0-9\\/]\{-}python[a-zA-Z0-9\\/]\{-}:__g" -

                                  过滤掉$PATH中的python。

                                  在 vimdo 中使用 - 从管道获取输入。

                                  虽然大部分语法是相同的。 Vim 具有更高级的功能,使用\{-} 是非贪婪匹配的标准。见help regexp

                                  【讨论】:

                                    猜你喜欢
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 2011-08-29
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 1970-01-01
                                    相关资源
                                    最近更新 更多