Enquanto:
sed "0,\~$var~s~$var~replacement~"
Pode ser usado para alterar o delimitador regex, incorporando expansões variáveis dentro do código sed
(ou qualquer outro interpretador) é uma coisa muito insensata no caso geral.
Primeiro, aqui, o delimitador não é o único caractere que precisa ser escapado. Todos os operadores de expressões regulares também precisam.
Mas o mais importante, e especialmente com o GNU sed
, é uma vulnerabilidade de injeção de comando. Se o conteúdo de $var
não estiver sob seu controle, é tão ruim quanto passar dados arbitrários para eval
.
Tente por exemplo:
$ var='^~s/.*/uname/e;#'
$ echo | sed "0,\~$var~s~$var~replacement~"
Linux
O comando uname
foi executado, felizmente inofensivo ... desta vez.
As implementações não-GNU sed
não podem executar comandos arbitrários, mas podem sobrescrever qualquer arquivo (com o comando w
), que é virtualmente tão ruim.
Uma maneira mais correta é escapar dos caracteres problemáticos em $var
first :
NL='
'
case $var in
(*"$NL"*)
echo >&2 "Sorry, can't handle variables with newline characters"
exit 1
esac
escaped_var=$(printf '%s\n' "$var" | sed 's:[][\/.^$*]:\&:g')
# and then:
sed "0,/$escaped_var/s/$escaped_var/replacement/" < file
Outra abordagem é usar perl
:
var=$var perl -pe 's/\Q$ENV{var}\E/replacement/g && $n++ unless $n' < file
Observe que não estamos expandindo o conteúdo de $var
dentro do código passado para perl
(que seria outra vulnerabilidade de injeção de comando), mas deixando perl
expandir seu conteúdo como parte de seu processamento de regexp ( e dentro de \Q...\E
, o que significa que os operadores regexp não são tratados especialmente).
Se $var
contiver caracteres de nova linha, isso só poderá coincidir se houver apenas um no final. Como alternativa, pode-se passar a opção -0777
para que a entrada seja processada como um registro único em vez de linha por linha.