代码之家  ›  专栏  ›  技术社区  ›  Joel

sed中的非贪婪(不情愿)正则表达式匹配?

  •  502
  • Joel  · 技术社区  · 17 年前

    所以从:

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

    我想要:

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

    (无论是否带有尾随斜线,都没关系)

    我试过:

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

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

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

    27 回复  |  直到 6 年前
        1
  •  377
  •   chaos    15 年前

    无论是基本的还是扩展的Posix/GUN正则表达式都不能识别非贪婪量词;您需要稍后的正则表达式。幸运的是,在这种情况下,Perl正则表达式很容易获得:

    perl -pe 's|(http://.*?/).*|\1|'
    
        2
  •  217
  •   Gumbo    11 年前

    [^/]* 而不是 .*? :

    sed 's|\(http://[^/]*/\).*|\1|g'
    
        3
  •  107
  •   Alan Moore Chris Ballance    12 年前

    使用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/] 这意味着要么 a 或 b 或 /
    • 第一 ^ 在 [] 手段 not ,所以后面除了里面的东西什么都没有 []
    • [^/] 意思是除了 / 字符
    • * 就是重复上一组 [^/]* 指除以下字符之外的字符 / .
    • 迄今为止 sed -n 's;\(http://[^/]*\) 意味着搜索和记忆 http:// / 记住你发现了什么
    • 我们想搜索到域的末尾,所以停止下一个 / 所以再添加一个 / 最后: sed -n 's;\(http://[^/]*\)/' 但我们想匹配域后的其余行,所以添加 .*
    • 现在,第一组的比赛被记住了( 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
  •  36
  •   andcoz    17 年前

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

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

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

      • -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]+).* 但秩序也很重要。

        enter image description here

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

      • POSIX纤维

        正则表达式:

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

        Sed:

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

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

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

        这个版本类似于ERE版本,但不涉及任何更改。这就是全部。在每个位置,引擎都试图匹配一个数字。

        enter image description here

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

    2. 查找a的首次出现 定界的 表达式:

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

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

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

      -艾德: end

      -SDE: start

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

      输出:

      start block #1 end
      

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

      enter image description here

      \(\(start.*\)*.\)* 这与上面的POSIX BRE版本相同。它匹配一个字符

      enter image description here


    直接回答您的问题

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

    • 艾德: [^:/]\/

    • http:

    用途:

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

    输出:

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

        5
  •  25
  •   revo shanwije    10 年前

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

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

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

        6
  •  20
  •   markasoftware    8 年前

    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
  •  16
  •   Lambda Fairy    15 年前

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

    这条线真的很旧,但我想人们仍然需要它。 假设你想杀死一切,直到第一次发生 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
    

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

    嗨!

        8
  •  14
  •   gresolio    9 年前

    这可以使用cut完成:

    echo "http://www.suepearson.co.uk/product/174/71/3816/" | cut -d'/' -f1-3
    
        9
  •  9
  •   ghostdog74    17 年前

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

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

    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
  •  4
  •   mTUX    10 年前
    sed 's|(http:\/\/[^\/]+\/).*|\1|'
    
        12
  •  3
  •   Lucero    17 年前

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

        13
  •  3
  •   stepancheg    15 年前

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

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

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

    -e "s,$,/,"
    
        14
  •  2
  •   Community Mohan Dere    9 年前

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

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

    here .

        15
  •  1
  •   Iain Henderson    15 年前

    由于完整域名的总长度不得超过253个字符,请进行替换。*与\{1, 255\}

        16
  •  1
  •   Ed Morton    8 年前

    这就是如何使用sed稳健地进行多字符串的非贪婪匹配。假设你想改变每一个 foo...bar 到 <foo...bar> 例如,这个输入:

    $ 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
    

    $ 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 正在转换 { 和 } 转换为输入中不存在的占位符字符串,以便这些字符可以进行转换 foo 和 bar 到。
    2. s/foo/{/g; s/bar/}/g 正在转换 foo 和 酒吧 到 { 和 } 分别
    3. s/{[^{}]*}/<&>/g 正在执行我们想要的操作-转换 foo。..bar 到 <foo。..bar>
    4. s/}/bar/g; s/{/foo/g 正在转换 { 和 } 回到 foo 和 酒吧 .
    5. s/@C/}/g; s/@B/{/g; s/@A/@/g 正在将占位符字符串转换回其原始字符。

    {[^{}]*} 在表达式中根据需要多次隔离您想要的实际匹配和/或使用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
  •  0
  •   Tim Cooper    15 年前

    vi 或 vim :

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

    这运行 六 :%s g ),如果未找到模式,则避免引发错误( e ),然后将结果更改保存到磁盘并退出。这 &>/dev/null 防止GUI在屏幕上短暂闪烁,这可能会很烦人。

    我喜欢用 六 有时对于超级复杂的正则表达式,因为(1)perl 死 死亡,(2)维姆有 非常 高级正则表达式引擎,以及(3)我已经非常熟悉 六

        18
  •  0
  •   GL2014    13 年前

    由于PCRE也标记在这里,我们可以使用GNU grep 在正则表达式中使用非懒惰匹配 .*? 这将匹配与之相反的最接近的第一场比赛 .*

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

    说明: 使用 全局正则表达式打印 oP -P 负责在此处启用PCRE正则表达式。在主程序中 全局正则表达式打印 :// 直到下一次发生 / 自从我们使用 .*? 它会先寻找 / 之后(http/https://)。它只会在线打印匹配的零件。

        19
  •  0
  •   Ports    10 年前

    以下解决方案适用于匹配/处理多重存在(链式;串联式;复合式)HTML或其他标签。例如,我想编辑HTML代码以删除 <span>

    问题: 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/<span> <跨度>
    • [^>] :后面跟着任何不是 >
    • *> >
    • //g :将任何存在的此类字符串替换为空。

    附录

    我试图清理网址,但遇到了匹配/排除单词的困难- href -使用上述方法。我简要地看了一下周围的消极景象( Regular expression to match a line that doesn't contain a word

    我决定更换 超链接 随着 ` (回溯),执行正则表达式替换,然后替换 ` 随着 超链接 .

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

    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/` :替换 超链接 随着 ` (后退)
    • s/<a :查找URL的开头
    • [^`] :后面跟着任何不是 ` (后退)
    • *` `
    • /<a href/g <a href
        20
  •  0
  •   jmeinlschmidt VINAY NAIR    9 年前
    echo "/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|'
    

    别担心,我在另一个论坛上得到的:)