464

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

所以从:

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

我想:

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

(无论有没有斜杠,都没有关系)

我试过了:

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

和(转义非贪婪量词)

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

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

4

27 回答 27

465

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

perl -pe 's|(http://.*?/).*|\1|'
于 2009-07-09T10:58:23.387 回答
297

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

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

sed 's|\(http://[^/]*/\).*|\1|g'
于 2009-07-09T10:51:34.693 回答
141

使用 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/
于 2012-12-20T23:36:55.853 回答
39

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

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

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

    • POSIX ERE(使用-r选项)

      正则表达式:

        (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
      

      它是如何工作的

      这个正则表达式受益于一个交替|。在每个位置,引擎都会尝试选择最长的匹配项(这是一个 POSIX 标准,后面还有几个其他引擎),这意味着它会.一直持续到([0-9]+).*. 但是顺序也很重要。

      在此处输入图像描述

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

    • POSIX BRE

      正则表达式:

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

      赛德:

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

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

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

      这个类似于 ERE 版本,但不涉及交替。就这样。在每个位置,引擎都会尝试匹配一个数字。

      在此处输入图像描述

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

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

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

    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.

    在此处输入图像描述

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

    在此处输入图像描述


直接回答你的问题

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

  • ED:[^:/]\/

  • SDE:http:

用法:

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

输出:

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

注意:这不适用于相同的分隔符。

于 2016-09-28T16:26:21.600 回答
38

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

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

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

PS 不需要反斜杠“/”。

于 2009-07-09T11:08:44.723 回答
23

sed - Christoph Sieghart 的非贪婪匹配

在 sed 中获得非贪婪匹配的技巧是匹配所有字符,不包括终止匹配的字符。我知道,这很简单,但是我在这上面浪费了宝贵的时间,而且 shell 脚本毕竟应该是快速和简单的。因此,以防其他人可能需要它:

贪心匹配

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

非贪心匹配

% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar
于 2017-10-12T21:45:39.617 回答
20

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

这个线程真的很旧,但我认为人们仍然需要它。假设您想杀死所有东西,直到第一次出现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

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

于 2013-10-30T13:05:53.770 回答
17

这可以使用 cut 来完成:

echo "http://www.suepearson.co.uk/product/174/71/3816/" | cut -d'/' -f1-3
于 2010-12-10T01:02:01.880 回答
9

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

string="http://www.suepearson.co.uk/product/174/71/3816/"
echo $string | awk -F"/" '{print $1,$2,$3}' OFS="/"
于 2009-07-09T10:59:12.713 回答
5

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

如您所见,这是一种更灵活的方法。

(所有功劳归于迪)

于 2013-08-30T14:41:15.797 回答
3
sed 's|(http:\/\/[^\/]+\/).*|\1|'
于 2009-07-09T10:58:59.563 回答
3

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

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

于 2009-07-09T11:25:07.433 回答
3

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

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

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

-e "s,$,/,"
于 2016-08-01T12:52:19.727 回答
2

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

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

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

于 2014-02-06T18:14:39.483 回答
1

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

于 2011-06-29T15:49:11.953 回答
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

在上面:

  1. s/@/@A/g; s/{/@B/g; s/}/@C/g正在将{and转换}为输入中不存在的占位符字符串,因此这些字符可用于转换foo和转换bar
  2. s/foo/{/g; s/bar/}/g分别将fooand转换bar{and}
  3. s/{[^{}]*}/<&>/g正在执行我们想要的操作 - 转换foo...bar<foo...bar>
  4. s/}/bar/g; s/{/foo/g正在转换{and}回到fooand bar
  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
于 2018-06-26T17:07:31.397 回答
1

还没有看到这个答案,所以这里是你可以用vior做到这一点的方法vim

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

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

我有时喜欢使用vi超级复杂的正则表达式,因为 (1) perl 快死了,(2) vim 有一个非常vi先进的正则表达式引擎,以及 (3) 我在日常使用编辑中已经非常熟悉正则表达式文件。

于 2019-04-03T20:38:33.700 回答
1

由于 PCRE 也被标记在这里,我们可以grep通过在正则表达式中使用非惰性匹配来使用 GNU,.*?这将匹配对面的第一个最近匹配.*(这真的很贪婪,直到最后一次出现匹配)。

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

说明: usinggrep'soPoptions here where-P负责在此处启用 PCRE 正则表达式。grep在提到正则表达式的主程序中,://直到下一次出现,/因为我们已经使用.*?它,它将/在 (http/https://) 之后首先查找。它只会在线打印匹配的部分。

于 2021-11-15T06:21:40.327 回答
0
echo "/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|'

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

于 2010-12-10T01:20:36.997 回答
0

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

于 2013-06-24T15:33:43.740 回答
0

另一个 sed 版本:

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

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

于 2016-02-02T00:03:00.653 回答
0

这是您可以通过两步方法和 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

希望有帮助!

于 2017-06-08T20:35:11.837 回答
0

@Daniel H(关于你对 andcoz 回答的评论,虽然很久以前):删除尾随零适用于

s,([[:digit:]]\.[[:digit:]]*[1-9])[0]*$,\1,g

这是关于明确定义匹配条件...

于 2020-07-27T13:34:43.367 回答
0

您还应该考虑没有匹配分隔符的情况。您是否要输出该行。如果没有匹配项,我的示例不会输出任何内容。

您需要最多第 3 个 / 的前缀,因此选择两次不包含 / 和跟随 / 的任意长度的字符串,然后选择不包含 / 的任意长度的字符串,然后匹配 / 跟随任何字符串,然后打印选择。这个想法适用于任何单个字符分隔符。

echo http://www.suepearson.co.uk/product/174/71/3816/ | \
  sed -nr 's,(([^/]*/){2}[^/]*)/.*,\1,p'

使用 sed 命令,您可以快速删除前缀或选择分隔符,例如:

echo 'aaa @cee: { "foo":" @cee: " }' | \
  sed -r 't x;s/ @cee: /\n/;D;:x'

这比一次吃炭要快得多。

如果之前匹配成功,则跳转到标签。在第一个分隔符之前的 / 处添加 \n。最多删除第一个\n。如果添加了 \n,则跳转到结尾并打印。

如果有开始和结束分隔符,很容易删除结束分隔符,直到你到达你想要的第n-2个元素然后做D技巧,在结束分隔符之后删除,如果不匹配则跳转到删除,在开始分隔符之前删除并且和打印。这仅在开始/结束分隔符成对出现时才有效。

echo 'foobar start block #1 end barfoo start block #2 end bazfoo start block #3 end goo start block #4 end faa' | \
  sed -r 't x;s/end//;s/end/\n/;D;:x;s/(end).*/\1/;T y;s/.*(start)/\1/;p;:y;d'
于 2021-06-11T14:11:55.130 回答
0

如果您可以访问 gnu grep,则可以使用 perl 正则表达式:

grep -Po '^https?://([^/]+)(?=)' <<< 'http://www.suepearson.co.uk/product/174/71/3816/'
http://www.suepearson.co.uk

或者,在域使用获取所有内容

grep -Po '^https?://([^/]+)\K.*' <<< 'http://www.suepearson.co.uk/product/174/71/3816/'
/product/174/71/3816/
于 2021-06-19T13:12:08.050 回答
0

以下解决方案适用于匹配/使用多重存在(链式;串联;复合)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> : 寻找<span>
  • [^>] : 后跟任何不是>
  • *> : 直到你找到>
  • //g :将任何此类字符串替换为空。

附录

我试图清理 URL,但我遇到了匹配/排除单词的困难href——使用上面的方法。我简要地查看了否定的外观(正则表达式以匹配不包含单词的行),但这种方法似乎过于复杂并且没有提供令人满意的解决方案。

我决定替换href`(反引号),进行正则表达式替换,然后替换`href.

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

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/` :替换href`(反引号)
  • s/<a : 查找 URL 的开始
  • [^`] : 后跟任何不是`(反引号)
  • *` : 直到你找到一个`
  • /<a href/g :将找到的每个替换为<a href
于 2021-11-15T01:03:54.613 回答
-1

不幸的是,如前所述,这在 sed 中不受支持。为了克服这个问题,我建议使用下一个最好的东西(实际上甚至更好),使用类似 vim sed 的功能。

定义在.bash-profile

vimdo() { vim $2 --not-a-term -c "$1"  -es +"w >> /dev/stdout" -cq!  ; }

这将创建无头 vim 来执行命令。

现在您可以执行以下操作:

echo $PATH | vimdo "%s_\c:[a-zA-Z0-9\\/]\{-}python[a-zA-Z0-9\\/]\{-}:__g" -

$PATH.

用于-在 vimdo 中从管道输入。

虽然大多数语法是相同的。Vim 具有更高级的功能,并且使用\{-}是非贪婪匹配的标准。见help regexp

于 2022-01-03T00:09:28.513 回答