web-dev-qa-db-fra.com

Échapper à une chaîne pour un modèle de remplacement sed

Dans mon script bash, j'ai une chaîne externe (reçue de l'utilisateur), que je devrais utiliser dans un modèle sed.

REPLACE="<funny characters here>"
sed "s/KEYWORD/$REPLACE/g"

Comment puis-je échapper à la chaîne $REPLACE afin qu'elle soit acceptée sans risque par sed comme un remplacement littéral?

NOTE: La KEYWORD est une sous-chaîne muette sans correspondance, etc. Elle n'est pas fournie par l'utilisateur.

285
Alexander Gladysh

Avertissement : Ceci ne considère pas les nouvelles lignes. Pour une réponse plus détaillée, voir this SO-question . (Merci, Ed Morton et Niklas Peter)

Notez qu'échapper à tout est une mauvaise idée. Sed nécessite que plusieurs caractères soient échappés pour obtenir leur signification particulière. Par exemple, si vous échappez un chiffre dans la chaîne de remplacement, il se transformera en une référence arrière.

Comme l'a dit Ben Blank, il ne faut échapper que trois caractères dans la chaîne de remplacement (les échappements eux-mêmes, la barre oblique pour la fin de l'instruction et pour tout remplacer):

sed -e 's/[\/&]/\\&/g'

Si vous avez besoin d'échapper à la chaîne KEYWORD, voici ce dont vous avez besoin:

sed -e 's/[]\/$*.^[]/\\&/g'

Rappelez-vous que si vous utilisez un caractère autre que / comme délimiteur, vous devez remplacer la barre oblique dans les expressions ci-dessus par le caractère que vous utilisez. Voir le commentaire de PeterJCLaw pour des explications.

Edité: En raison de certains cas d'angle non pris en compte auparavant, les commandes ci-dessus ont été modifiées plusieurs fois. Consultez l'historique de modification pour plus de détails.

246
Pianosaurus

La commande sed vous permet d'utiliser d'autres caractères au lieu de / comme séparateur:

sed 's#"http://www\.fubar\.com"#URL_FUBAR#g'

Les guillemets ne sont pas un problème.

83
scre_www

Les trois seuls caractères littéraux traités spécialement dans la clause de remplacement sont / (pour fermer la clause), \ (pour les caractères d'échappement, référence arrière, & c.) Et & (pour inclure l'allumette dans le remplacement). Par conséquent, tout ce que vous avez à faire est d'échapper à ces trois personnages:

sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"

Exemple:

$ export REPLACE="'\"|\\/><&!"
$ echo fooKEYWORDbar | sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"
foo'"|\/><&!bar
43
Ben Blank

Basé sur les expressions régulières de Pianosaurus, j'ai créé une fonction bash qui échappe à la fois au mot clé et au remplacement.

function sedeasy {
  sed -i "s/$(echo $1 | sed -e 's/\([[\/.*]\|\]\)/\\&/g')/$(echo $2 | sed -e 's/[\/&]/\\&/g')/g" $3
}

Voici comment vous l'utilisez:

sedeasy "include /etc/nginx/conf.d/*" "include /apps/*/conf/nginx.conf" /etc/nginx/nginx.conf
31
Gurpartap Singh

Il est un peu tard pour répondre ... mais il y a IS un moyen beaucoup plus simple de procéder. Il suffit de changer le délimiteur (c'est-à-dire le caractère qui sépare les champs). Ainsi, au lieu de s/foo/bar/, vous écrivez s|bar|foo.

Et voici le moyen facile de le faire:

sed 's|/\*!50017 DEFINER=`snafu`@`localhost`\*/||g'

La sortie qui en résulte est dépourvue de cette vilaine clause DEFINER.

16
user2460464

Il s'avère que vous posez la mauvaise question. J'ai aussi posé la mauvaise question. La raison pour laquelle c'est faux est le début de la première phrase: "Dans mon bash script ...".

J'ai eu la même question et fait la même erreur. Si vous utilisez bash, vous n'avez pas besoin d'utiliser sed pour effectuer des remplacements de chaîne (et il est beaucoup plus propre d'utiliser la fonctionnalité de remplacement intégrée à bash ).

Au lieu de quelque chose comme, par exemple:

function escape-all-funny-characters() { UNKNOWN_CODE_THAT_ANSWERS_THE_QUESTION_YOU_ASKED; }
INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A="$(escape-all-funny-characters 'KEYWORD')"
B="$(escape-all-funny-characters '<funny characters here>')"
OUTPUT="$(sed "s/$A/$B/g" <<<"$INPUT")"

vous pouvez utiliser les fonctionnalités bash exclusivement:

INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A='KEYWORD'
B='<funny characters here>'
OUTPUT="${INPUT//"$A"/"$B"}"
11
destenson

Utilisez awk - c'est plus propre:

$ awk -v R='//addr:\\file' '{ sub("THIS", R, $0); print $0 }' <<< "http://file:\_THIS_/path/to/a/file\\is\\\a\\ nightmare"
http://file:\_//addr:\file_/path/to/a/file\\is\\\a\\ nightmare
1
greggster

Voici un exemple de AWK que j'ai utilisé il y a longtemps. C'est un AWK qui imprime de nouveaux AWKS. AWK et SED étant similaires, cela peut être un bon modèle.

ls | awk '{ print "awk " "'"'"'"  " {print $1,$2,$3} " "'"'"'"  " " $1 ".old_ext > " $1 ".new_ext"  }' > for_the_birds

Cela semble excessif, mais d'une manière ou d'une autre, cette combinaison de citations permet de conserver le "caractère littéral". Ensuite, si je me souviens bien, les vaiables sont simplement entourées de guillemets comme celui-ci: "$ 1". Essayez-le, laissez-moi savoir comment cela fonctionne avec SED.

0
Alex

n'oubliez pas tout le plaisir que procure la limitation de Shell autour de "and '

alors (en ksh)

Var=">New version of \"content' here <"
printf "%s" "${Var}" | sed "s/[&\/\\\\*\\"']/\\&/g' | read -r EscVar

echo "Here is your \"text\" to change" | sed "s/text/${EscVar}/g"
0
NeronLeVelu

Si le cas échéant, vous générez un mot de passe aléatoire à transmettre à sed replace pattern, choisissez de faire attention au jeu de caractères de la chaîne aléatoire. Si vous choisissez un mot de passe créé en codant une valeur en base64, seul un caractère est possible en base64 et constitue également un caractère spécial dans sed replace pattern. Ce caractère est "/", et est facilement supprimé du mot de passe que vous générez:

# password 32 characters log, minus any copies of the "/" character.
pass=`openssl Rand -base64 32 | sed -e 's/\///g'`;
0
Mark Stosberg

J'ai une amélioration par rapport à la fonction sedeasy, qui va rompre avec des caractères spéciaux comme tab.

function sedeasy_improved {
    sed -i "s/$(
        echo "$1" | sed -e 's/\([[\/.*]\|\]\)/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/$(
        echo "$2" | sed -e 's/[\/&]/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/g" "$3"
}

Alors, qu'est-ce qui est différent? $1 et $2 entourés de guillemets pour éviter les extensions du shell et préserver les tabulations ou les doubles espaces.

Tuyauterie supplémentaire | sed -e 's:\t:\\t:g' (j'aime bien : comme marque) qui transforme une tabulation en \t.

0