Sed 正则表达式影响正则表达式后的内容
Sed Regular Expression affecting content after the Regex
我有一个包含以下文本的 HTML 文件:
<!doctype html><html><head><meta charset="utf-8"><title>Test</title><base href="/"><meta name="viewport" content="width=device-width,initial-scale=1"></head><body>test</body></html>
我 运行 这个 sed
命令反对它:
sed -i -e "s:<base href\s*=\s*\".*\"\s*>:<base href=\"/apps/test/\">:g" /tmp/test/index.html
我希望只是将 <base href="/">
替换为 <base href="/apps/test/">
并保留其余部分,但它最终会影响正则表达式之后的内容:
<!doctype html><html><head><meta charset="utf-8"><title>Test</title><base href="/apps/test/"></head><body>test</body></html>
它最终删除了在正则表达式之后找到的整个 meta
标记。我只是没有正确使用正则表达式吗?
GNU sed version 4.2.1
处理 xml/html 数据的唯一正确方法是使用 xml/html 解析器。
xmlstarlet
解法:
xmlstarlet fo -R -H /tmp/test/index.html | xmlstarlet ed -O -u '//base/@href' -v '/apps/test/'
输出:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Test</title>
<base href="/apps/test/"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
</head>
<body>test</body>
</html>
修改文件in-place添加-L
选项:xmlstarlet ed -L -u ....
因为*
是贪心的,所以=\s*\".*\"\s*>
中的.*
匹配最右边的>
可用。
您可以在命令周围使用单引号,这样就不必使用 \"
双引号。然后,您可以使用 "[^"]*"
而不是 ".*"
,它只匹配下一个双引号。
这将使您的命令变成
sed 's:<base href\s*=\s*"[^"]*"\s*>:<base href="/apps/test/">:g'
但是,使用 sed 和正则表达式操作 HTML 永远是脆弱的,一有机会就会崩溃。您可以使用 XML/HTML 解析器,例如 xmllint,请参阅 Roman 的回答;另一种选择是 W3C HTML-XML-utils 及其 hxpipe
和 hxunpipe
命令。
这些命令解析您的 HTML 并将其转换为易于使用 sed、awk 和朋友处理的格式,然后将其转换回 HTML:
$ hxpipe infile.html
!html ""
(html
(head
Acharset CDATA utf-8
(meta
(title
-Test
)title
Ahref CDATA /
(base
Aname CDATA viewport
Acontent CDATA width=device-width,initial-scale=1
(meta
)head
(body
-test
)body
)html
-\n
所以要将 base
标签的 href
中的 /
变成 /apps/test/
,我们可以这样做:
$ hxpipe infile.html \
| sed '/Ahref CDATA/{N;/\n(base$/s|\(CDATA\) .*| /apps/test/|}' \
| hxunpipe
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Test</title><meta href="/apps/test/" name="viewport" content="width=device-width,initial-scale=1"></head><body>test</body></html>
sed 命令所在的位置
sed '/Ahref CDATA/{N;/\n(base$/s|\(CDATA\) .*| /apps/test/|}'
或者,更好的可读性
/Ahref CDATA/ { # If line matches this
N # Append next line
/\n(base$/ s|\(CDATA\) .*| /apps/test/| # If in base tag, replace href
}
以或多或少的健壮方式进行更改。
我有一个包含以下文本的 HTML 文件:
<!doctype html><html><head><meta charset="utf-8"><title>Test</title><base href="/"><meta name="viewport" content="width=device-width,initial-scale=1"></head><body>test</body></html>
我 运行 这个 sed
命令反对它:
sed -i -e "s:<base href\s*=\s*\".*\"\s*>:<base href=\"/apps/test/\">:g" /tmp/test/index.html
我希望只是将 <base href="/">
替换为 <base href="/apps/test/">
并保留其余部分,但它最终会影响正则表达式之后的内容:
<!doctype html><html><head><meta charset="utf-8"><title>Test</title><base href="/apps/test/"></head><body>test</body></html>
它最终删除了在正则表达式之后找到的整个 meta
标记。我只是没有正确使用正则表达式吗?
GNU sed version 4.2.1
处理 xml/html 数据的唯一正确方法是使用 xml/html 解析器。
xmlstarlet
解法:
xmlstarlet fo -R -H /tmp/test/index.html | xmlstarlet ed -O -u '//base/@href' -v '/apps/test/'
输出:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Test</title>
<base href="/apps/test/"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
</head>
<body>test</body>
</html>
修改文件in-place添加-L
选项:xmlstarlet ed -L -u ....
因为*
是贪心的,所以=\s*\".*\"\s*>
中的.*
匹配最右边的>
可用。
您可以在命令周围使用单引号,这样就不必使用 \"
双引号。然后,您可以使用 "[^"]*"
而不是 ".*"
,它只匹配下一个双引号。
这将使您的命令变成
sed 's:<base href\s*=\s*"[^"]*"\s*>:<base href="/apps/test/">:g'
但是,使用 sed 和正则表达式操作 HTML 永远是脆弱的,一有机会就会崩溃。您可以使用 XML/HTML 解析器,例如 xmllint,请参阅 Roman 的回答;另一种选择是 W3C HTML-XML-utils 及其 hxpipe
和 hxunpipe
命令。
这些命令解析您的 HTML 并将其转换为易于使用 sed、awk 和朋友处理的格式,然后将其转换回 HTML:
$ hxpipe infile.html
!html ""
(html
(head
Acharset CDATA utf-8
(meta
(title
-Test
)title
Ahref CDATA /
(base
Aname CDATA viewport
Acontent CDATA width=device-width,initial-scale=1
(meta
)head
(body
-test
)body
)html
-\n
所以要将 base
标签的 href
中的 /
变成 /apps/test/
,我们可以这样做:
$ hxpipe infile.html \
| sed '/Ahref CDATA/{N;/\n(base$/s|\(CDATA\) .*| /apps/test/|}' \
| hxunpipe
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Test</title><meta href="/apps/test/" name="viewport" content="width=device-width,initial-scale=1"></head><body>test</body></html>
sed 命令所在的位置
sed '/Ahref CDATA/{N;/\n(base$/s|\(CDATA\) .*| /apps/test/|}'
或者,更好的可读性
/Ahref CDATA/ { # If line matches this
N # Append next line
/\n(base$/ s|\(CDATA\) .*| /apps/test/| # If in base tag, replace href
}
以或多或少的健壮方式进行更改。