Щоб уникнути змінних, які будуть використовуватися в лівій і правій частині 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 виходять усі оператори regexp ( ][.^$*
), сам символ утечі ( \
) та роздільник ( /
).
На RHS вам потрібно лише втекти &
, роздільник, зворотний косий риси та символ нового рядка (що ви робите, вставляючи зворотній косу риску в кінці кожного рядка, за винятком останнього ( $!s/$/\\/
)).
Це передбачає, що ви використовуєте /
як роздільник у своїх sed
s
командах, і ви не вмикаєте розширені RE-адреси за допомогою -r
(GNU sed
/ ssed
/ ast
/ busybox sed
) або -E
(BSD ast
, останні GNU, нещодавно зайнятої скриньки ) або PCRE з -R
( ssed
) або доповненими REs з -A
/ -X
( ast
), які всі мають додаткових операторів з РЕ.
Кілька основних правил при роботі з довільними даними:
- Не використовуйте
echo
- цитуйте свої змінні
- розглянути вплив локалі (особливо його набір символів: це важливо, щоб минути
sed
команди виконуються в тій же місцевості, що і sed
команди , використовуючи вцілілі рядки (і з тієї ж sed
командою), наприклад)
- не забувайте про символ нового рядка (тут ви можете перевірити, чи
$lhs
містить він, і вжити заходів).
Іншим варіантом є використання perl
замість sed
і передавання рядків у оточенні та використання операторів \Q
/ \E
perl
regexp для буквального взяття рядків:
A="$lhs" B="$rhs" perl -pe 's/\Q$ENV{A}\E/$ENV{B}/g'
perl
(за замовчуванням) набір символів локалі не вплине, оскільки у вищезазначеному він розглядає лише рядки як масиви байтів, не піклуючись про те, які символи (якщо такі є) вони можуть представляти для користувача. З sed
, ви могли б досягти того ж, зафіксувавши локаль на C
з LC_ALL=C
для всіх sed
команд (хоча це також вплине на мову повідомлень про помилки, якщо такі є).