sed中的非贪婪正则expression式匹配?

我正在尝试使用sed来清理URL的行来提取只是域..

所以来自:

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

我想要:

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

(无论有没有训练的斜线,没关系)

我努力了:

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

和(逃避非贪心量词)

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

但我似乎无法得到非贪婪量词的工作,所以它总是最终匹配整个string。

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

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

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

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

对于sed,我通常通过search除分隔符之外的任何东西来实现非贪婪search,直到分隔符:

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

输出:

 http://www.suon.co.uk 

这是:

  • 不要输出-n
  • search,匹配模式,replace和打印s/<pattern>/<replace>/p
  • 使用 search命令分隔符而不是/使其更容易inputs;<pattern>;<replace>;p
  • 记住方括号\(\)之间的匹配,稍后可以用\1\2 …进行访问
  • 匹配http://
  • 其次是括号[]的任何内容, [ab/]表示ab/
  • 第一个^[]意味着not ,所以除了[]的东西之外
  • 所以[^/]意味着除了/字符之外的任何东西
  • *是重复上一组,所以[^/]*表示除/之外的字符。
  • 到目前为止sed -n 's;\(http://[^/]*\)表示search并记住http://后跟除/之外的任何字符,并记住您find的内容
  • 我们要search,直到域的末尾,所以停在下一个/所以添加另一个/最后: sed -n 's;\(http://[^/]*\)/'但我们要匹配在域名之后的其余行添加.*
  • 现在在组1( \1 )中记住的比赛是域,所以用组\1保存的东西replace匹配的线,并打印: 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/ 

sed不支持“非贪婪”操作符。

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

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

PS没有必要反斜杠“/”。

非单一字符的非贪婪解决scheme

这个线程真的很老,但我认为人们仍然需要它。 比方说,你想杀了一切,直到第一次发生HELLO 。 你不能说[^HELLO]你好[^HELLO]

所以一个好的解决scheme包含两个步骤,假设你可以省去一个你在input中不需要的独特的angular色,比如说(反引号)。

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

 s_HELLO_`_ #will only replace the very first occurrence s_.*`__ #kill everything till end of the first HELLO 

HTH!

这可以使用cut来完成:

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

模拟sed懒惰(不贪心)量词

和所有其他正则expression式的风味!

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

    • POSIX ERE (使用-r选项)

      正则expression式:

       (EXPRESSION).*|. 

      桑达:

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

      示例(查找第一个数字序列) 现场演示

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

      它是如何工作的

      这个正则expression式受益于一个替代| 。 在每一个位置,引擎会寻找交替的第一面(我们的目标),如果它不匹配有交点的交替的第二面. 匹配下一个直接的字符。

      在这里输入图像说明

      由于设置了全局标志,因此引擎会尝试逐个字符地继续匹配,直到inputstring或目标的末尾。 一旦交替左侧的第一个捕获组匹配(EXPRESSION)行的其余部分立即被消耗。 我们现在在第一个捕获组中保持我们的价值。

    • POSIX BRE

      正则expression式:

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

      桑达:

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

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

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

      这个就像ERE版本,但是没有涉及到更改。 就这样。 在每个单一的位置引擎试图匹配一个数字。

      在这里输入图像说明

      如果发现,其他的数字被消耗和捕获,其余的行是立即匹配,否则,因为*表示更多或为零跳过第二个捕获组\(\([0-9]\{1,\}\).*\)*并到达一个点. 以匹配单个字符,并且这个过程继续。

  2. 查找首次出现的分隔expression式:

    这种方法将匹配被分隔的string的第一次出现。 我们可以把它称为一个string块。

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

    inputstring:

     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 

    第一个正则expression式\(end\).*匹配并捕获第一个结束定界符end和replace全部匹配最近捕获的字符,这是最后的分隔符。 在这个阶段,我们的输出是: foobar start block #1 end

    在这里输入图像说明

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

    在这里输入图像说明


直接回答你的问题

使用方法#2(分隔expression式),你应该select两个适当的expression式:

  • EDE: [^:/]\/

  • SDE: http:

用法:

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

输出:

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

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

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

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)

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

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

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

纯(GNU)sed仍然有希望解决这个问题。 尽pipe在某些情况下这不是一个通用的解决scheme,但您可以使用“loops”来消除string中所有不必要的部分,如下所示:

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

这里唯一的问题是它也会截断最后的分隔符('/'),但是如果你确实需要它,你仍然可以简单地在“循环”结束之后把它放回去,只是在前一个末尾附加这个附加命令命令行:

 -e "s,$,/," 

因为你明确表示你正在尝试使用sed(而不是perl,cut等),请尝试分组。 这规避了可能不被识别的非贪婪标识符。 第一组是协议(即'http://','https://','tcp://'等)。 第二组是域名:

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

如果您不熟悉分组,请从这里开始。

sed – 由Christoph Sieghart非贪婪匹配

在sed中获得非贪婪匹配的技巧是匹配除终止匹配之外的所有字符。 我知道,这是毫不费力的,但是我浪费了宝贵的时间,毕竟,shell脚本应该是快速和容易的。 所以如果有人可能需要它:

贪婪的匹配

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

非贪婪的匹配

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

我意识到这是一个旧的入口,但有人可能会觉得它有用。 由于完整的域名不得超过253个字符,请使用。\ {1,255 \}replace。*。

 echo "/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|' 

不要打扰,我得到了另一个论坛:)

sed 's|\(http:\/\/www\.[az.0-9]*\/\).*|\1| 也是如此

另一个sed版本:

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

它匹配/后跟一个字母数字字符(所以不是另一个正斜杠)以及字符的其余部分,直到行的末尾。 之后它将其replace为无(即删除它)。

这是你可以用两步法和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

希望有所帮助!