Виберіть рядок для шаблону заміни sed


317

У моєму скрипті bash у мене є зовнішня (отримана від користувача) рядок, яку я повинен використовувати в шаблоні sed.

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

Як я можу уникнути $REPLACEрядка, щоб його сміливо сприйняли sedяк буквальну заміну?

Примітка:KEYWORD німа подстрока, без сірників і т.д. Це не поставляється користувачем.


13
Ви намагаєтесь уникнути проблеми "Столів маленького Бобі", якщо вони говорять "/ g -e 's / PASSWORD =. * / PASSWORD = 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 є ще дві символи ^, $не згадані вище:s/[]\/$*.^|[]/\\&/g
Peter.O

1
@Jesse: виправлено. Насправді, це помилка, яку я попереджую в першому пункті. Я думаю, я не практикую те, що проповідую.
Піанозавр

1
@NeronLeVelu: Я не впевнений, що я знаю, що ви маєте на увазі, але "не має особливого значення в трубах або змінних. Він розбирається оболонкою перед запуском результату, тому подвійні лапки всередині змінних є безпечними. Наприклад, спробуйте запустити A='foo"bar' echo $A | sed s/$A/baz/в Подвійні цитати трактуються так само, як "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-адресою з косою рисою. Це не відповідає на питання ОП, як уникнути рядка, введеного користувачем, який може містити /, \, але також і #, якщо ви вирішите використовувати це. І крім того, 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
Будьте уважні, що поведінка ехо за замовчуванням стосується зворотних нахилів. В основному, відлуння за замовчуванням не дає інтерпретації зворотної косої риси, що служить цілі тут. З іншого боку, тире (ш), ехо інтерпретує втечі зворотньої косої риси і не має можливості, як я знаю, придушити це. Тому в тире (sh) замість echo $ x зробіть printf '% s \ n' $ x.
Юссеф Ельдакар

Крім того, завжди використовуйте параметр -r під час читання для обробки зворотних косих вводів користувача як літералів.
Юссеф Ельдакар

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

2
@Drux Три символи - єдині спеціальні в пункті заміни . Набагато більше особливого в застережній формі.
lenz

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

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

17

Трохи пізно реагувати ... але існує набагато простіший спосіб зробити це. Просто змініть роздільник (тобто символ, який розділяє поля). Отже, замість s/foo/bar/вас пишіть s|bar|foo.

І ось простий спосіб зробити це:

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

Отриманий результат позбавлений цього неприємного пункту DEFINER.


10
Ні, &і `` все одно слід уникати, як і роздільник, що б не було вибрано.
mirabilos

3
Це вирішило мою проблему, оскільки у мене було символи "/" у рядку заміни. Спасибі, чоловіче!
Євген Голдін

працює для мене. Що я роблю, це спробувати вийти $з рядка, який збирається змінити, і зберегти значення $в рядку заміни. сказати, я хочу змінити $XXXзначення змінної $YYY, sed -i "s|\$XXX|$YYY|g" fileпрацює чудово.
хакунамі

11

Виявляється, ви задаєте неправильне запитання. Я також задав неправильне запитання. Причина, що це неправильно, - це початок першого речення: "У моєму баш- скрипті ...".

У мене було те саме питання і я зробив ту саму помилку. Якщо ви використовуєте 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"}"

BTW, виділення синтаксису тут неправильне. Зовнішні котирування збігаються, а внутрішні цитати збігаються. Іншими словами, це виглядає $Aі $Bне цитується, але це не так. Цитати всередині ${}не збігаються з цитатами поза ним.
destenson

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

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

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, яку ВИНАГАЮТЬ із спеціальними символами, такими як вкладка.

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

Це коди втечі, які я знайшов:

* = \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 / [& \\\ * \\" \ '\ "') (] / \\ & / г '
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=/надаєsed: -e expression #1, char 12: unknown option to `s'
Тіно
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.