Совпадение любого символа (включая символы новой строки) в sed
У меня есть команда sed, которую я хочу запустить на огромном, ужасном, уродливом HTML файле, который был создан из документа Microsoft Word. Все, что он должен сделать, это удалить любой экземпляр строки
style='text-align:center; color:blue;
exampleStyle:exampleValue'
Команда sed, которую я пытаюсь изменить,
sed "s/ style='[^']*'//" fileA > fileB
Это отлично работает, за исключением того, что всякий раз, когда в соответствующем тексте есть новая строка, она не совпадает. Есть ли модификатор для sed или что-то, что я могу сделать, чтобы принудительно выполнить сопоставление любого символа, включая символы новой строки?
Я понимаю, что регулярные выражения ужасны в XML и HTML, blah blah blah, но в этом случае строковые шаблоны хорошо сформированы в том, что атрибуты стиля всегда начинаются с одной цитаты и заканчиваются одной цитатой. Поэтому, если бы я мог просто решить проблему новой строки, я мог бы сократить размер HTML более чем на 50% только с помощью одной команды.
В итоге оказалось, что Sinan Ünür perl script работал лучше всего. Это было почти мгновенно, и это уменьшило размер файла от 2,3 МБ до 850 тыс. Хороший ol 'Perl...
Ответы
Ответ 1
sed
просматривает строку входного файла по строке, что означает, что, как я понимаю, то, что вы хотите, невозможно в sed
.
Вы можете использовать следующий Perl script (untested), хотя:
#!/usr/bin/perl
use strict;
use warnings;
{
local $/; # slurp mode
my $html = <>;
$html =~ s/ style='[^']*'//g;
print $html;
}
__END__
Один лайнер будет:
$ perl -e 'local $/; $_ = <>; s/ style=\047[^\047]*\047//g; print' fileA > fileB
Ответ 2
Sed считывает входные строки за строкой, поэтому выполнить непростую обработку по одной строке... но это тоже не невозможно, вам нужно использовать ветвление sed. Следующее будет работать, я прокомментировал его, чтобы объяснить, что происходит (не самый читаемый синтаксис!):
sed "# if the line matches 'style='', then branch to label,
# otherwise process next line
/style='/b style
b
# the line contains 'style', try to do a replace
: style
s/ style='[^']*'//
# if the replace worked, then process next line
t
# otherwise append the next line to the pattern space and try again.
N
b style
" fileA > fileB
Ответ 3
Вы можете удалить все CR/LF с помощью tr
, запустить sed
, а затем импортировать в редактор, который автоматически форматируется.
Ответ 4
Вы можете попробовать следующее:
awk '/style/&&/exampleValue/{
gsub(/style.*exampleValue\047/,"")
}
/style/&&!/exampleValue/{
gsub(/style.* /,"")
f=1
}
f &&/exampleValue/{
gsub(/.*exampleValue\047 /,"")
f=0
}
1
' file
Вывод:
# more file
this is a line
style='text-align:center; color:blue; exampleStyle:exampleValue'
this is a line
blah
blah
style='text-align:center; color:blue;
exampleStyle:exampleValue' blah blah....
# ./test.sh
this is a line
this is a line
blah
blah
blah blah....
Ответ 5
Другой способ:
$ cat toreplace.txt
I want to make \
this into one line
I also want to \
merge this line
$ sed -e 'N;N;s/\\\n//g;P;D;' toreplace.txt
Вывод:
I want to make this into one line
I also want to merge this line
N
загружает другую строку, P
печатает пространство шаблонов до первой новой строки, а D
удаляет пространство шаблонов до первой новой строки.
Ответ 6
Удалить элементы XML через несколько строк
Мой вариант использования был почти таким же, но мне нужно было сопоставить открывающие и закрывающие теги из элементов XML и полностью удалять их --including, что бы ни было внутри.
<xmlTag whatever="parameter that holds in the tag header">
<whatever_is_inside/>
<InWhicheverFormat>
<AcrossSeveralLines/>
</InWhicheverFormat>
</xmlTag>
Тем не менее, sed
работает в одной строке. Здесь мы обманываем его, чтобы добавить последующие строки к текущей, чтобы мы могли редактировать все строки, которые нам нравятся, а затем переписать вывод (\n
- это допустимый символ, который можно вывести с помощью sed
, чтобы снова разделить строки).
Вдохновленный ответом @beano и другим ответом в Unix stackExchange, я создал свою рабочую "программу" sed:
sed -s --in-place=.back -e '/\(^[ ]*\)<xmlTag/{ # whenever you encounter the xmlTag
$! { # do
:begin # label to return to
N; # append next line
s/\(^[ ]*\)<\(xmlTag\)[^·]\+<\/\2>//; # Attempt substitution (elimination) of pattern
t end # if substitution succeeds, jump to :end
b begin # unconditional jump to :begin to append yet another line
:end # label to mark the end
}
}' myxmlfile.xml
Некоторые объяснения:
- Я сопоставляю
<xmlTag
, не закрывая >
, потому что мой элемент XML содержит параметры.
- То, что предшествует
<xmlTag
, является очень полезным элементом RegExp для соответствия любому существующему отступу: \(^[ ]*\)
, так что вы можете позже вывести его только с помощью \1
(даже если в этот раз он не был нужен).
- Добавление
;
в нескольких местах так, что sed
поймет, что команда (N
, s
или что-то еще) заканчивается там, а следующие символы являются другой командой.
- большая часть моей проблемы заключалась в попытке найти RegExp, который соответствовал бы "чему-либо промежуточному". В конце концов я согласился на что угодно, кроме
·
(т.е. [^·]\+
), рассчитывая на то, что этот символ не будет ни в одном из файлов данных. Мне нужно было убрать +
, потому что он специально для GNU sed.
- мои исходные файлы остаются как .back, на случай, если что-то пойдет не так --tests, но после modification-- все-таки произойдет сбой, и они легко помечаются контролем версий для массового удаления.
Я использую этот вид sed-автоматизации для развития файлов .XML, которые мы используем с сериализованными данными для запуска наших модульных и интеграционных тестов. Всякий раз, когда наши классы меняются (свободные поля или поля усиления), данные должны обновляться. Я делаю это с одним "find", который выполняет sed-автоматизацию в файлах, которые содержат измененный класс. Мы храним сотни XML файлов данных.