web-dev-qa-db-fra.com

Utilisation de sed pour rechercher et remplacer une chaîne complexe (de préférence avec regex)

J'ai un fichier avec le contenu suivant:

<username><![CDATA[name]]></username>
<password><![CDATA[password]]></password>
<dbname><![CDATA[name]]></dbname>

et je dois créer un script qui change le "nom" dans la première ligne en "quelque chose", le "mot de passe" sur la deuxième ligne en "quelque chose d'autre" et le "nom" dans la troisième ligne en "quelque chose de différent". Je ne peux pas me fier à leur ordre d'apparition dans le fichier, je ne peux donc pas simplement remplacer la première occurrence de "nom" par "quelque chose" et la deuxième occurrence de "nom" par "quelque chose de différent". J'ai en fait besoin de rechercher les chaînes environnantes pour m'assurer que je trouve et remplace la bonne chose.

Jusqu'à présent, j'ai essayé cette commande pour trouver et remplacer la première occurrence de "nom":

sed -i "s/<username><![CDATA[name]]><\/username>/something/g" file.xml

mais cela ne fonctionne pas, donc je pense que certains de ces personnages pourraient avoir besoin de s'échapper, etc.

Idéalement, j'aimerais pouvoir utiliser l'expression régulière pour faire correspondre les deux occurrences de "nom d'utilisateur" et remplacer uniquement le "nom". Quelque chose comme ça mais avec sed:

<username>.+?(name).+?</username>

et remplacez le contenu entre crochets par "quelque chose".

Est-ce possible?

102
Harry Muscle
sed -i -E "s/(<username>.+)name(.+<\/username>)/\1something\2/" file.xml

C'est, je pense, ce que vous cherchez.

Explication:

  • les parenthèses dans la première partie définissent des groupes (des chaînes en fait) qui peuvent être réutilisés dans la deuxième partie
  • \1, \2, etc. dans la deuxième partie sont des références au i-ème groupe capturé dans la première partie (la numérotation commence par 1)
  • -E active les expressions régulières étendues (nécessaires pour + et regroupement).
187
lgeorget
sed -e '/username/s/CDATA\[name\]/CDATA\[something\]/' \
-e '/password/s/CDATA\[password\]/CDATA\[somethingelse\]/' \
-e '/dbname/s/CDATA\[name\]/CDATA\[somethingdifferent\]/' file.txt

Le /username/ avant le s indique à sed de ne travailler que sur les lignes contenant la chaîne 'username'.

16
evilsoup

Si sed n'est pas une exigence difficile, utilisez plutôt un outil dédié à la place.

Si votre fichier est du XML valide (pas seulement ces 3 balises XML), alors vous pouvez utiliser XMLStarlet :

xml ed -P -O -L \
  -u '//username/text()' -v 'something' \
  -u '//password/text()' -v 'somethingelse' \
  -u '//dbname/text()' -v 'somethingdifferent' file.xml

Ce qui précède fonctionnera également dans des situations qui seraient difficiles à résoudre avec des expressions régulières:

  • Peut remplacer les valeurs des balises sans spécifier leurs valeurs actuelles.
  • Peut remplacer les valeurs même si elles sont juste échappées et non enfermées dans CDATA.
  • Peut remplacer les valeurs même si les balises ont des attributs.
  • Peut facilement remplacer uniquement les occurrences de balises, s'il y en a plusieurs avec le même nom.
  • Peut formater le XML modifié en le mettant en retrait.

Brève démonstration de ce qui précède:

bash-4.2$ cat file.xml
<sith>
<master>
<username><![CDATA[name]]></username>
</master>
<apprentice>
<username><![CDATA[name]]></username>
<password>password</password>
<dbname foo="bar"><![CDATA[name]]></dbname>
</apprentice>
</sith>

bash-4.2$ xml ed -O -u '//apprentice/username/text()' -v 'something' -u '//password/text()' -v 'somethingelse' -u '//dbname/text()' -v 'somethingdifferent' file.xml
<sith>
  <master>
    <username><![CDATA[name]]></username>
  </master>
  <apprentice>
    <username><![CDATA[something]]></username>
    <password>somethingelse</password>
    <dbname foo="bar"><![CDATA[somethingdifferent]]></dbname>
  </apprentice>
</sith>
7
manatwork
$ sed -e '1s/name/something/2' \
      -e '3s/name/somethingdifferent/2' \
      -e 's/password/somethingelse/2' sample.xml

Vous pouvez simplement utiliser des adresses comme dans le numéro précédant "s" qui indique le numéro de ligne.

Le nombre à la fin indique également à sed de remplacer la deuxième correspondance au lieu de remplacer la première correspondance.

5
A. Wench

Vous devez citer \[.*^$/ dans la partie expression régulière de la commande s et \&/ dans la pièce de rechange, plus les nouvelles lignes. L'expression régulière est une expression régulière de base , et en plus vous devez citer le délimiteur pour la commande s.

Vous pouvez choisir un délimiteur différent pour éviter d'avoir à citer /. Vous devrez citer ce caractère à la place, mais généralement le point de changer le délimiteur est d'en choisir un qui n'apparaît ni dans le texte à remplacer ni dans le texte de remplacement.

sed -e 's~<username><!\[CDATA\[name\]\]></username>~<username><![CDATA[something]]></username>~'

Vous pouvez utiliser des groupes pour éviter de répéter certaines parties dans le texte de remplacement et tenir compte des variations de ces parties.

sed -e 's~\(<username><!\[[A-Z]*\[\)name\(\]\]></username>\)~\1something\2~'

sed -e 's~\(<username>.*[^A-Za-z]\[\)name\([^A-Za-z].*</username>\)~\1something\2~'

Pour remplacer le mot "nom" par le mot "quelque chose", utilisez:

sed "s/\(<username><\!\[[A-Z]*\[\)name\]/\1something/g" file.xml

Cela va remplacer toutes les occurrences du mot spécifié.

Jusqu'à présent, tout est sorti en sortie standard, vous pouvez utiliser:

sed "s/\(<username><\!\[[A-Z]*\[\)name\]/\1something/g" file.xml > anotherfile.xml

pour enregistrer les modifications dans un autre fichier.

1
slackmart
Usage: sed [OPTION]... {script-only-if-no-other-script} [input-file]...

    -r, --regexp-extended
             use extended regular expressions in the script.

donc pour remplacer la valeur dans un fichier de propriétés

sed -i -r 's/MAIL\=(.+)/MAIL\[email protected]/' etc/service.properties 
0
alfiogang