Избегайте строки для шаблона замены sed - PullRequest
281 голосов
/ 02 января 2009

В моем скрипте bash у меня есть внешняя (полученная от пользователя) строка, которую я должен использовать в шаблоне sed.

REPLACE="<funny characters here>"
sed "s/KEYWORD/$REPLACE/g"

Как я могу избежать строки $REPLACE, чтобы она была безопасно принята sed в качестве буквальной замены?

ПРИМЕЧАНИЕ: KEYWORD - немая подстрока без совпадений и т. Д. Она не предоставляется пользователем.

Ответы [ 13 ]

240 голосов
/ 24 апреля 2010

Предупреждение : не учитывает переводы строки. Для более подробного ответа см. этот SO-вопрос . (Спасибо, Эд Мортон и Никлас Питер)

Обратите внимание, что избегать всего - плохая идея. Седу нужно много символов для экранирования, чтобы получить их особое значение. Например, если вы укажете цифру в строке замены, она превратится в обратную ссылку.

Как сказал Бен Бланк, в замещающей строке необходимо экранировать только три символа (экранирует себя, косую черту в конце оператора и & для замены всего):

sed -e 's/[\/&]/\\&/g'

Если вам когда-либо понадобится экранировать строку KEYWORD, вам понадобится следующее:

sed -e 's/[]\/$*.^[]/\\&/g'

Помните, что если вы используете символ, отличный от /, в качестве разделителя, вам необходимо заменить косую черту в приведенных выше выражениях на используемый вами символ. См. Комментарий PeterJCLaw для объяснения.

Отредактировано: Из-за некоторых угловых случаев, ранее не учтенных, приведенные выше команды менялись несколько раз. Проверьте историю изменений для деталей.

78 голосов
/ 30 апреля 2015

Команда sed позволяет использовать другие символы вместо / в качестве разделителя:

sed 's#"http://www\.fubar\.com"#URL_FUBAR#g'

Двойные кавычки не проблема.

41 голосов
/ 02 января 2009

Единственными тремя литеральными символами, которые специально обрабатываются в предложении замены, являются / (для закрытия предложения), \ (для экранирования символов, обратной ссылки и т. Д.) И & (для включения соответствия в замену). Поэтому все, что вам нужно сделать, это экранировать эти три символа:

sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"

Пример:

$ export REPLACE="'\"|\\/><&!"
$ echo fooKEYWORDbar | sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"
foo'"|\/><&!bar
31 голосов
/ 06 мая 2012

Основываясь на регулярных выражениях Pianosaurus, я создал функцию bash, которая экранирует и ключевое слово, и замену.

function sedeasy {
  sed -i "s/$(echo $1 | sed -e 's/\([[\/.*]\|\]\)/\\&/g')/$(echo $2 | sed -e 's/[\/&]/\\&/g')/g" $3
}

Вот как вы его используете:

sedeasy "include /etc/nginx/conf.d/*" "include /apps/*/conf/nginx.conf" /etc/nginx/nginx.conf
16 голосов
/ 23 января 2014

Уже поздно отвечать ... но есть гораздо более простой способ сделать это. Просто измените разделитель (то есть символ, который разделяет поля). Итак, вместо s/foo/bar/ вы пишете s|bar|foo.

И вот простой способ сделать это:

sed 's|/\*!50017 DEFINER=`snafu`@`localhost`\*/||g'

Полученный результат лишен этого неприятного предложения DEFINER.

11 голосов
/ 30 декабря 2015

Оказывается, вы задаете не тот вопрос. Я тоже задал не тот вопрос. Причина, по которой это неправильно, - начало первого предложения: «В моем bash сценарии ...».

У меня был тот же вопрос и я сделал ту же ошибку. Если вы используете bash, вам не нужно использовать sed для замены строк (и это намного чище для использования функции замены, встроенной в bash).

Вместо чего-то вроде, например:

function escape-all-funny-characters() { UNKNOWN_CODE_THAT_ANSWERS_THE_QUESTION_YOU_ASKED; }
INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A="$(escape-all-funny-characters 'KEYWORD')"
B="$(escape-all-funny-characters '<funny characters here>')"
OUTPUT="$(sed "s/$A/$B/g" <<<"$INPUT")"

Вы можете использовать исключительно функции bash:

INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A='KEYWORD'
B='<funny characters here>'
OUTPUT="${INPUT//"$A"/"$B"}"
1 голос
/ 15 ноября 2014

Используйте awk - это чище:

$ awk -v R='//addr:\\file' '{ sub("THIS", R, $0); print $0 }' <<< "http://file:\_THIS_/path/to/a/file\\is\\\a\\ nightmare"
http://file:\_//addr:\file_/path/to/a/file\\is\\\a\\ nightmare
0 голосов
/ 06 октября 2017

Более простой способ сделать это - просто построить строку перед рукой и использовать ее в качестве параметра для sed

rpstring="s/KEYWORD/$REPLACE/g"
sed -i $rpstring  test.txt
0 голосов
/ 30 сентября 2017

У меня есть улучшение по сравнению с функцией sedeasy, которая ломается со специальными символами, такими как tab.

function sedeasy_improved {
    sed -i "s/$(
        echo "$1" | sed -e 's/\([[\/.*]\|\]\)/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/$(
        echo "$2" | sed -e 's/[\/&]/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/g" "$3"
}

Так что же отличается? $1 и $2 заключены в кавычки, чтобы избежать расширения оболочки и сохранить символы табуляции или двойные пробелы.

Дополнительный трубопровод | sed -e 's:\t:\\t:g' (мне нравится : в качестве токена), который преобразует вкладку в \t.

0 голосов
/ 01 сентября 2017

Если вы просто хотите заменить значение переменной в команде sed, просто удалите Пример:

sed -i 's/dev-/dev-$ENV/g' test to sed -i s/dev-/dev-$ENV/g test
...