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 及其 hxpipehxunpipe 命令。

这些命令解析您的 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
}

以或多或少的健壮方式进行更改。