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

Joe*_*oel 393 regex pcre sed greedy regex-greedy

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

所以来自:

http://www.suepearson.co.uk/product/174/71/3816/
Run Code Online (Sandbox Code Playgroud)

我想要:

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

(有或没有火车斜线,没关系)

我试过了:

 sed 's|\(http:\/\/.*?\/\).*|\1|'
Run Code Online (Sandbox Code Playgroud)

和(逃避非贪婪量词)

sed 's|\(http:\/\/.*\?\/\).*|\1|'
Run Code Online (Sandbox Code Playgroud)

但我似乎无法使非贪婪量词工作,所以它总是最终匹配整个字符串.

cha*_*aos 410

基本或扩展的Posix/GNU正则表达式都不能识别非贪心量词; 你需要一个后来的正则表达式.幸运的是,这个上下文的Perl正则表达式非常容易获得:

perl -pe 's|(http://.*?/).*|\1|'
Run Code Online (Sandbox Code Playgroud)

  • 为了做到这一点,使用选项`-pi -e`. (12认同)
  • 神圣的烟我不敢相信有用:-)只有糟透了的东西现在我的脚本有一个Perl依赖:-(从好的方面来看,几乎每个Linux发行版都有Perl已经可能不是问题了:-) (10认同)
  • @Freedom_Ben:POSIX需要*IIRC`perl`* (6认同)
  • @ dolphus333:"基本和扩展的Posix/GNU正则表达式都不识别非贪婪量词"意味着"你不能在sed中使用非贪婪量词". (4认同)
  • @Sérgio这是你如何做所要求的事情,这在`sed`是不可能的,使用的语法基本上与`sed`相同 (3认同)

Gum*_*mbo 239

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

sed 's|\(http://[^/]*/\).*|\1|g'
Run Code Online (Sandbox Code Playgroud)

  • [Christoph Sieghart的非贪婪匹配](http://0x2a.at/blog/2008/07/sed--non-greedy-matching/) (13认同)
  • 不幸的是你不能; 见[混沌答案](http://stackoverflow.com/a/1103177/27302). (5认同)
  • 如何使用这种技术使sed匹配非贪婪的短语? (3认同)
  • 这是我的首选答案,但让我们明确一点,sed 仍在使用 _greedy_ 匹配,我们只是构建一个模式,其中贪婪匹配在我们想要的地方终止。 (2认同)

ste*_*anB 116

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

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

输出:

http://www.suon.co.uk
Run Code Online (Sandbox Code Playgroud)

这是:

  • 不要输出 -n
  • 搜索,匹配模式,替换和打印 s/<pattern>/<replace>/p
  • 使用;搜索命令分隔符而不是/更容易键入s;<pattern>;<replace>;p
  • 记住括号之间的匹配\(...... \),以后可以使用\1,\2......
  • 比赛 http://
  • 后面在括号任何东西[],[ab/]就意味着无论是ab/
  • 首先^[]手段not,然后是除了东西之外的任何东西[]
  • [^/]意味着除了/角色以外
  • *是重复上一组,所以[^/]*意味着字符除外/.
  • 到目前为止sed -n 's;\(http://[^/]*\)意味着搜索和记住http://后跟任何字符,除了/并记住你发现了什么
  • 我们想搜索直到域的结尾,所以在下一个停止,/所以最后添加另一个/:sed -n 's;\(http://[^/]*\)/'但是我们想在域之后匹配其余的行,所以添加.*
  • 现在,在组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'
Run Code Online (Sandbox Code Playgroud)

输出:

http://www.suon.co.uk/
Run Code Online (Sandbox Code Playgroud)

  • 关于最近的编辑:括号是一种括号字符,因此将它们称为括号是不正确的,特别是如果你按照作者所做的那样跟随实际字符的单词.此外,它是某些文化中的首选用法,因此将其替换为您自己文化中的首选用法似乎有点粗鲁,但我确信这不是编辑的意图.就个人而言,我认为最好使用纯粹的描述性名称,如*圆括号*,*方括号*和*尖括号*. (7认同)
  • 是否可以用字符串替换分隔符? (2认同)

and*_*coz 36

sed不支持"非贪婪"运营商.

您必须使用"[]"运算符从匹配中排除"/".

sed 's,\(http://[^/]*\)/.*,\1,'
Run Code Online (Sandbox Code Playgroud)

PS没有必要反斜杠"/".


rev*_*evo 29

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

和所有其他正则表达口味!

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

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

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

    sed "s/\(END-DELIMITER-EXPRESSION\).*/\1/; \
         s/\(\(START-DELIMITER-EXPRESSION.*\)*.\)*/\1/g"
    
    Run Code Online (Sandbox Code Playgroud)

    输入字符串:

    foobar start block #1 end barfoo start block #2 end
    
    Run Code Online (Sandbox Code Playgroud)

    -EDE: end

    -SDE: start

    $ sed "s/\(end\).*/\1/; s/\(\(start.*\)*.\)*/\1/g"
    
    Run Code Online (Sandbox Code Playgroud)

    输出:

    start block #1 end
    
    Run Code Online (Sandbox Code Playgroud)

    第一个正则表达式\(end\).*匹配并捕获第一个结束分隔符,end并且所有子代码都与最近捕获的字符匹配,这些字符是结束分隔符.在这个阶段,我们的输出是:foobar start block #1 end.

    在此输入图像描述

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

    在此输入图像描述


直接回答你的问题

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

  • EDE: [^:/]\/

  • SDE: http:

用法:

$ sed "s/\([^:/]\/\).*/\1/g; s/\(\(http:.*\)*.\)*/\1/" <<< "http://www.suepearson.co.uk/product/174/71/3816/"
Run Code Online (Sandbox Code Playgroud)

输出:

http://www.suepearson.co.uk/
Run Code Online (Sandbox Code Playgroud)


ish*_*hak 21

多贪婪的解决方案,适用于多个角色

这个帖子真的很旧但我认为人们仍然需要它.让我们说你要杀掉所有东西,直到第一次出现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
Run Code Online (Sandbox Code Playgroud)

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

HTH!

  • 为了使它更好,在你不能指望未使用的字符的情况下有用:1.用真正未使用的WORD替换那个特殊字符,2.用特殊字符替换结束序列,3.用特殊字符结束搜索,4 .替换特殊字符,5.替换特殊的WORD.例如,您需要<hello>和</ hello>之间的贪婪运算符: (4认同)
  • 这里的例子:echo"Find:<hello> fir~st <br> yes </ hello> <hello> sec~ond </ hello>"| sed -e"s,〜,VERYSPECIAL,g"-e"s,</ hello>,〜,g"-e"s,.*查找:<hello> \([^〜]*\).*,\1," - e"s,\〜,</ hello>," - e"s,VERYSPECIAL,〜," (3认同)
  • 我同意.好的解决方案 我会改写评论说:如果你不能依赖〜未使用,首先使用s /〜/ VERYspeciaL/g替换当前的事件,然后执行上述技巧,然后使用s/VERYspeciaL /〜/ g返回原始〜 (2认同)

小智 16

这可以使用cut来完成:

echo "http://www.suepearson.co.uk/product/174/71/3816/" | cut -d'/' -f1-3
Run Code Online (Sandbox Code Playgroud)


gre*_*lio 16

sed - Christoph Sieghart的非贪婪匹配

在sed中获得非贪婪匹配的技巧是匹配除终止匹配的字符之外的所有字符.我知道,这是一个不费吹灰之力,但我浪费了宝贵的时间,而且shell脚本应该是快速而简单的.所以如果其他人可能需要它:

贪心匹配

% echo "<b>foo</b>bar" | sed 's/<.*>//g'
bar
Run Code Online (Sandbox Code Playgroud)

非贪心匹配

% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar
Run Code Online (Sandbox Code Playgroud)


gho*_*g74 9

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

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


pet*_*erh 5

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

正如迪伊指出:只是使用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-)
Run Code Online (Sandbox Code Playgroud)

给你:

protocol = "http"
host = "www.suepearson.co.uk"
urlhost = "http://www.suepearson.co.uk"
urlpath = "product/174/71/3816/"
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,这是一种更灵活的方法.

(全部归功于迪)