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/]
表示a
或b
或/
- 第一个
^
在[]
意味着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式的风味!
-
查找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,\}\).*\)*
并到达一个点.
以匹配单个字符,并且这个过程继续。
-
-
查找首次出现的分隔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
希望有所帮助!