Как обеспечить, чтобы строка, интерполированная в подстановку `sed`, экранировала все метасхемы


21

У меня есть скрипт, который читает текстовый поток и генерирует файл команд sed, который позже запускается sed -f. Сгенерированные команды sed выглядят так:

s/cid:image002\.gif@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1922/g
s/cid:image003\.gif@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1923/g
s/cid:image004\.jpg@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1924/g

Предположим, что скрипт, который генерирует sedкоманды, выглядит примерно так:

while read cid fileid
do
    cidpat="$(echo $cid | sed -e s/\\./\\\\./g)"
    echo 's/'"$cidpat"'/https:\/\/mysite.com\/files\/'"$fileid"'/g' >> sedscr
done

Как я могу улучшить сценарий, чтобы гарантировать, что все метасимволы регулярных выражений в cidстроке экранированы и интерполированы правильно?

Ответы:


24

Чтобы экранировать переменные, которые будут использоваться в левой и правой частях sкоманды sed(здесь $lhsи $rhsсоответственно), вы должны сделать:

escaped_lhs=$(printf '%s\n' "$lhs" | sed 's:[][\/.^$*]:\\&:g')
escaped_rhs=$(printf '%s\n' "$rhs" | sed 's:[\/&]:\\&:g;$!s/$/\\/')

sed "s/$escaped_lhs/$escaped_rhs/"

Обратите внимание, что $lhsне может содержать символ новой строки.

То есть на LHS экранируют все операторы регулярных выражений ( ][.^$*), сам экранирующий символ ( \) и разделитель ( /).

На RHS вам нужно только экранировать &, разделитель, обратную косую черту и символ новой строки (что вы делаете, вставляя обратную косую черту в конце каждой строки, кроме последней ( $!s/$/\\/)).

Это предполагает, что вы используете /в качестве разделителя в своих sed sкомандах и что вы не включаете расширенные RE с -r(GNU sed/ ssed/ ast/ busybox sed) или -E(BSD ast, недавний GNU, недавний busybox) или PCRE с -R( ssed) или дополненные RE с -A/ -X( ast), которые у всех есть дополнительные операторы RE.

Несколько основных правил при работе с произвольными данными:

  • Не использовать echo
  • процитируйте свои переменные
  • рассмотреть влияние локали (особенно ее набора символов: важно, чтобы экранирующие sed команды выполнялись в той же локали, что и sedкоманда, например, с использованием экранированных строк (и с той же sedкомандой))
  • не забывайте о символе перевода строки (здесь вы можете проверить, есть ли в $lhsнем символ, и принять меры).

Другой вариант - использовать perlвместо sedи передавать строки в среде и использовать операторы \Q/ \E perlregexp для буквального восприятия строк:

A="$lhs" B="$rhs" perl -pe 's/\Q$ENV{A}\E/$ENV{B}/g'

perl(по умолчанию) не будет зависеть от набора символов локали, так как в приведенном выше примере он рассматривает только строки как массивы байтов, не заботясь о том, какие символы (если они есть) они могут представлять для пользователя. С помощью sedэтого вы можете добиться того же, установив локаль в Cwith LC_ALL=Cдля всех sedкоманд (хотя это также повлияет на язык сообщений об ошибках, если таковые имеются).


Что если мне нужно избежать двойных кавычек?
Менон

@ Менон, двойные кавычки не особенные sed, вам не нужно избегать их.
Стефан Шазелас

Это не может быть использовано для сопоставления с шаблоном, используя подстановочный знак, не так ли?
Менон

@ Менон, нет, сопоставление с шаблоном, как с find, -nameотличается от регулярных выражений. Там вам нужно только бежать ?, *обратный слеш и[
Стефан Шазелас
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.