Избегайте строки для шаблона замены sed
В моем bash-скрипте у меня есть внешняя (полученная от пользователя) строка, которую я должен использовать в шаблоне sed.
REPLACE="<funny characters here>"
sed "s/KEYWORD/$REPLACE/g"
Как я могу избежать $REPLACE
строка, так что было бы безопасно принято sed
как буквальная замена?
ПРИМЕЧАНИЕ: KEYWORD
является немой подстрокой без совпадений и т. д. Она не предоставлена пользователем.
19 ответов
Предупреждение: это не учитывает переводы строки. Для более подробного ответа см. Этот SO-вопрос. (Спасибо, Эд Мортон и Никлас Питер)
Обратите внимание, что избегать всего - плохая идея. Седу нужно много символов, чтобы избежать их особого значения. Например, если вы укажете цифру в строке замены, она превратится в обратную ссылку.
Как сказал Бен Бланк, в замещающей строке необходимо экранировать только три символа (экранирование, косая черта для конца оператора и & для замены всего):
sed -e 's/[\/&]/\\&/g'
Если вам когда-либо нужно сбежать KEYWORD
строка, вам нужно следующее:
sed -e 's/[]\/$*.^[]/\\&/g'
Помните, если вы используете символ, отличный от /
в качестве разделителя необходимо заменить косую черту в приведенных выше выражениях символом, который вы используете. См. Комментарий PeterJCLaw для объяснения.
Отредактировано: из-за некоторых угловых случаев, ранее не учтенных, вышеприведенные команды менялись несколько раз. Проверьте историю изменений для деталей.
Команда sed позволяет использовать другие символы вместо /
в качестве разделителя:
sed 's#"http://www\.fubar\.com"#URL_FUBAR#g'
Двойные кавычки не проблема.
Единственными тремя литеральными символами, которые рассматриваются специально в предложении замены, являются /
(закрыть пункт), \
(чтобы избежать символов, обратной ссылки и т. д.) и &
(включить матч в замену). Поэтому все, что вам нужно сделать, это экранировать эти три символа:
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
Основываясь на регулярных выражениях 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
Уже поздно отвечать... но есть гораздо более простой способ сделать это. Просто измените разделитель (то есть символ, который разделяет поля). Итак, вместо s/foo/bar/
ты пишешь s|bar|foo
,
И вот простой способ сделать это:
sed 's|/\*!50017 DEFINER=`snafu`@`localhost`\*/||g'
Полученный результат лишен этого неприятного предложения DEFINER.
Оказывается, вы задаете не тот вопрос. Я тоже задал не тот вопрос. Причина, по которой это неправильно, - начало первого предложения: "В моем сценарии 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"}"
Используйте 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
Вот пример AWK, который я использовал некоторое время назад. Это AWK, который печатает новые AWKS. AWK и SED схожи, это может быть хорошим шаблоном.
ls | awk '{ print "awk " "'"'"'" " {print $1,$2,$3} " "'"'"'" " " $1 ".old_ext > " $1 ".new_ext" }' > for_the_birds
Это выглядит чрезмерно, но каким-то образом эта комбинация кавычек работает так, чтобы печатать как литералы. Тогда, если я правильно помню, переменные просто заключены в такие кавычки: "$1". Попробуйте, дайте мне знать, как это работает с SED.
У меня есть улучшение по сравнению с функцией 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
,
как правило беспорядок, особенно разница междуgnu-sed
иbsd-sed
может быть, было бы проще поставить какой-нибудь часовой вsed
сторона, затем быстрая труба к , которая гораздо более гибкая в принятии любыхERE
регулярное выражение, экранированное шестнадцатеричное или экранированное восьмеричное число.
напримерOFS
вawk
настоящая замена ::
date | sed -E 's/[0-9]+/\xC1\xC0/g' | mawk NF=NF FS='\xC1\xC0' OFS='\360\237\244\241'
1 Tue Aug :: EDT
(проверено и подтверждено, работает на обоихBSD-sed
иGNU-sed
- эмодзи - это не опечатка, это то, на что эти 4 байта сопоставляются вUTF-8
)
Там есть десятки ответов ... Если вы не возражаете против использования схемы функции bash, ниже приведен хороший ответ. Цель ниже заключалась в том, чтобы разрешить использование sed практически с любым параметром в качестве KEYWORD (F_PS_TARGET) или в качестве REPLACE (F_PS_REPLACE). Мы протестировали его во многих сценариях, и он кажется довольно безопасным. Приведенная ниже реализация поддерживает табуляцию, разрывы строк и одинарные кавычки как для KEYWORD , так и для replace REPLACE.
ПРИМЕЧАНИЯ. Идея состоит в том, чтобы использовать sed для экранирования записей для другой команды sed .
КОД
F_REVERSE_STRING_R=""
f_reverse_string() {
: 'Do a string reverse.
To undo just use a reversed string as STRING_INPUT.
Args:
STRING_INPUT (str): String input.
Returns:
F_REVERSE_STRING_R (str): The modified string.
'
local STRING_INPUT=$1
F_REVERSE_STRING_R=$(echo "x${STRING_INPUT}x" | tac | rev)
F_REVERSE_STRING_R=${F_REVERSE_STRING_R%?}
F_REVERSE_STRING_R=${F_REVERSE_STRING_R#?}
}
# [Ref(s).: https://stackoverflow.com/a/2705678/3223785 ]
F_POWER_SED_ECP_R=""
f_power_sed_ecp() {
: 'Escape strings for the "sed" command.
Escaped characters will be processed as is (e.g. /n, /t ...).
Args:
F_PSE_VAL_TO_ECP (str): Value to be escaped.
F_PSE_ECP_TYPE (int): 0 - For the TARGET value; 1 - For the REPLACE value.
Returns:
F_POWER_SED_ECP_R (str): Escaped value.
'
local F_PSE_VAL_TO_ECP=$1
local F_PSE_ECP_TYPE=$2
# NOTE: Operational characters of "sed" will be escaped, as well as single quotes.
# By Questor
if [ ${F_PSE_ECP_TYPE} -eq 0 ] ; then
# NOTE: For the TARGET value. By Questor
F_POWER_SED_ECP_R=$(echo "x${F_PSE_VAL_TO_ECP}x" | sed 's/[]\/$*.^[]/\\&/g' | sed "s/'/\\\x27/g" | sed ':a;N;$!ba;s/\n/\\n/g')
else
# NOTE: For the REPLACE value. By Questor
F_POWER_SED_ECP_R=$(echo "x${F_PSE_VAL_TO_ECP}x" | sed 's/[\/&]/\\&/g' | sed "s/'/\\\x27/g" | sed ':a;N;$!ba;s/\n/\\n/g')
fi
F_POWER_SED_ECP_R=${F_POWER_SED_ECP_R%?}
F_POWER_SED_ECP_R=${F_POWER_SED_ECP_R#?}
}
# [Ref(s).: https://stackoverflow.com/a/24134488/3223785 ,
# https://stackoverflow.com/a/21740695/3223785 ,
# https://unix.stackexchange.com/a/655558/61742 ,
# https://stackoverflow.com/a/11461628/3223785 ,
# https://stackoverflow.com/a/45151986/3223785 ,
# https://linuxaria.com/pills/tac-and-rev-to-see-files-in-reverse-order ,
# https://unix.stackexchange.com/a/631355/61742 ]
F_POWER_SED_R=""
f_power_sed() {
: 'Facilitate the use of the "sed" command. Replaces in files and strings.
Args:
F_PS_TARGET (str): Value to be replaced by the value of F_PS_REPLACE.
F_PS_REPLACE (str): Value that will replace F_PS_TARGET.
F_PS_FILE (Optional[str]): File in which the replacement will be made.
F_PS_SOURCE (Optional[str]): String to be manipulated in case "F_PS_FILE" was
not informed.
F_PS_NTH_OCCUR (Optional[int]): [1~n] - Replace the nth match; [n~-1] - Replace
the last nth match; 0 - Replace every match; Default 1.
Returns:
F_POWER_SED_R (str): Return the result if "F_PS_FILE" is not informed.
'
local F_PS_TARGET=$1
local F_PS_REPLACE=$2
local F_PS_FILE=$3
local F_PS_SOURCE=$4
local F_PS_NTH_OCCUR=$5
if [ -z "$F_PS_NTH_OCCUR" ] ; then
F_PS_NTH_OCCUR=1
fi
local F_PS_REVERSE_MODE=0
if [ ${F_PS_NTH_OCCUR} -lt -1 ] ; then
F_PS_REVERSE_MODE=1
f_reverse_string "$F_PS_TARGET"
F_PS_TARGET="$F_REVERSE_STRING_R"
f_reverse_string "$F_PS_REPLACE"
F_PS_REPLACE="$F_REVERSE_STRING_R"
f_reverse_string "$F_PS_SOURCE"
F_PS_SOURCE="$F_REVERSE_STRING_R"
F_PS_NTH_OCCUR=$((-F_PS_NTH_OCCUR))
fi
f_power_sed_ecp "$F_PS_TARGET" 0
F_PS_TARGET=$F_POWER_SED_ECP_R
f_power_sed_ecp "$F_PS_REPLACE" 1
F_PS_REPLACE=$F_POWER_SED_ECP_R
local F_PS_SED_RPL=""
if [ ${F_PS_NTH_OCCUR} -eq -1 ] ; then
# NOTE: We kept this option because it performs better when we only need to replace
# the last occurrence. By Questor
# [Ref(s).: https://linuxhint.com/use-sed-replace-last-occurrence/ ,
# https://unix.stackexchange.com/a/713866/61742 ]
F_PS_SED_RPL="'s/\(.*\)$F_PS_TARGET/\1$F_PS_REPLACE/'"
elif [ ${F_PS_NTH_OCCUR} -gt 0 ] ; then
# [Ref(s).: https://unix.stackexchange.com/a/587924/61742 ]
F_PS_SED_RPL="'s/$F_PS_TARGET/$F_PS_REPLACE/$F_PS_NTH_OCCUR'"
elif [ ${F_PS_NTH_OCCUR} -eq 0 ] ; then
F_PS_SED_RPL="'s/$F_PS_TARGET/$F_PS_REPLACE/g'"
fi
# NOTE: As the "sed" commands below always process literal values for the "F_PS_TARGET"
# so we use the "-z" flag in case it has multiple lines. By Quaestor
# [Ref(s).: https://unix.stackexchange.com/a/525524/61742 ]
if [ -z "$F_PS_FILE" ] ; then
F_POWER_SED_R=$(echo "x${F_PS_SOURCE}x" | eval "sed -z $F_PS_SED_RPL")
F_POWER_SED_R=${F_POWER_SED_R%?}
F_POWER_SED_R=${F_POWER_SED_R#?}
if [ ${F_PS_REVERSE_MODE} -eq 1 ] ; then
f_reverse_string "$F_POWER_SED_R"
F_POWER_SED_R="$F_REVERSE_STRING_R"
fi
else
if [ ${F_PS_REVERSE_MODE} -eq 0 ] ; then
eval "sed -i -z $F_PS_SED_RPL \"$F_PS_FILE\""
else
tac "$F_PS_FILE" | rev | eval "sed -z $F_PS_SED_RPL" | tac | rev > "$F_PS_FILE"
fi
fi
}
МОДЕЛЬ
f_power_sed "F_PS_TARGET" "F_PS_REPLACE" "" "F_PS_SOURCE"
echo "$F_POWER_SED_R"
ПРИМЕР
f_power_sed "{ gsub(/,[ ]+|$/,\"\0\"); print }' ./ and eliminate" "[ ]+|$/,\"\0\"" "" "Great answer (+1). If you change your awk to awk '{ gsub(/,[ ]+|$/,\"\0\"); print }' ./ and eliminate that concatenation of the final \", \" then you don't have to go through the gymnastics on eliminating the final record. So: readarray -td '' a < <(awk '{ gsub(/,[ ]+/,\"\0\"); print; }' <<<\"$string\") on Bash that supports readarray. Note your method is Bash 4.4+ I think because of the -d in readar"
echo "$F_POWER_SED_R"
ЕСЛИ ВЫ ПРОСТО ХОТИТЕ ИЗМЕНИТЬ ПАРАМЕТРЫ КОМАНДЫ SED
МОДЕЛЬ
# "TARGET" value.
f_power_sed_ecp "F_PSE_VAL_TO_ECP" 0
echo "$F_POWER_SED_ECP_R"
# "REPLACE" value.
f_power_sed_ecp "F_PSE_VAL_TO_ECP" 1
echo "$F_POWER_SED_ECP_R"
ВАЖНО: Если строки для KEYWORD и/или замены REPLACE содержат символы табуляции или разрывы строк, вам нужно будет использовать флаг «-z» в вашей команде «sed». Подробнее здесь .
ПРИМЕР
f_power_sed_ecp "{ gsub(/,[ ]+|$/,\"\0\"); print }' ./ and eliminate" 0
echo "$F_POWER_SED_ECP_R"
f_power_sed_ecp "[ ]+|$/,\"\0\"" 1
echo "$F_POWER_SED_ECP_R"
ПРИМЕЧАНИЕ. Приведенные выше функции f_power_sed_ecp и f_power_sed были доступны совершенно бесплатно в рамках этого проекта ez_i — Легко создавайте установщики сценариев оболочки!.
echo '1.2+3*[4]|5' | sed -r 's#([().+$*\[\]|])#\\&#g;s#\|#\\|#g'
Стандартная рекомендация здесь: используйте perl :)
echo KEYWORD > /tmp/test
REPLACE="<funny characters here>"
perl -pi.bck -e "s/KEYWORD/${REPLACE}/g" /tmp/test
cat /tmp/test
Не забывайте все удовольствие, которое происходит с ограничением оболочки вокруг "и"
так (в кш)
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"
Это escape-коды, которые я нашел:
* = \x2a
( = \x28
) = \x29
" = \x22
/ = \x2f
\ = \x5c
' = \x27
? = \x3f
% = \x25
^ = \x5e
Если вы просто хотите заменить значение переменной в команде sed, просто удалите Пример:
sed -i 's/dev-/dev-$ENV/g' test to sed -i s/dev-/dev-$ENV/g test
Просто избегайте всего в переменной REPLACE:
echo $REPLACE | awk '{gsub(".", "\\\\&");print}'
Более простой способ сделать это - просто построить строку перед использованием и использовать ее в качестве параметра для sed
rpstring="s/KEYWORD/$REPLACE/g"
sed -i $rpstring test.txt
Если случается, что вы генерируете случайный пароль для передачи sed
замените шаблон, затем вы решите, какой набор символов в случайной строке. Если вы выбираете пароль, созданный путем кодирования значения как base64, то есть только символ, который возможен в base64 и также является специальным символом в sed
заменить шаблон. Этот символ "/", и его легко удалить из пароля, который вы генерируете:
# password 32 characters log, minus any copies of the "/" character.
pass=`openssl rand -base64 32 | sed -e 's/\///g'`;