首页 文章

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

提问于
浏览
350

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

所以来自:

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

我想要:

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

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

我试过了:

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

和(逃避非贪婪量词)

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

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

20 回答

  • 2

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

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

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

    另一个sed版本:

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

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

  • 0

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

    以及所有其他正则表达口味!

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

    • POSIX ERE (使用 -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
    

    How does it work

    这个正则表达式得益于交替 | . 在每个位置,引擎将查找交替的第一侧(我们的目标),如果不匹配,则交替的第二侧有一个点 . 匹配下一个直接字符 .

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

    • POSIX BRE

    正则表达式:

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

    桑达:

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

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

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

    这个版本与ERE版本类似,但没有涉及更改 . 就这样 . 在每个单一位置引擎尝试匹配一个数字 .

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

    • 查找 delimited 表达式的第一个匹配项:

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

    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 .

    然后将结果传递给第二个regex \(\(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/"
    

    输出:

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

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

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

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

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

  • 5

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

  • 1

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

    sed -r -e ":loop" -e 's|(http://.+)/.*|\1|' -e "t loop"
    
    • -r:使用扩展正则表达式(用于和未转义的括号)

    • ":loop":定义名为"loop"的新标签

    • -e:向sed添加命令

    • "t loop":如果成功替换,则跳回标签"loop"

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

    -e "s,$,/,"
    
  • 106

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

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

    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/"
    

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

    (全部归功于迪)

  • 4

    使用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://[^/]*\)/' 但是我们希望在域之后匹配其余的行,所以添加 .*

    • 现在在第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/
    
  • 12

    超过一个字符的非贪婪解决方案

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

  • 0

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

  • 35
    sed 's|(http:\/\/[^\/]+\/).*|\1|'
    
  • 363
    echo "/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|'
    

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

  • 213

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

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

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

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

  • 16

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

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

    如果您不熟悉分组,请启动here .

  • 1

    这是如何使用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
    

    要做到这一点,你将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
    

    在上面:

    • s/@/@A/g; s/{/@B/g; s/}/@C/g 正在将 {} 转换为输入中不存在的占位符字符串,因此这些字符可用于将 foobar 转换为 .

    • s/foo/{/g; s/bar/}/g 分别将 foobar 转换为 {}

    • s/{[^{}]*}/<&>/g 正在执行我们想要的操作 - 将 foo...bar 转换为 <foo...bar>

    • s/}/bar/g; s/{/foo/g 正在将 {} 转换回 foobar .

    • 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
    
  • 3

    这可以使用cut来完成:

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

相关问题