Como garantir que a string interpolada na substituição `sed` escape de todos os metacarpos

21

Eu tenho um script que lê um fluxo de texto e gera um arquivo de comandos sed que é posteriormente executado sed -f. Os comandos sed gerados são como:

s/cid:image002\.gif@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1922/g
s/cid:image003\.gif@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1923/g
s/cid:image004\.jpg@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1924/g

Suponha que o script que gera os sedcomandos seja algo como:

while read cid fileid
do
    cidpat="$(echo $cid | sed -e s/\\./\\\\./g)"
    echo 's/'"$cidpat"'/https:\/\/mysite.com\/files\/'"$fileid"'/g' >> sedscr
done

Como posso melhorar o script para garantir que todos os metacaracteres de expressão regular na cidseqüência de caracteres sejam escapados e interpolados corretamente?

dan
fonte

Respostas:

24

Para escapar das variáveis ​​a serem usadas no lado esquerdo e no lado direito de um scomando sed(aqui $lhse $rhsrespectivamente), faça o seguinte:

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/"

Observe que $lhsnão pode conter um caractere de nova linha.

Ou seja, no LHS, escape todos os operadores regexp ( ][.^$*), o próprio caractere de escape ( \) e o separador ( /).

No RHS, você só precisa escapar &, o separador, a barra invertida e o caractere de nova linha (o que você faz inserindo uma barra invertida no final de cada linha, exceto a última ( $!s/$/\\/)).

Isso pressupõe que você use /como separador em seus sed scomandos e que não ative REs estendidas com -r(GNU sed/ ssed/ ast/ busybox sed) ou -E(BSDs, astGNU recente, busybox recente) ou PCREs com -R( ssed) ou REs aumentadas com -A/ -X( ast) que todos têm operadores extras de ER.

Algumas regras básicas ao lidar com dados arbitrários:

  • Não use echo
  • cite suas variáveis
  • considere o impacto da localidade (especialmente seu conjunto de caracteres: é importante que os comandos de escape sed sejam executados no mesmo local que o sedcomando usando as seqüências de escape (e com o mesmo sedcomando) por exemplo)
  • não se esqueça do caractere de nova linha (aqui você pode verificar se $lhscontém algum e executar uma ação).

Outra opção é usar em perlvez de sede passar as strings no ambiente e usar os operadores \Q/ \E perlregexp para interpretar as strings literalmente:

A="$lhs" B="$rhs" perl -pe 's/\Q$ENV{A}\E/$ENV{B}/g'

perl(por padrão) não será afetado pelo conjunto de caracteres da localidade, pois, acima, ela considera apenas as cadeias de caracteres como matrizes de bytes sem se preocupar com os caracteres (se houver) que eles podem representar para o usuário. Com sed, você pode conseguir o mesmo fixando o código do idioma Ccom LC_ALL=Cpara todos os sedcomandos (embora isso também afete o idioma das mensagens de erro, se houver).

Stéphane Chazelas
fonte
E se eu precisar escapar de aspas duplas?
Menon
@ Menon, aspas duplas não são especiais sed, você não precisa escapar delas.
Stéphane Chazelas
Isso não pode ser usado para correspondência de padrões usando curinga, pode?
Menon
@Menon, não, o padrão de caracteres curinga correspondente ao findda -nameé diferente das expressões regulares. Lá você só precisa escapar ?, *barra invertida e[
Stéphane Chazelas