Регулярное выражение Sed, влияющее на содержимое после регулярных выражений

У меня есть 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

2 ответа

Решение

Так как * жадный, .* в =\s*\".*\"\s*> соответствует крайнему правому > имеется в наличии.

Вы можете использовать одинарные кавычки вокруг вашей команды, поэтому вам не нужно использовать \" для двойных кавычек. Тогда вместо ".*", ты можешь использовать "[^"]*", который соответствует только следующей двойной кавычке.

Это сделало бы вашу команду в

sed 's:<base href\s*=\s*"[^"]*"\s*>:<base href="/apps/test/">:g'

Однако манипулирование HTML с помощью sed и регулярных выражений является вечно хрупким и сломается при первой же возможности. Вы можете использовать синтаксический анализатор XML/HTML, такой как xmllint, см. Ответ Романа; альтернативой являются HTML-XML-утилиты W3C с его hxpipe а также hxunpipe команды.

Эти команды анализируют ваш HTML и превращают его в формат, легко обрабатываемый с помощью sed, awk & friends, а затем снова превращают его в 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

чтобы повернуть / в href для base тег в /apps/test/мы могли бы сделать это:

$ hxpipe infile.html \
    | sed '/Ahref CDATA/{N;/\n(base$/s|\(CDATA\) .*|\1 /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\) .*|\1 /apps/test/|}'

или лучше читаемый

/Ahref CDATA/ {                                # If line matches this
    N                                          # Append next line
    /\n(base$/ s|\(CDATA\) .*|\1 /apps/test/|  # If in base tag, replace href
}

более или менее надежным образом вносит изменения.

Единственный правильный способ обработки данных 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>

Чтобы изменить файл на месте добавить -L опция: xmlstarlet ed -L -u ....

Другие вопросы по тегам