Comment s'assurer que la chaîne interpolée dans la substitution `sed` échappe à tous les métachars

21

J'ai un script qui lit un flux de texte et génère un fichier de commandes sed qui est ensuite exécuté avec sed -f. Les commandes sed générées sont comme:

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

Supposons que le script qui génère les sedcommandes ressemble à:

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

Comment puis-je améliorer le script pour garantir que tous les métacaractères d'expression régulière de la cidchaîne sont correctement échappés et interpolés?

dan
la source

Réponses:

24

Pour échapper les variables à utiliser à gauche et à droite d'une scommande dans sed(ici $lhset $rhsrespectivement), vous devez:

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

Notez qu'il $lhsne peut pas contenir de caractère de nouvelle ligne.

Autrement dit, sur le LHS, échappez à tous les opérateurs regexp ( ][.^$*), au caractère d'échappement lui-même ( \) et au séparateur ( /).

Sur le RHS, il vous suffit d'échapper &, le séparateur, la barre oblique inverse et le caractère de nouvelle ligne (ce que vous faites en insérant une barre oblique inversée à la fin de chaque ligne, sauf la dernière ( $!s/$/\\/)).

Cela suppose que vous utilisez /comme séparateur dans vos sed scommandes et que vous n'activez pas les RE étendues avec -r(GNU sed/ ssed/ ast/ busybox sed) ou -E(BSD ,,ast GNU récent, boîte occupée récente) ou PCRE avec -R( ssed) ou RE augmentées avec -A/ -X( ast) qui tous ont des opérateurs RE supplémentaires.

Quelques règles de base lors du traitement de données arbitraires:

  • Ne pas utiliser echo
  • citer vos variables
  • considérer l'impact des paramètres régionaux (en particulier son jeu de caractères: il est important que l' échappement sed commandes sont exécutées dans le même lieu que la sedcommande en utilisant les échappées des chaînes (et avec la même sedcommande) , par exemple)
  • n'oubliez pas le caractère de nouvelle ligne (ici, vous voudrez peut-être vérifier s'il en $lhscontient et prendre des mesures).

Une autre option consiste à utiliser à la perlplace de sedet à passer les chaînes dans l'environnement et à utiliser le \Q/\E perl opérateurs regexp pour prendre des chaînes littéralement:

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

perl(par défaut) ne sera pas affecté par le jeu de caractères des paramètres régionaux car, dans ce qui précède, il considère uniquement les chaînes comme des tableaux d'octets sans se soucier des caractères (le cas échéant) qu'ils peuvent représenter pour l'utilisateur. Avec sed, vous pouvez obtenir le même résultat en fixant les paramètres régionaux à Cwith LC_ALL=Cpour toutes les sedcommandes (bien que cela affecte également la langue des messages d'erreur, le cas échéant).

Stéphane Chazelas
la source
Et si j'ai besoin d'échapper aux guillemets doubles?
Menon
@Menon, les guillemets doubles ne sont pas spéciaux sed, vous n'avez pas besoin de leur échapper.
Stéphane Chazelas
Cela ne peut pas être utilisé pour la correspondance de motifs à l'aide de caractères génériques, n'est-ce pas?
Menon
@Menon, non, le motif générique correspondant à celui de find's -nameest différent des expressions régulières. Là, il ne vous reste plus qu'à vous échapper ?, *barre oblique inverse et[
Stéphane Chazelas