Избегайте строки для шаблона замены sed


317

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

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

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

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


13
Вы пытаетесь избежать проблемы "Столики Бобби", если они говорят "/ g -e 's / ПАРОЛЬ =. * / ПАРОЛЬ = abc / g'"?
Пол Томблин

2
Если вы используете bash, вам не нужен sed. Просто используйтеoutputvar="${inputvar//"$txt2replace"/"$txt2replacewith"}".
destenson

@destenson Я думаю, вы не должны помещать две переменные вне кавычек. Bash может читать переменные внутри двойных кавычек (в вашем примере пробел может все испортить).
Камило Мартин

2
Смотрите также: stackoverflow.com/q/29613304/45375
mklement0

1
@CamiloMartin, см. Мой комментарий к моему собственному ответу. Кавычки внутри $ {} не совпадают с кавычками внутри. Две переменные не находятся вне кавычек.
Дестенсон

Ответы:


268

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

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

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

ESCAPED_REPLACE=$(printf '%s\n' "$REPLACE" | sed -e 's/[\/&]/\\&/g')
# Now you can use ESCAPED_REPLACE in the original sed statement
sed "s/KEYWORD/$ESCAPED_REPLACE/g"

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

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

И может быть использован:

KEYWORD="The Keyword You Need";
ESCAPED_KEYWORD=$(printf '%s\n' "$KEYWORD" | sed -e 's/[]\/$*.^[]/\\&/g');

# Now you can use it inside the original sed statement to replace text
sed "s/$ESCAPED_KEYWORD/$ESCAPED_REPLACE/g"

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

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


17
Стоит отметить, что вы можете избежать экранирования, не используя их в качестве разделителей. Большинство (всех?) Версий sed позволяют использовать любой символ, если он соответствует шаблону: $ echo 'foo / bar' | sed s _ / _: _ # foo: bar
PeterJCLaw

2
sed -e 's / (\ / \ | \\\ | &) / \\ & / g' не работает для меня на OSX, но это работает: sed 's / ([\\\ / &]) / \\ & / g 'и это немного короче.
jcoffland

1
Для шаблона поиска KEYWORDв GNU sed есть еще 2 символа ^, $не упомянутые выше:s/[]\/$*.^|[]/\\&/g
Peter.O

1
@Jesse: исправлено. Фактически, это ошибка, против которой я предупреждаю в самом первом абзаце. Я думаю, я не практикую то, что я проповедую.
Пианозавр

1
@NeronLeVelu: Я не уверен, что знаю, что вы имеете в виду, но "не имеет никакого особого значения в каналах или переменных. Он анализируется оболочкой перед выполнением результата, поэтому двойные кавычки внутри переменных безопасны. Например, попробуйте запустить A='foo"bar' echo $A | sed s/$A/baz/в bash. Двойные кавычки обрабатываются так же, как 'foo' и 'bar' вокруг него
Pianosaurus

92

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

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

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


5
Вам все еще нужно бежать, .что в противном случае имеет особое значение. Я отредактировал твой ответ.
Ypid

Я только что попытался сделать: sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' fileс, sed '|CLIENTSCRIPT="foo"|a CLIENTSCRIPT2="hello"' fileи это не делает то же самое.
Дмитрий Коприва,

1
Поскольку это относится только к замене, это должно означать: sкоманда (как в замене) sed позволяет вам использовать другие символы вместо / в качестве разделителя. Кроме того, это будет ответом на то, как использовать sed в URL с косой чертой. Он не отвечает на вопрос OP, как избежать строки, введенной пользователем, которая может содержать /, \, но также #, если вы решите использовать это. И, кроме того, URI тоже может содержать #
papo

2
это изменило мою жизнь! Спасибо!
Францискан Сантос

48

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

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

Также перевод строки, я думаю. Как мне избежать перевода строки?
Александр Гладыш

2
Будьте осторожны с поведением эха по умолчанию в отношении обратной косой черты. В bash эхо по умолчанию не интерпретирует экранирование от обратной косой черты, что служит здесь цели. В dash (sh), с другой стороны, echo интерпретирует escape-символы обратной косой черты и, насколько я знаю, не имеет возможности подавить это. Поэтому в dash (sh) вместо echo $ x выполните printf '% s \ n' $ x.
Юсеф Эльдакар

Кроме того, всегда используйте параметр -r при чтении, чтобы рассматривать обратную косую черту в пользовательском вводе как литералы.
Юсеф Эльдакар

Для кросс-платформенной совместимости с другими оболочками вы должны обратиться к этому документу относительно замены специальных символов sed: grymoire.com/Unix/Sed.html#toc-uh-62
Dejay Clayton

2
@Drux Три символа являются единственными специальными в предложении замены . Гораздо больше особенного в предложении шаблона.
Ленц

33

Основываясь на регулярных выражениях 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

3
Спасибо! если кто-то еще получит синтаксическую ошибку при попытке использовать его, как и я, просто не забудьте запустить его, используя bash, а не sh
Константин Переяслов

1
Есть ли функция, которая просто убирает строку для sed, а не оборачивает вокруг sed?
CMCDragonkai

Эй, просто общее предупреждение относительно запуска каналов с эхом, подобным этому: Некоторые (большинство?) Реализации echo принимают параметры (см. man echo), Заставляя канал работать неожиданно, когда ваш аргумент $1начинается с тире. Вместо этого вы можете начать свою трубу с printf '%s\n' "$1".
Пианозавр

17

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

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

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

Результирующий вывод лишен этого неприятного предложения DEFINER.


10
Нет, &и `` все еще должен быть экранирован, как и разделитель, какой бы ни был выбран.
Мирабилось

3
Это решило мою проблему, так как у меня были символы "/" в строке замены. Спасибо чувак!
Евгений Гольдин

работает для меня. Что я делаю, так это пытаюсь скрыться $в строке, которая должна быть изменена, и сохранить значение $в строке замены. скажем, я хочу изменить $XXXзначение переменной $YYY, sed -i "s|\$XXX|$YYY|g" fileработает отлично.
Hakunami

11

Оказывается, вы задаете не тот вопрос. Я тоже задал не тот вопрос. Причина, по которой это неправильно, - начало первого предложения: «В моем сценарии 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"}"

Кстати, синтаксическая подсветка здесь неправильная. Внешние цитаты совпадают, а внутренние цитаты совпадают. Другими словами, это выглядит $Aи $Bне цитируется, но это не так. Кавычки внутри ${}не совпадают с кавычками снаружи.
Дестенсон

На самом деле вам не нужно цитировать правую часть задания (если вы не хотите делать что-то подобное var='has space') - OUTPUT=${INPUT//"$A"/"$B"}это безопасно.
Бенджамин В.

На самом деле вам не нужно цитировать правую часть задания (если только вы не хотите, чтобы оно работало в реальном мире, а не просто как игрушечный сценарий, показывающий ваше безумное умение). Я всегда пытаюсь процитировать каждое расширение переменной, которое не хочу интерпретировать оболочкой, если только у меня нет особой причины этого не делать Таким образом, вещи имеют тенденцию ломаться реже, особенно когда они снабжены новым или неожиданным вводом.
Дестенсон

1
См. Руководство : «Все значения подвергаются расширению тильды, расширению параметров и переменных, подстановке команд, расширению арифметики и удалению кавычек (подробно описано ниже)». То есть так же, как в двойных кавычках.
Бенджамин В.

1
Что если вам нужно использовать sed для файла?
Эфрен

1

Используйте 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

2
Проблема в awkтом, что он не имеет ничего подобного sed -i, что очень удобно в 99% случаев.
Тино

Это шаг в правильном направлении, но awk по-прежнему интерпретирует некоторые метасимволы в вашей замене, поэтому он по-прежнему небезопасен для ввода пользователем.
Джереми Хуискамп

0

Вот пример AWK, который я использовал некоторое время назад. Это AWK, который печатает новые AWKS. AWK и SED схожи, это может быть хорошим шаблоном.

ls | awk '{ print "awk " "'"'"'"  " {print $1,$2,$3} " "'"'"'"  " " $1 ".old_ext > " $1 ".new_ext"  }' > for_the_birds

Это выглядит чрезмерно, но каким-то образом эта комбинация кавычек работает так, чтобы печатать как литералы Тогда, если я правильно помню, переменные просто заключены в такие кавычки: «$ 1». Попробуйте, дайте мне знать, как это работает с SED.


0

У меня есть улучшение по сравнению с функцией 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.


Но посмотрите мой комментарий к ответу sedeasy относительно использования echo в каналах.
Пианозавр

0

Это коды перехода, которые я нашел:

* = \x2a
( = \x28
) = \x29

" = \x22
/ = \x2f
\ = \x5c

' = \x27
? = \x3f
% = \x25
^ = \x5e

-1

не забывайте все удовольствие, которое происходит с ограничением оболочки вокруг "и"

так (в кш)

Var=">New version of \"content' here <"
printf "%s" "${Var}" | sed "s/[&\/\\\\*\\"']/\\&/g' | read -r EscVar

echo "Here is your \"text\" to change" | sed "s/text/${EscVar}/g"

именно то направление, в котором я нуждался, чтобы избежать результатов поиска, найденных через Google, поэтому он может быть полезен для кого-то - в конце концов - sed "s / [& \\\ * \\" \ '\ "') (] / \\ & / g '
MolbOrg

-1

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

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

-2

Если случается, что вы генерируете случайный пароль для передачи sedвместо шаблона, то вы должны быть осторожны с тем, какой набор символов в случайной строке. Если вы выберете пароль, созданный путем кодирования значения в виде base64, то будет присутствовать только символ, который возможен в base64 и также является специальным символом в sedшаблоне замены. Этот символ "/", и его легко удалить из пароля, который вы генерируете:

# password 32 characters log, minus any copies of the "/" character.
pass=`openssl rand -base64 32 | sed -e 's/\///g'`;

-4

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

rpstring="s/KEYWORD/$REPLACE/g"
sed -i $rpstring  test.txt

Сбои и чрезвычайно опасны, так как REPLACE предоставляется пользователем: REPLACE=/даетsed: -e expression #1, char 12: unknown option to `s'
Тино
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.