Ответ 1
Заметка:
- Если вы ищете готовые функции на основе методов, обсуждаемых в этом ответе:
- функции
bash
которые обеспечивают надежное экранирование даже в многострочных подстановках, можно найти в нижней части этой публикации (плюс решениеperl
которое использует встроенную поддержкуperl
для такого экранирования). - Ответ @EdMorton содержит инструмент (скрипт
bash
), который надежно выполняет подстановки single-.
- функции
- Все фрагменты предполагают
bash
как оболочку (возможны переформулировки, совместимые с POSIX):
single- линейные решения
Вывод строкового литерала для использования в качестве регулярного выражения в sed
:
Чтобы получить кредит, когда кредит должен быть: я нашел регулярное выражение, используемое ниже в этом ответе.
Предполагая, что строка поиска является строкой строки single-:
search='abc\n\t[a-z]\+\([^ ]\)\{2,3\}\3' # sample input containing metachars.
searchEscaped=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<<"$search") # escape it.
sed -n "s/$searchEscaped/foo/p" <<<"$search" # if ok, echoes 'foo'
- Каждый символ, кроме
^
, помещается в свой собственный набор символов[...]
чтобы рассматривать его как литерал.- Заметим, что
^
- один символ. вы не можете представлять как[^]
, потому что в этом месте (отрицание) оно имеет особое значение.
- Заметим, что
- Тогда,
^
chars. экранируются как\^
.
Этот подход является надежным, но не эффективным.
Устойчивость исходит из того, что вы не пытаетесь предвидеть все специальные символы регулярных выражений, которые будут варьироваться в зависимости от диалектов регулярных выражений, но сосредоточиться только на двух функциях, общих для всех диалектов regex:
- возможность указывать буквенные символы внутри набора символов.
- возможность избежать буквальное
^
а\^
Экранирование строкового литерала для использования в качестве замены строки в команде sed
s///
:
Строка замены в команде sed
s///
не является регулярным выражением, но она распознает заполнители, которые ссылаются либо на всю строку, сопоставляемую с выражением regex (&
), либо с конкретными результатами группы захвата по индексу (\1
, \2
,...), поэтому они должны быть экранированы вместе с (обычным) разделителем регулярных выражений, /
.
Предполагая, что строка замены является строкой строки single-:
replace='Laurel & Hardy; PS\2' # sample input containing metachars.
replaceEscaped=$(sed 's/[&/\]/\\&/g' <<<"$replace") # escape it
sed -n "s/\(.*\) \(.*\)/$replaceEscaped/p" <<<"foo bar" # if ok, outputs $replace as is
Решения MULTI-line
Выключение строкового литерала MULTI-LINE для использования в качестве регулярного выражения в sed
:
Примечание. Это имеет смысл только в том случае, если перед попыткой сопоставить несколько строк ввода (возможно, ВСЕ).
Поскольку такие инструменты, как sed
и awk
работают по одной строке за раз по умолчанию, необходимы дополнительные шаги, чтобы заставить их читать более одной строки за раз.
# Define sample multi-line literal.
search='/abc\n\t[a-z]\+\([^ ]\)\{2,3\}\3
/def\n\t[A-Z]\+\([^ ]\)\{3,4\}\4'
# Escape it.
searchEscaped=$(sed -e 's/[^^]/[&]/g; s/\^/\\^/g; $!a\'$'\n''\\n' <<<"$search" | tr -d '\n') #'
# Use in a Sed command that reads ALL input lines up front.
# If ok, echoes 'foo'
sed -n -e ':a' -e '$!{N;ba' -e '}' -e "s/$searchEscaped/foo/p" <<<"$search"
- Новые строки в многострочных входных строках должны быть переведены в строки
'\n'
, а именно, как новые строки закодированы в регулярном выражении. -
$!a\'$'\n''\\n'
присоединяет строку'\n'
к каждой выходной строке, но последняя (последняя новая строка игнорируется, поскольку она была добавлена<<<
) -
tr -d '\n
затем удаляет все строки строки из строки (sed
добавляет каждый раз, когда печатает свое пространство с образцом), эффективно заменяя все строки новой строки на входе строками'\n'
.
-
-e ':a' -e '$!{N;ba' -e '}'
- это форма, соответствующая POSIX, с надписьюsed
которая считывает все входные строки цикла, поэтому оставляя последующие команды для работы на всех ввод строк сразу.- Если вы используете GNU
sed
(только), вы можете использовать его-z
чтобы упростить чтение всех входных строк одновременно:sed -z "s/$searchEscaped/foo/" <<<"$search"
- Если вы используете GNU
Исключение строкового литерала MULTI-LINE для использования в качестве замены в команде sed
s///
:
# Define sample multi-line literal.
replace='Laurel & Hardy; PS\2
Masters\1 & Johnson\2'
# Escape it for use as a Sed replacement string.
IFS= read -d '' -r < <(sed -e ':a' -e '$!{N;ba' -e '}' -e 's/[&/\]/\\&/g; s/\n/\\&/g' <<<"$replace")
replaceEscaped=${REPLY%$'\n'}
# If ok, outputs $replace as is.
sed -n "s/\(.*\) \(.*\)/$replaceEscaped/p" <<<"foo bar"
- Новые строки во входной строке должны сохраняться как фактические строки новой строки, но
\
-e scaped. -
-e ':a' -e '$!{N;ba' -e '}'
- это POSIX-совместимая формаsed
идиомы, которая считывает все входные строки цикла. -
's/[&/\]/\\&/g
экранирует все&
,\
и/
экземпляры, как в линейном решении single-. -
s/\n/\\&/g'
затем\
-prefixes все фактические строки новой строки. -
IFS= read -d '' -r
используется для чтения вывода командыsed
как есть (чтобы избежать автоматического удаления конечных строк, которые могла бы выполнять замена команд ($(...)
)). -
${REPLY%$'\n'}
затем удаляет одну конечную новую строку, которую<<<
неявно добавляет к вводу.
bash
на основе вышеизложенного (для sed
):
-
quoteRe()
кавычки (escapes) для использования в регулярном выражении -
quoteSubst()
для использования в строке подстановки вызоваs///
. - оба правильно управляют многострочным входом
- Обратите внимание, что поскольку
sed
по умолчанию использует по одной строке по умолчанию, использованиеquoteRe()
с многострочными строками имеет смысл только в командахsed
которые явно читают сразу несколько (или всех) строк. - Кроме того, использование подстановок (
$(...)
) для вызова функций не будет работать для строк, имеющих завершающие символы новой строки; в этом случае используйте что-то вродеIFS= read -d '' -r escapedValue <(quoteSubst "$value")
- Обратите внимание, что поскольку
# SYNOPSIS
# quoteRe <text>
quoteRe() { sed -e 's/[^^]/[&]/g; s/\^/\\^/g; $!a\'$'\n''\\n' <<<"$1" | tr -d '\n'; }
# SYNOPSIS
# quoteSubst <text>
quoteSubst() {
IFS= read -d '' -r < <(sed -e ':a' -e '$!{N;ba' -e '}' -e 's/[&/\]/\\&/g; s/\n/\\&/g' <<<"$1")
printf %s "${REPLY%$'\n'}"
}
Пример:
from=$'Cost\(*):\n$3.' # sample input containing metachars.
to='You & I'$'\n''eating A\1 sauce.' # sample replacement string with metachars.
# Should print the unmodified value of $to
sed -e ':a' -e '$!{N;ba' -e '}' -e "s/$(quoteRe "$from")/$(quoteSubst "$to")/" <<<"$from"
Обратите внимание на использование -e ':a' -e '$!{N;ba' -e '}'
чтобы прочитать все входные данные сразу, так что работает многострочная подстановка.
раствор perl
:
Perl имеет встроенную поддержку для экранирования произвольных строк для использования в регулярном выражении: quotemeta()
или ее эквивалентная \Q...\E
котировка.
Такой подход одинаковый как для single-, так и для многострочных строк; например:
from=$'Cost\(*):\n$3.' # sample input containing metachars.
to='You owe me $1/$& for'$'\n''eating A\1 sauce.' # sample replacement string w/ metachars.
# Should print the unmodified value of $to.
# Note that the replacement value needs NO escaping.
perl -s -0777 -pe 's/\Q$from\E/$to/' -- -from="$from" -to="$to" <<<"$from"
-
Обратите внимание на использование
-0777
для чтения всех входных данных сразу, так что работает многострочная подстановка. -
-s
Опция позволяет размещение-<var>=<val>
-s мозоль определение Perl переменного следующий--
после того, как сценарий перед любым именем операндов.