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

85

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 modifie le "nom" de la première ligne en "quelque chose", le "mot de passe" de la deuxième ligne en "quelque chose de différent" et le "nom" de la troisième ligne en "quelque chose de différent". Je ne peux pas compter sur l'ordre de ces occurrences dans le fichier, je ne peux donc pas simplement remplacer la première occurrence de "name" par "quelque chose" et la seconde occurrence de "name" par "quelque chose de différent". En fait, je dois rechercher les chaînes environnantes pour trouver et remplacer la bonne chose.

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

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

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

Idéalement, j'aimerais pouvoir utiliser regex uniquement pour faire correspondre les deux occurrences "nom d'utilisateur" et ne remplacer que le "nom". Quelque chose comme ça mais avec sed:

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

et remplacez le contenu entre parenthèses par "quelque chose".

Est-ce possible?

Harry Muscle
la source
2
Notez simplement que presque toutes les solutions basées sur les expressions rationnelles, à moins qu'elles ne soient extrêmement artificielles, risquent de se briser à chaque fois que le format d'entrée change. Les expressions régulières sont un mauvais choix pour traiter avec XML, SGML ou des dérivés (ce qui me semble).
un CVn
Approuvé! Pensez à utiliser XQuery, par exemple: w3schools.com/xquery/default.asp . Il s'agit de la norme W3C pour la récupération et la manipulation de contenu XML.
lgeorget

Réponses:

158
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 les groupes (les chaînes en fait) qui peuvent être réutilisés dans la deuxième partie
  • \1, \2etc., dans la deuxième partie, des références au ième groupe capturé dans la première partie (la numérotation commence par 1)
  • -Eactive les expressions régulières étendues (nécessaires pour +et grouper).
lgeorget
la source
21
+1 pour l'option -E
slackmart
4
il laisse un fichier de sauvegarde, avec le nom (original name) + "-E".
Sarge Borsch
4
Sur OSX, je reçois 'sed: 1: "s / (<nom d'utilisateur>. +) Nom (. + ...": \ 1 non défini dans la RE'. J'ai collé l'exemple exact de cette question dans un fichier. J'ai lancé la commande à partir de cette réponse sur ce fichier. Peut-être que OSX a une syntaxe différente?
deweydb
1
La version gnu de sed supporte le paramètre "-E", mais n’est pas officielle. Ce n'est même pas mentionné dans la page de manuel. Si vous voulez utiliser l'expression rationnelle étendue, vous devez utiliser le paramètre "-r" à la place.
Ikem Krueger le
3
@deweydb Selon cette réponse , vous devriez utiliser \(et à la \)place de (et ).
Zhang Buzz
14
sed -e '/username/s/CDATA\[name\]/CDATA\[something\]/' \
-e '/password/s/CDATA\[password\]/CDATA\[somethingelse\]/' \
-e '/dbname/s/CDATA\[name\]/CDATA\[somethingdifferent\]/' file.txt

L' /username/avant sindique à sed de ne travailler que sur les lignes contenant la chaîne 'nom d'utilisateur'.

maux de tête
la source
1
Élégant, efficace et parfaitement adapté à l'affaire. +1
lgeorget
6

Si ce sedn’est pas une exigence absolue, utilisez plutôt un outil dédié.

Si votre fichier est un fichier XML valide (pas seulement ces 3 balises ayant l'apparence de XML), 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 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 simplement échappées et ne sont pas 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 en existe plusieurs portant 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>
homme au travail
la source
3

Vous devez citer \[.*^$/dans la partie expression régulière de la scommande et \&/dans la partie de remplacement, ainsi que les nouvelles lignes. L'expression régulière est une expression régulière de base. De plus, vous devez citer le délimiteur de la scommande.

Vous pouvez choisir un séparateur différent pour éviter de devoir citer /. Vous devrez citer ce caractère à la place, mais il est généralement important de changer le délimiteur pour en choisir un qui ne figure 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 pièces dans le texte de remplacement et pour prendre en compte les variations de ces pièces.

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

sed -e 's~\(<username>.*[^A-Za-z]\[\)name\([^A-Za-z].*</username>\)~\1something\2~'
Gilles
la source
3
$ 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.

De plus, le nombre à la fin indique sedde remplacer la deuxième correspondance au lieu de remplacer la première.

A. Jeune fille
la source
1

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'ici tout est sorti en sortie standard, vous pouvez utiliser:

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

enregistrer les modifications dans un autre fichier.

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

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

afin de remplacer la valeur dans un fichier de propriétés

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