У меня есть текст в текстовом файле, где я хочу удалить все, что находится между строками типа \{{[}
и - включая сами эти строки. Эти две строки{]}\}
можетлежат как на разных линиях, так и на одной линии. В любом случае, настроку, на которой \{{[}
находится начало, я не хочу, чтобы текст перед ней, т. е. слева, был удален - и то же самое касается текста после нее {]}\}
.
Вот пример: дан текстовый файл с содержимым
Bla Bla bla bla \{{[} more bla bla
even more bla bla bla bla.
A lot of stuff might be here.
Bla bla {]}\} finally done.
Nonetheless, the \{{[} show {]}\} goes on.
скрипт должен вернуть другой текстовый файл с содержимым
Bla Bla bla bla finally done.
Nonetheless, the goes on.
К сожалению, эта простая на вид задача оказалась для меня слишком сложной.sed
. Я доволенлюбойрешение на любом языке, при условии, что мне не придется ничего устанавливать на мою стандартную машину Linux (C и часть Java уже установлены).
решение1
С perl
:
perl -0777 -pe 's/\Q\{{[}\E.*?\Q{]}\}\E//gs'
Обратите внимание, что все входные данные загружаются в память перед обработкой.
\Qsomething\E
следует something
рассматривать как буквальную строку, а не как регулярное выражение.
Чтобы изменить обычный файл на месте, добавьте опцию -i
:
perl -0777 -i -pe 's/\Q\{{[}\E.*?\Q{]}\}\E//gs' file.txt
С GNU awk
или mawk
:
awk -v 'RS=\\\\\\{\\{\\[}|\\{\\]}\\\\}' -v ORS= NR%2
Там мы определяемразделитель записейкак любой из этих начальных или конечных маркеров (только gawk
и mawk
поддерживают RS
здесь регулярное выражение). Но нам нужно экранировать символы, которые являются оператором регулярного выражения (обратная косая черта, {
, [
), а также обратную косую черту еще раз, потому что она является специальной в аргументах -v
(используется для таких вещей, как \n
, \b
...), отсюда и многочисленные обратные косые черты.
Тогда все, что нам нужно сделать, это вывести каждую вторую запись. NR%2
будет 1
(истина) для каждой нечетной записи.
Для обоих решений мы предполагаем, что маркеры совпадают и эти разделы не являются вложенными.
Чтобы изменить файл на месте, в последних версиях GNU awk
добавьте -i /usr/share/awk/inplace.awk
опцию ¹.
¹не использовать-i inplace
as сначала gawk
пытается загрузить inplace
расширение (как inplace
или inplace.awk
) из текущего рабочего каталога, где кто-то мог разместить вредоносное ПО. Путь расширения, inplace
поставляемого с, gawk
может различаться в зависимости от системы, см. выводgawk 'BEGIN{print ENVIRON["AWKPATH"]}'
решение2
sed -e:t -e'y/\n/ /;/\\{{\[}/!b' \
-e:N -e'/\\{{\[.*{\]}\\}/!N' \
-e's/\(\\{{\[}\).*\n/\1/;tN' \
-e'y/ /\n/;s/\\{{\[}/& /;ts' \
-e:s -e's/\(\[} [^ ]*\)\({\]}\\}\)/\1 \2/' \
-ets -e's/..... [^ ]* .....//;s/ //g;bt' \
<<""
#Bla Bla {]}\} bla bla \{{[} more bla bla
#even more bla bla bla bla. \{{[}
#
#A lot of stuff might be here.
#hashes are for stupid syntax color only
#Bla bla {]}\} finally {]}\} done.
#
#Nonetheless, the \{{[} show {]}\} goes \{{[} show {]}\} on.
#Bla Bla {]}\} bla bla finally {]}\} done.
#
#Nonetheless, the goes on.
Но вот гораздо лучший способ. Гораздо меньше замен, и те, которые делаются, делаются для пары символов за раз, а не .*
все время. Практически единственный раз, когда .*
это используется, это очистка пространства шаблона между пробелами, когда первое встречающееся начало определенно сопряжено с первым следующим концом. Все остальное время sed
просто D
удаляет столько, сколько нужно, чтобы добраться до следующего встречающегося разделителя. Дон научил меня этому.
sed -etD -e:t -e'/\\{{\[}/!b' \
-e's//\n /;h;D' -e:D \
-e'/^}/{H;x;s/\n.*\n.//;}' \
-ett -e's/{\]}\\}/\n}/' \
-e'/\n/!{$!N;s//& /;}' -eD \
<<""
#Bla Bla {]}\} bla bla \{{[} more bla bla
#even more bla bla bla bla. \{{[}
#
#A lot of stuff might be here.
#hashes are for stupid syntax color only
#Bla bla {]}\} finally {]}\} done.
#
#Nonetheless, the \{{[} show {]}\} goes \{{[} show {]}\} on.
#Bla Bla {]}\} bla bla finally {]}\} done.
#
#Nonetheless, the goes on.
Однако экранированные символы RHS \n
ewline, возможно, придется заменить на буквальные экранированные символы новой строки с обратной косой чертой.
Вот более общая версия:
#!/usr/bin/sed -f
####replace everything between START and END
#branch to :Kil if a successful substitution
#has already occurred. this can only happen
#if pattern space has been Deleted earlier
t Kil
#set a Ret :label so we can come back here
#when we've cleared a START -> END occurrence
#and check for another if need be
:Ret
#if no START, don't
/START/!b
#sigh. there is one. get to work. replace it
#with a newline followed by an S and save
#a copy then Delete up to our S marker.
s||\
S|
h;D
#set the :Kil label. we'll come back here from now
#on until we've definitely got END at the head of
#pattern space.
:Kil
#do we?
/^E/{
#if so, we'll append it to our earlier save
#and slice out everything between the two newlines
#we've managed to insert at just the right points
H;x
s|\nS.*\nE||
}
#if we did just clear START -> END we should
#branch back to :Ret and look for another START
t Ret
#pattern space didnt start w/ END, but is there even
#one at all? if so replace it w/ a newline followed
#by an E so we'll recognize it at the next :Kil
s|END|\
E|
#if that last was successful we'll have a newline
#but if not it means we need to get the next line
#if the last line we've got unmatched pairs and are
#currently in a delete cycle anyway, but maybe we
#should print up to our START marker in that case?
/\n/!{
#i guess so. now that i'm thinking about it
#we'll swap into hold space, and Print it
${ x;P;d
}
#get next input line and add S after the delimiting
#newline because we're still in START state. Delete
#will handle everything up to our marker before we
#branch back to :Kil at the top of the script
N
s||&S|
}
#now Delete will slice everything from head of pattern space
#to the first occurring newline and loop back to top of script.
#because we've definitely made successful substitutions if we
#have a newline at all we'll test true and branch to :Kil
#to go again until we've definitely got ^E
D
...без комментариев...
#!/usr/bin/sed -f
t Kil
:Ret
/START/!b
s||\
S|
h;D
:Kil
/^E/{
H;x
s|\nS.*\nE||
}
t Ret
s|END|\
E|
/\n/!{
${ x;P;d
}
N
s||&S|
}
D
Я скопировал прокомментированную версию в буфер обмена и сделал:
{ xsel; echo; } >se.sed
chmod +x se.sed
./se.sed <se.sed
#!/usr/bin/sed -f
####replace everything between
#branch to :Kil if a successful substitution
#has already occurred. this can only happen
#if pattern space has been Deleted earlier
t Kil
#set a Ret :label so we can come back here
#when we've cleared a occurrence
#and check for another if need be
:Ret
#if no at the head of
#pattern space.
:Kil
#do we?
/^E/{
#if so, we'll append it to our earlier save
#and slice out everything between the two newlines
#we've managed to insert at just the right points
H;x
s|\nS.*\nE||
}
#if we did just clear we should
#branch back to :Ret and look for another , but is there even
#one at all? if so replace it w/ a newline followed
#by an E so we'll recognize it at the next :Kil
s|END|\
E|
#if that last was successful we'll have a newline
#but if not it means we need to get the next line
#if the last line we've got unmatched pairs and are
#currently in a delete cycle anyway, but maybe we
#should print up to our
решение3
Если ваш файл — test.txt, вы можете использовать:
sed ':a;N;$!ba;s/\n/ /g' test.txt|sed 's/\\{{\[}.*{\]}\\}//'
Первая команда sed удаляет все переводы строк, вторая удаляет текст внутри тегов.
Я не знаю, нужно ли вам более общее решение.