Non greedy (reluctant) regex matching in sed?
我正在尝试使用sed清理URL行以仅提取域。
所以来自:
1 | http://www.suepearson.co.uk/product/174/71/3816/ |
我想要:
网址:http://www.suepearson.co.uk/
(有或没有牵引斜杠,没关系)
我已经尝试过:
1 | sed 's|\(http:\/\/.*?\/\).*|\1|' |
和(逃离非贪婪量词)
1 | sed 's|\(http:\/\/.*\?\/\).*|\1|' |
但我似乎不能让非贪婪量词工作,所以它总是以匹配整个字符串结束。
基本的或扩展的posix/gnu regex都不能识别非贪婪量词;您需要一个更晚的regex。幸运的是,此上下文的PerlRegex非常容易获得:
1 | perl -pe 's|(http://.*?/).*|\1|' |
在这种特定的情况下,您可以在不使用非贪婪regex的情况下完成任务。
尝试这个非贪婪的regex
1 | sed 's|\(http://[^/]*/\).*|\1|g' |
对于SED,我通常通过搜索除分隔符之外的任何内容来实现非贪婪搜索,直到分隔符:
1 | echo"http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*\)/.*;\1;p' |
输出:
1 | http://www.suon.co.uk |
这是:
- 不输出
-n 。 - 搜索、匹配图案、替换并打印
s/ 。/ /p - 使用
; 搜索命令分隔符而不是/ 使键入s; 更容易。; ;p - 记住括号
\( 之间的匹配…\) ,稍后可通过\1 、\2 … - 与
http:// 匹配 - 在括号
[] 中,[ab/] 表示a 或b 或/ 。 - 在
[] 中,第一个^ 是指not 的意思,其次是[] 中的东西。 - 因此,
[^/] 是指除/ 字符以外的任何字符。 * 是重复前一组,所以[^/]* 表示除/ 以外的字符。- 到目前为止,
sed -n 's;\(http://[^/]*\) 是指搜索并记住http:// 后面跟除/ 以外的任何字符,并记住所发现的内容。 - 我们要搜索到域的结尾,所以在下一个
/ 处停止搜索,所以在结尾处添加另一个/ :sed -n 's;\(http://[^/]*\)/' ,但我们要在域之后匹配行的其余部分,所以添加.* 。 - 现在,组1(
\1 中记住的匹配是域,因此用组\1 中保存的内容替换匹配行,并打印:sed -n 's;\(http://[^/]*\)/.*;\1;p' 。
如果要在域后也包含反斜杠,请在组中再添加一个反斜杠以记住:
1 | echo"http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*/\).*;\1;p' |
输出:
1 | http://www.suon.co.uk/ |
SED不支持"非贪婪"运算符。
必须使用"[]"运算符从匹配中排除"/"。
1 | sed 's,\(http://[^/]*\)/.*,\1,' |
另外,不需要反斜杠"/"。
在
还有所有其他的雷吉克斯口味!
查找表达式的第一个匹配项:
posix-ere(使用
-r 选项)Regex:
1(EXPRESSION).*|.塞德:
1sed -r"s/(EXPRESSION).*|./\1/g" # Global `g` modifier should be on示例(查找第一个数字序列)实时演示:
1$ sed -r"s/([0-9]+).*|./\1/g" <<<"foo 12 bar 34"112它是如何工作的?
这个regex得益于一个交替的
| 。在每个位置,引擎都将查找交替的第一个边(我们的目标),如果它与交替的第二个边不匹配,该边有一个点. 与下一个直接字符匹配。由于设置了全局标志,引擎将尝试逐字符匹配到输入字符串或目标的末尾。一旦第一个也是唯一一个交替左侧的捕获组匹配
(EXPRESSION) ,则立即消耗其余线路以及.* 。我们现在在第一个捕获组中保持我们的价值。POSIX BRE
Regex:
1\(\(\(EXPRESSION\).*\)*.\)*塞德:
1sed"s/\(\(\(EXPRESSION\).*\)*.\)*/\3/"示例(查找第一个数字序列):
1$ sed"s/\(\(\([0-9]\{1,\}\).*\)*.\)*/\3/" <<<"foo 12 bar 34"112这一个类似于ERE版本,但没有涉及到变更。这就是全部。在每一个位置,引擎都试图匹配一个数字。
如果找到,则消耗并捕获后面的其他数字,其余的行立即匹配,否则,因为
* 表示它跳过第二个捕获组\(\([0-9]\{1,\}\).*\)* ,到达一个点. ,以匹配单个字符,这个过程继续进行。
查找第一个出现的分隔表达式:
这种方法将匹配分隔字符串的第一次出现。我们可以称它为一个字符串块。
1 2 | sed"s/\(END-DELIMITER-EXPRESSION\).*/\1/; \ s/\(\(START-DELIMITER-EXPRESSION.*\)*.\)*/\1/g" |
输入字符串:
1 | foobar start block #1 end barfoo start block #2 end |
-ede:
-sde:
1 | $ sed"s/\(end\).*/\1/; s/\(\(start.*\)*.\)*/\1/g" |
输出:
1 | start block #1 end |
第一个regex
然后将结果传递给第二个regex
直接回答你的问题
使用方法2(分隔表达式),您应该选择两个适当的表达式:
ede:
[^:/]\/ 。sde:
http: 。
用途:
1 | $ sed"s/\([^:/]\/\).*/\1/g; s/\(\(http:.*\)*.\)*/\1/" <<<"http://www.suepearson.co.uk/product/174/71/3816/" |
输出:
1 | http://www.suepearson.co.uk/ |
不止一个字符的非贪婪解决方案
这条线真的很旧,但我想人们仍然需要它。假设你想杀死所有的东西,直到第一次出现
所以一个好的解决方案包括两个步骤,假设您可以在输入中保留一个您不期望的惟一单词,比如
在这种情况下,我们可以:
1 2 | s/HELLO/top_sekrit/ #will only replace the very first occurrence s/.*top_sekrit// #kill everything till end of the first HELLO |
当然,使用更简单的输入,您可以使用更小的单词,甚至可以使用单个字符。
嗯!
这可以使用CUT来完成:
1 | echo"http://www.suepearson.co.uk/product/174/71/3816/" | cut -d'/' -f1-3 |
SED-Christoph Sieghart的非贪婪匹配
在SED中获得非贪婪匹配的技巧是匹配所有字符,不包括终止匹配的字符。我知道,这是一个轻而易举的人,但我浪费了宝贵的时间,毕竟shell脚本应该是快速而简单的。因此,如果其他人可能需要它:
贪婪匹配
1 2 | % echo"foobar" | sed 's/<.*>//g' bar |
非贪婪匹配
1 2 | % echo"foobar" | sed 's/<[^>]*>//g' foobar |
另一种不使用regex的方法是使用字段/分隔符方法eg
1 2 | string="http://www.suepearson.co.uk/product/174/71/3816/" echo $string | awk -F"/" '{print $1,$2,$3}' OFS="/" |
以东十一〔37〕当然有它的位置,但这不是其中之一!
正如迪伊所指出的:只要使用
1 2 3 4 5 6 | 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-) |
给你:
1 2 3 4 | protocol ="http" host ="www.suepearson.co.uk" urlhost ="http://www.suepearson.co.uk" urlpath ="product/174/71/3816/" |
正如您所看到的,这是一种更加灵活的方法。
(全部贷记DEE)
仍然有希望使用纯(GNU)SED来解决这个问题。尽管这不是一般的解决方案,但在某些情况下,您可以使用"循环"消除字符串中所有不必要的部分,如:
1 | sed -r -e":loop" -e 's|(http://.+)/.*|\1|' -e"t loop" |
- -R:使用扩展regex(用于+和无括号括号)
- ":loop":定义名为"loop"的新标签
- -E:向SED添加命令
- "T loop":如果替换成功,则跳转回标签"loop"
这里唯一的问题是它还将剪切最后一个分隔符("/"),但是如果您确实需要它,您仍然可以在"循环"完成后将其放回原处,只需在上一个命令行末尾附加此附加命令:
1 | -e"s,$,/," |
sed-e将正则表达式解释为扩展(现代)正则表达式
更新:macos x上的-e,gnu-sed中的-r。
1 | sed 's|(http:\/\/[^\/]+\/).*|\1|' |
因为您特别声明要使用SED(而不是Perl、Cut等),所以请尝试分组。这会绕过可能无法识别的非贪婪标识符。第一组是协议(即"http://"、"https://"、"tcp://"等)。第二组是域:
1 | echo"http://www.suon.co.uk/product/1/7/3/" | sed"s|^\(.*//\)\([^/]*\).*$|\1\2|" |
如果您不熟悉分组,请从这里开始。
我知道这是一个旧条目,但有人会发现它很有用。由于域名的完整长度不能超过253个字符,请用. 1,255替换。
这就是如何使用sed对多个字符串进行可靠的非贪婪匹配。假设您想将每个
1 2 | $ cat file ABC foo DEF bar GHI foo KLM bar NOP foo QRS bar TUV |
应成为该输出:
1 | ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV |
为此,您可以将foo和bar转换为单个字符,然后在它们之间使用这些字符的负数:
1 2 | $ 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 |
在上面:
请注意,上面的内容并不依赖于输入中没有的任何特定字符串,因为它在第一步中生成这些字符串,也不关心要匹配的任何特定regexp的哪个出现,因为您可以在表达式中使用
1 2 | $ 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 |
还没有看到这个答案,下面是您如何使用
1 | vi -c '%s/\(http:\/\/.\{-}\/\).*/\1/ge | wq' file &>/dev/null |
这将全局运行
我喜欢有时对超复杂的regex使用deaddilling,(2)vim有一个非常高级的regex引擎,和(3)我已经在我的日常使用编辑文档中非常熟悉
1 | echo"/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|' |
别麻烦了,我在另一个论坛上看到的:)
另一个SED版本:
1 | sed 's|/[:alphanum:].*||' file.txt |
它匹配
以下是您可以使用两步方法和awk进行的操作:
1 2 3 4 5 6 7 | A=http://www.suepearson.co.uk/product/174/71/3816/ echo $A|awk ' { var=gensub(///,"||",3,$0) ; sub(/\|\|.*/,"",var); print var }' |
Output:
http://www.suepearson.co.uk
希望有帮助!