我正在尝试使用 sed 清理 URL 行以仅提取域。
所以从:
http://www.suepearson.co.uk/product/174/71/3816/
我想:
http://www.suepearson.co.uk/
(无论有没有斜杠,都没有关系)
我努力了:
sed 's|\(http:\/\/.*?\/\).*|\1|'
和(转义非贪婪量词)
sed 's|\(http:\/\/.*\?\/\).*|\1|'
但我似乎无法让非贪婪量词(?
)工作,所以它总是最终匹配整个字符串。
sed -E 's...
。尽管如此,没有不情愿的运营商。
cut -d'/' -f1-3
有效。
基本的和扩展的 Posix/GNU 正则表达式都不能识别非贪婪量词;你需要一个以后的正则表达式。幸运的是,这种上下文的 Perl 正则表达式很容易获得:
perl -pe 's|(http://.*?/).*|\1|'
在这种特定情况下,您可以在不使用非贪婪正则表达式的情况下完成工作。
试试这个非贪婪的正则表达式 [^/]*
而不是 .*?
:
sed 's|\(http://[^/]*/\).*|\1|g'
([^&=#]+)=([^&#]*)
查找分配。有些情况肯定不会以这种方式工作,例如,在解析其主机部分的 URL 和路径名时,假定从捕获中排除可选的最后一个斜杠:^(http:\/\/.+?)/?$
使用 sed,我通常通过搜索除分隔符之外的任何内容直到分隔符来实现非贪婪搜索:
echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*\)/.*;\1;p'
输出:
http://www.suon.co.uk
这是:
不输出 -n
搜索、匹配模式、替换和打印 s/
利用 ;搜索命令分隔符而不是 / 以便更容易键入 so s;
记住括号 \( ... \) 之间的匹配,稍后可通过 \1,\2... 访问
匹配 http://
后跟括号 [] 中的任何内容,[ab/] 表示 a 或 b 或 /
[] 中的第一个 ^ 表示不是,所以后面是 [] 中的东西以外的任何内容
所以 [^/] 表示除 / 字符之外的任何内容
是重复前一组,所以 [^/]* 表示除 / 之外的字符。
到目前为止 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/
在 sed 中模拟惰性(非贪婪)量词
和所有其他正则表达式风格!
查找表达式的第一次出现:POSIX ERE(使用 -r 选项)正则表达式:(EXPRESSION).*|。 sed: sed -r 's/(EXPRESSION).*|./\1/g' # 全局 `g` 修饰符应该打开 示例(查找第一个数字序列) 现场演示: $ sed -r 's/([0 -9]+).*|./\1/g' <<< 'foo 12 bar 34' 12 它是如何工作的?此正则表达式受益于交替 |。在每个位置,引擎都尝试选择最长的匹配项(这是一个 POSIX 标准,随后还有几个其他引擎),这意味着它与 .直到找到 ([0-9]+).* 的匹配项。但是顺序也很重要。由于设置了全局标志,引擎会尝试逐个字符地继续匹配,直到输入字符串的末尾或我们的目标。一旦交替左侧的第一个也是唯一的捕获组匹配(EXPRESSION),其余的行也立即被消耗。*。我们现在在第一个捕获组中保留我们的值。 POSIX BRE 正则表达式:\(\(\(EXPRESSION\).*\)*.\)* Sed: sed 's/\(\(\(EXPRESSION\).*\)*.\)*/\3/ ' 示例(查找第一个数字序列): $ sed 's/\(\(\([0-9]\{1,\}\).*\)*.\)*/\3/' << < 'foo 12 bar 34' 12 这一个类似于 ERE 版本,但不涉及交替。就这样。在每个位置,引擎都会尝试匹配一个数字。如果找到,则消耗并捕获其他后续数字,并立即匹配其余行,否则因为 * 表示更多或零,它会跳过第二个捕获组 \(\([0-9]\{1,\}\) .*\)* 并到达一个点。匹配单个字符,此过程将继续。查找第一次出现的分隔表达式:这种方法将匹配第一次出现的分隔字符串。我们可以称它为字符串块。 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。然后将结果传递给与上述 POSIX BRE 版本相同的第二个正则表达式 \(\(start.*\)*.\)*。如果起始分隔符 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/
注意:这不适用于相同的分隔符。
sed
和所有其他遵循相同标准的引擎中,顺序确实很重要,当涉及到相等性时。所以 echo 'foo 1' | sed -r 's/.|([0-9]+).*/\1/g'
没有匹配,但 echo 'foo 1' | sed -r 's/([0-9]+).*|./\1/g'
有。
sed 不支持“非贪婪”运算符。
您必须使用“[]”运算符从匹配中排除“/”。
sed 's,\(http://[^/]*\)/.*,\1,'
PS 不需要反斜杠“/”。
s/([[:digit:]]\.[[1-9]]*)0*/\1/
显然不适用于 1.20300
。但是,由于最初的问题是关于 URL 的,因此应该在接受的答案中提及它们。
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
多个字符的非贪婪解决方案
这个线程真的很旧,但我认为人们仍然需要它。假设您想杀死所有东西,直到第一次出现 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
当然,通过更简单的输入,您可以使用更小的单词,甚至可以使用单个字符。
!
<$$>
而不是 `
(因为 $$
在 shell 中扩展为您的进程 ID,尽管您必须使用双引号而不是单引号,这可能会破坏正则表达式的其他部分)或者,如果 unicode 可用,则类似于 <∈∋>
。
perl
或 python
或其他一些语言。 perl
在一行中以不那么脆弱的方式执行此操作...
这可以使用 cut 来完成:
echo "http://www.suepearson.co.uk/product/174/71/3816/" | cut -d'/' -f1-3
另一种不使用正则表达式的方法是使用字段/分隔符方法,例如
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/"
如您所见,这是一种更灵活的方法。
(所有功劳归于迪)
仍然有希望使用纯(GNU)sed 来解决这个问题。尽管这在某些情况下不是通用解决方案,但您可以使用“循环”来消除字符串中所有不必要的部分,如下所示:
sed -r -e ":loop" -e 's|(http://.+)/.*|\1|' -e "t loop"
-r:使用扩展正则表达式(用于 + 和未转义的括号)
":loop": 定义一个名为 "loop" 的新标签
-e:将命令添加到 sed
“t loop”:如果替换成功,则跳回标签“loop”
这里唯一的问题是它还会剪切最后一个分隔符('/'),但如果你真的需要它,你仍然可以在“循环”完成后简单地把它放回去,只需在前面的末尾附加这个额外的命令命令行:
-e "s,$,/,"
sed 's|(http:\/\/[^\/]+\/).*|\1|'
sed -E 将正则表达式解释为扩展(现代)正则表达式
更新:MacOS X 上的 -E,GNU sed 上的 -r。
-E
是 BSD sed
独有的,因此也是 OS X 独有的。指向手册页的链接。如@stephancheg 的更正中所述,-r
确实为 GNU sed
带来了扩展的正则表达式。在 'nix 发行版中使用已知可变性的命令时要小心。我很难学到这一点。
-r
选项仅更改转义规则,根据 info 文件的 Appendix A Extended regular expressions
和一些快速测试;它实际上并没有添加非贪婪限定符(至少从 GNU sed version 4.2.1
开始。)
因为您特别声明您正在尝试使用 sed(而不是 perl、cut 等),所以请尝试分组。这规避了可能无法识别的非贪婪标识符。第一组是协议(即'http://'、'https://'、'tcp://'等)。第二组是域:
echo "http://www.suon.co.uk/product/1/7/3/" | sed "s|^\(.*//\)\([^/]*\).*$|\1\2|"
如果您不熟悉分组,请从 here 开始。
我意识到这是一个旧条目,但有人可能会觉得它很有用。由于完整域名的总长度不得超过 253 个字符,请将 .* 替换为 .\{1, 255\}
这是如何使用 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 正在将 { 和 } 转换为输入中不存在的占位符字符串,因此这些字符可用于将 foo 和 bar 转换为。 s/foo/{/g; s/bar/}/g 正在将 foo 和 bar 分别转换为 { 和 } s/{[^{}]*}/<&>/g 正在执行我们想要的操作 - 将 foo...bar 转换为
请注意,上述内容不依赖于输入中不存在的任何特定字符串,因为它在第一步中制造此类字符串,也不关心您要匹配的任何特定正则表达式的出现,因为您可以使用 {[^{}]*}
尽可能多在表达式中必要的时间以隔离您想要的实际匹配和/或使用 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
尚未看到此答案,因此您可以使用 vi
或 vim
执行此操作:
vi -c '%s/\(http:\/\/.\{-}\/\).*/\1/ge | wq' file &>/dev/null
这将全局运行 vi
:%s
替换(尾随 g
),如果找不到模式(e
)则避免引发错误,然后将结果更改保存到磁盘并退出。 &>/dev/null
可防止 GUI 在屏幕上短暂闪烁,这可能很烦人。
我喜欢有时将 vi
用于超级复杂的正则表达式,因为 (1) perl dead 正在死去,(2) vim 有一个 very 高级正则表达式引擎,以及 (3)在我的日常使用编辑文档中,我已经非常熟悉 vi
正则表达式。
由于这里也标记了 PCRE,我们可以通过在正则表达式 .*?
中使用非惰性匹配来使用 GNU grep
,这将匹配与 .*
相对的第一个最近匹配(这真的很贪心,直到最后一次匹配)。
grep -oP '^http[s]?:\/\/.*?/' Input_file
说明: 在此处使用 grep
的 oP
选项,其中 -P
负责在此处启用 PCRE 正则表达式。在 grep
的主程序中提到正则表达式匹配开始 http/https 后跟 ://
直到下一次出现 /
因为我们使用了 .*?
它将在 (http/https:/ /)。它只会在线打印匹配的部分。
echo "/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|'
不要打扰,我在另一个论坛上得到了它:)
/home/one/two/three/
,如果你添加另一个像 /home/one/two/three/four/myfile.txt
这样的 /
,你也会贪婪匹配 four
:/home/one/two/three/four
,问题是关于非贪婪
sed 's|\(http:\/\/www\.[a-z.0-9]*\/\).*|\1|
也可以
这是您可以通过两步方法和 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
希望有帮助!
另一个 sed 版本:
sed 's|/[:alnum:].*||' file.txt
它匹配 /
后跟一个字母数字字符(因此不是另一个正斜杠)以及直到行尾的其余字符。之后它什么都没有替换它(即删除它。)
"[[:alnum:]]"
,而不是 "[:alphanum:]"
。
@Daniel H(关于你对 andcoz 回答的评论,虽然很久以前):删除尾随零适用于
s,([[:digit:]]\.[[:digit:]]*[1-9])[0]*$,\1,g
这是关于明确定义匹配条件...
您还应该考虑没有匹配分隔符的情况。您是否要输出该行。如果没有匹配项,我的示例不会输出任何内容。
您需要最多第 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'
如果您可以访问 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/
以下解决方案适用于匹配/使用多重存在(链式;串联;复合)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/ : 找到
[^>] : 后面跟任何不是 >
*> : 直到你找到 >
//g : 将任何此类字符串替换为空。
附录
我试图清理 URL,但我遇到了匹配/排除单词的困难 - href
- 使用上述方法。我简要地查看了负面环视 (Regular expression to match a line that doesn't contain a word),但这种方法似乎过于复杂,并且没有提供令人满意的解决方案。
我决定将 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
不幸的是,如前所述,这在 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
中过滤掉 python。
使用 -
从 vimdo 中的管道输入。
虽然大多数语法是相同的。 Vim 具有更高级的功能,并且使用 \{-}
是非贪婪匹配的标准。见help regexp
。
不定期副业成功案例分享
-pi -e
。perl
是 POSIX 必需的sed
中是不可能的,使用与sed
基本相同的语法