Quels personnages dois-je échapper lorsque j'utilise sed dans un script sh?

248

Prenez le script suivant:

#!/bin/sh
sed 's/(127\.0\.1\.1)\s/\1/' [some file]

Si j'essaie d'exécuter ceci sh( dashici), cela échouera à cause des parenthèses, qui doivent être évitées. Mais je n'ai pas besoin d'échapper aux barres obliques inverses elles-mêmes (entre les octets, ou dans le \sou \1). Quelle est la règle ici? Qu'en est-il quand j'ai besoin d'utiliser {...}ou [...]? Existe-t-il une liste de ce que je fais et n'ai pas besoin de m'échapper?

vraiment
la source
1
Voici une fonction bash permettant de convertir les chemins à utiliser avec SED:function sedPath { path=$((echo $1|sed -r 's/([\$\.\*\/\[\\^])/\\\1/g'|sed 's/[]]/\[]]/g')>&1) } #Escape path for use with sed
user2428118
Dura lex, sed sed
Nemo

Réponses:

282

Il y a deux niveaux d'interprétation ici: la coquille et sed.

Dans le shell, tout ce qui se trouve entre guillemets simples est interprété littéralement, à l'exception des guillemets simples eux-mêmes. Vous pouvez effectivement avoir un seul devis entre guillemets simples en écrivant '\''(fermez un seul devis, un seul devis littéral, ouvrez un seul devis).

Sed utilise des expressions régulières de base . Dans un BRE, pour les traiter littéralement, vous $.*[\^devez les citer en les faisant précéder d'une barre oblique inverse, sauf dans les jeux de caractères ( […]). Les lettres, les chiffres et (){}+?|ne doivent pas être cités (vous pouvez vous en tenir à certaines de ces implémentations). Les séquences \(, \), \n, et , dans certaines mises en œuvre \{, \}, \+, \?, \|et d' autres caractères alphanumériques + barre oblique inverse ont une signification particulière. Vous pouvez vous en tirer en ne citant pas $^certaines positions dans certaines implémentations.

De plus, vous avez besoin d'une barre oblique inverse avant /si elle doit apparaître dans l'expression rationnelle en dehors des expressions entre crochets. Vous pouvez choisir un autre caractère comme séparateur en écrivant, par exemple, s~/dir~/replacement~ou \~/dir~p; vous aurez besoin d'une barre oblique inverse avant le délimiteur si vous souhaitez l'inclure dans le BRE. Si vous choisissez un caractère qui a une signification particulière dans un BRE et que vous souhaitez l'inclure littéralement, vous aurez besoin de trois barres obliques inverses. Je ne le recommande pas, car cela peut se comporter différemment dans certaines implémentations.

En un mot, pour sed 's/…/…/':

  • Ecrivez l'expression régulière entre guillemets simples.
  • Utilisez '\''pour finir avec une seule citation dans la regex.
  • Mettez une barre oblique inverse avant $.*/[\]^et uniquement ces caractères (mais pas à l'intérieur des expressions entre crochets). (Techniquement vous ne devriez pas mettre une barre oblique inverse avant ]mais je ne sais pas d'une mise en œuvre qui traite ]et \]différemment en dehors des expressions du support.)
  • Dans une expression de parenthèse, pour -être traité littéralement, assurez-vous qu’elle est première ou dernière ( [abc-]ou [-abc]non [a-bc]).
  • Dans une expression de parenthèse, pour ^être traité à la lettre, assurez-vous que ce n’est pas la première (utilisation [abc^], non [^abc]).
  • Pour inclure ]dans la liste des caractères mis en correspondance par une expression entre crochets, ^définissez- le comme premier (ou premier après pour un ensemble annulé): []abc]ou [^]abc](ni [abc]]nor[abc\]] ).

Dans le texte de remplacement:

  • &et \doivent être cités en les précédant par une barre oblique inverse, comme le font le délimiteur (généralement /) et les nouvelles lignes.
  • \suivi d'un chiffre a une signification particulière. \suivi d'une lettre a une signification spéciale (caractères spéciaux) dans certaines implémentations, et \suivi d'un autre caractère \cou en cfonction de l'implémentation.
  • Avec des guillemets simples autour de l'argument ( sed 's/…/…/'), utilisez '\''pour mettre un guillemet simple dans le texte de remplacement.

Si le regex ou le texte de remplacement provient d'une variable shell, rappelez-vous que

  • La regex est un BRE, pas une chaîne littérale.
  • Dans l'expression rationnelle, une nouvelle ligne doit être exprimée sous la forme \n(ce qui ne correspondra jamais à moins que vous n'ayez un autre sedcode ajoutant des caractères de nouvelle ligne à l'espace de modèle). Mais notez que cela ne fonctionnera pas dans les expressions entre crochets avec certaines sedimplémentations.
  • Dans le texte de remplacement &, \et les nouvelles lignes doivent être citées.
  • Le délimiteur doit être cité (mais pas à l'intérieur d'expressions entre crochets).
  • Utilisez des guillemets doubles pour l' interpolation: sed -e "s/$BRE/$REPL/".
Gilles
la source
Pour échapper au caractère générique réel (*), vous pouvez utiliser une double barre oblique inversée ( \\*). Exemple:echo "***NEW***" | sed /\\*\\*\\*NEW\\*\\*\\*/s/^/#/
danger89
43

Le problème que vous rencontrez n'est pas dû à l'interpolation ni aux échappements de shell, mais bien au fait que vous essayez d'utiliser la syntaxe d'expression régulière étendue sans transmettre l' option -ror --regexp-extended.

Changer votre ligne sed de

sed 's/(127\.0\.1\.1)\s/\1/' [some file]

à

sed -r 's/(127\.0\.1\.1)\s/\1/' [some file]

et cela fonctionnera comme je crois que vous avez l’intention.

Par défaut, sed utilise des expressions rationnelles de base (think grep style), qui nécessiteraient la syntaxe suivante:

sed 's/\(127\.0\.1\.1\)[ \t]/\1/' [some file]
R Perrin
la source
J'ai eu ce problème à nouveau, et j'ai oublié de faire défiler pour trouver la solution que j'ai voté la dernière fois. Merci encore.
isaaclw
Merci beaucoup. Ajouter -rcomme option était ce qui était nécessaire dans mon cas.
HelloGoodbye
15

Sauf si vous souhaitez interpoler une variable shell dans l'expression sed, utilisez des guillemets simples pour l'expression entière, car ils font que tout ce qui les sépare est interprété tel quel, y compris les barres obliques inverses.

Donc, si vous voulez que sed puisse voir s/\(127\.0\.1\.1\)\s/\1/mettre des guillemets simples autour et que le shell ne touchera pas les parenthèses ou les barres obliques inverses. Si vous avez besoin d'interpoler une variable shell, mettez uniquement cette partie entre guillemets. Par exemple

sed 's/\(127\.0\.1\.1\)/'"$ip"'/'

Cela vous évitera de vous rappeler quels métacaractères de shell ne sont pas protégés par des guillemets doubles.

Kyle Jones
la source
Je veux sedvoir s/(127\.0\.1\.1)/..., mais mettre cela dans un script shell tel quel ne fonctionne pas. Ce que vous dites à propos de la coquille ne touchant pas les parenthèses semble faux. J'ai modifié ma question pour élaborer.
Détly
3
La coquille ne touche pas les parenthèses. Vous avez besoin des backslases car sed a besoin de les voir. sed 's/(127\.0\.1\.1)/IP \1/'échoue parce que sed a besoin de voir \(et \)pour la syntaxe de groupe, pas (et ).
Kyle Jones
facepalm Ce n'est pas dans la page de manuel, mais dans un manuel en ligne que j'ai trouvé. Est-ce normal pour regex, car je n'ai jamais eu à l'utiliser dans des bibliothèques de regex (dans, par exemple, Python)?
Détly
3
Pour les commandes Unix traditionnelles, il existe des expressions régulières de base et des expressions régulières étendues. Détails . sed utilise des expressions rationnelles de base, les barres obliques inverses sont donc nécessaires pour la syntaxe de groupe. Perl et Python sont allés au-delà même des expressions régulières étendues. Tandis que je fouinais, j'ai trouvé un tableau extrêmement informatif qui illustre à quel point une ronce déroutante est évocatrice lorsque nous disons avec gloire «expression régulière».
Kyle Jones
1
J'ajouterais également que le seul caractère qui ne puisse pas être utilisé entre guillemets simples est un guillemet simple.
enzotib