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

问题描述 投票:370回答:21

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

所以来自:

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

我想要:

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

(有或没有尾随斜线,没关系)

我试过了:

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

和(逃避非贪婪量词)

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

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

regex sed pcre greedy regex-greedy
21个回答
396
投票

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

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

5
投票

% echo "<b>foo</b>bar" | sed 's/<.*>//g' bar 肯定有它的位置,但这不是其中之一!

正如迪伊指出:只需使用% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g' foobar 。在这种情况下,它更简单,更安全。这是一个使用Bash语法从URL中提取各种组件的示例:

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

给你:

sed

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

(全部归功于迪)


4
投票

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

cut
  • -r:使用扩展正则表达式(用于+和非转义括号)
  • “:loop”:定义一个名为“loop”的新标签
  • -A:带有奇数命令的种子
  • “t loop”:如果成功替换,则跳回标签“loop”

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

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-)

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

3
投票

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

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


2
投票

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

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

如果您不熟悉分组,请启动sed -r -e ":loop" -e 's|(http://.+)/.*|\1|' -e "t loop"


1
投票

我意识到这是一个旧条目,但有人可能会发现它很有用。由于完整域名的总长度不得超过253个字符,因此替换。* with。\ {1,255 \}


1
投票

这是如何使用sed稳健地进行多字符串的非贪婪匹配。假设您想要将每个-e "s,$,/," 更改为sed 's|(http:\/\/[^\/]+\/).*|\1|' ,例如此输入:

here

应该成为这个输出:

foo...bar

要做到这一点,你将foo和bar转换为单个字符,然后使用它们之间的那些字符的否定:

<foo...bar>

在上面:

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

请注意,上面的内容不依赖于输入中不存在的任何特定字符串,因为它在第一步中制作了这样的字符串,也不关心您想要匹配的任何特定正则表达式的出现,因为您可以使用foo多次在表达式中必要的,以隔离您想要的实际匹配和/或与seds数字匹配运算符,例如仅替换第二次出现:

bar

0
投票
s/@C/}/g; s/@B/{/g; s/@A/@/g

不要打扰,我在另一个论坛上得到它:)


0
投票

{[^{}]*}也有效


0
投票

另一个sed版本:

$ 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

它匹配echo "/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|' 后跟一个字母数字字符(所以不是另一个正斜杠)以及其余字符直到行尾。之后它没有替换它(即删除它。)


228
投票

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

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

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

0
投票

这是你可以用两步法和awk做的事情:

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

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

希望有所帮助!


0
投票

还没有看到这个答案,所以这里你可以用/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

这将全局运行vi vim替换(尾随vi -c '%s/\(http:\/\/.\{-}\/\).*/\1/ge | wq' file &>/dev/null ),如果找不到模式(vi)则避免引发错误,然后将得到的更改保存到磁盘并退出。 :%s防止GUI在屏幕上短暂闪烁,这可能很烦人。

我喜欢有时使用g作为超级复杂的正则表达式,因为(1)perl是 死 死亡,(2)vim有一个非常先进的正则表达式引擎,(3)我已经在我的日常使用编辑文档中非常熟悉e正则表达式。


112
投票

使用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
  • 记得括号之间的匹配qazxsw poi ... qazxsw poi,后来可以通过qazxsw poi访问,\( ...
  • 匹配\)
  • 其次是括号中的任何东西\1\2意味着http://[][ab/]
  • a的第一个b意味着/,所以除了^中的东西之外的任何东西
  • 所以[]意味着除了not角色以外的任何东西
  • []将重复前一组,所以[^/]意味着除/之外的人物。
  • 到目前为止*意味着搜索和记住[^/]*follow任何字符除了/并记住你发现了什么
  • 我们想搜索直到域的结尾所以停在下一个sed -n 's;\(http://[^/]*\)所以在最后添加另一个http:///但我们想要匹配域之后的其余行,所以添加/
  • 现在在第1组(/)记住的比赛是域名,所以用sed -n 's;\(http://[^/]*\)/'组中保存的东西替换匹配的行并打印:.*

如果你想在域之后包含反斜杠,那么在组中添加一个反斜杠来记住:

\1

输出:

\1

36
投票

sed不支持“非贪婪”运营商。

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

sed -n 's;\(http://[^/]*\)/.*;\1;p'

附:没有必要反斜杠“/”。


26
投票

模拟echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*/\).*;\1;p' 中的懒惰(非贪婪)量词

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

  1. 查找表达式的第一次出现: POSIX ERE(使用http://www.suon.co.uk/ 选项) 正则表达式: sed 's,\(http://[^/]*\)/.*,\1,' 桑达: sed 示例(查找第一个数字序列)-r(EXPRESSION).*|. 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 BRE 正则表达式: enter image description here 桑达: (EXPRESSION) 示例(查找第一个数字序列): .* \(\(\(EXPRESSION\).*\)*.\)* 这个版本与ERE版本类似,但没有涉及更改。就这样。在每个单一位置,引擎会尝试匹配一个数字。 sed "s/\(\(\(EXPRESSION\).*\)*.\)*/\3/" 如果找到,则消耗并捕获其他后续数字,然后立即匹配其余行,否则因为$ sed "s/\(\(\([0-9]\{1,\}\).*\)*.\)*/\3/" <<< "foo 12 bar 34" 意味着更多或零,它跳过第二个捕获组12 并到达点enter image description here以匹配单个字符并且此过程继续。
  2. 查找第一次出现的分隔表达式: 此方法将匹配第一次出现的分隔字符串。我们可以称之为字符串块。 * 输入字符串: \(\([0-9]\{1,\}\).*\)* -EDE:. -SDE:sed "s/\(END-DELIMITER-EXPRESSION\).*/\1/; \ s/\(\(START-DELIMITER-EXPRESSION.*\)*.\)*/\1/g" foobar start block #1 end barfoo start block #2 end 输出: end 第一个正则表达式start匹配并捕获第一个结束分隔符$ sed "s/\(end\).*/\1/; s/\(\(start.*\)*.\)*/\1/g" 和替换所有匹配最近捕获的字符,这是最终分隔符。在这个阶段,我们的输出是:start block #1 end \(end\).* 然后将结果传递给第二个正则表达式end,它与上面的POSIX BRE版本相同。如果起始分隔符foobar start block #1 end未匹配,则匹配单个字符,否则匹配并捕获起始分隔符并匹配其余字符。 enter image description here

直接回答你的问题

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

  • EDE:\(\(start.*\)*.\)*
  • SDE:start

用法:

enter image description here

输出:

[^:/]\/

21
投票

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

这个帖子真的很旧但我认为人们仍然需要它。让我们说你要杀掉一切,直到第一次出现http:。你不能说$ sed "s/\([^:/]\/\).*/\1/g; s/\(\(http:.*\)*.\)*/\1/" <<< "http://www.suepearson.co.uk/product/174/71/3816/" ......

所以一个不错的解决方案涉及两个步骤,假设你可以在输入中留下一个你不期望的独特单词,比如http://www.suepearson.co.uk/

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

HELLO

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

HTH!


16
投票

这可以使用cut来完成:

[^HELLO]

16
投票

top_sekrit

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

贪心匹配

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

非贪心匹配

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

9
投票

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

sed - non greedy matching by Christoph Sieghart
© www.soinside.com 2019 - 2024. All rights reserved.