Usando sed para encontrar y reemplazar cadenas complejas (preferiblemente con expresiones regulares)


85

Tengo un archivo con los siguientes contenidos:

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

y necesito hacer un script que cambie el "nombre" en la primera línea a "algo", la "contraseña" en la segunda línea a "algo más" y el "nombre" en la tercera línea a "algo diferente". No puedo confiar en el orden de estos en el archivo, por lo que no puedo simplemente reemplazar la primera aparición de "nombre" con "algo" y la segunda aparición de "nombre" con "algo diferente". De hecho, necesito hacer una búsqueda de las cadenas circundantes para asegurarme de encontrar y reemplazar lo correcto.

Hasta ahora he intentado este comando para encontrar y reemplazar la primera aparición de "nombre":

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

Sin embargo, no funciona, así que creo que algunos de estos personajes podrían necesitar escapar, etc.

Idealmente, me encantaría poder usar expresiones regulares para que coincida con las dos ocurrencias de "nombre de usuario" y reemplazar solo el "nombre". Algo como esto pero con sed:

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

y reemplace el contenido entre paréntesis con "algo".

es posible?


2
Solo tenga en cuenta que prácticamente cualquier solución basada en expresiones regulares, a menos que sea extremadamente artificial, correrá el riesgo de romperse cada vez que cambie el formato de entrada. Las expresiones regulares son una mala elección para tratar con XML, SGML o derivados (lo cual me parece).
un CVn

¡Aprobado! Considere usar XQuery, por ejemplo: w3schools.com/xquery/default.asp . Este es el estándar W3C para recuperar y manipular contenido XML.
lgeorget

Respuestas:


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

Esto es, creo, lo que estás buscando.

Explicación:

  • los paréntesis en la primera parte definen grupos (cadenas de hecho) que pueden reutilizarse en la segunda parte
  • \1, \2, Etc. en la segunda parte son referencias al grupo i-ésimo capturado en la primera parte (la numeración comienza con 1)
  • -Ehabilita expresiones regulares extendidas (necesarias para +y agrupación).

21
+1 para la opción -E
slackmart

44
deja un archivo de respaldo con el nombre (original name) + "-E".
Sarge Borsch

44
En OSX obtengo 'sed: 1: "s / (<username>. +) Name (. + ...": \ 1 no definido en el RE'. Pegué el ejemplo exacto de esta pregunta en un archivo. Luego . i ejecutó el comando de esta respuesta en ese archivo OSX Tal vez tiene una sintaxis diferente?
deweydb

1
La versión gnu de sed admite el parámetro "-E", pero no es oficial. Ni siquiera se menciona en la página de manual. Si desea usar la expresión regular extendida, debe usar el parámetro "-r" en su lugar.
Ikem Krueger

3
@deweydb De acuerdo con esta respuesta , debe usar \(y en \)lugar de (y ).
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

El /username/antes de la sle indica a sed que sólo el trabajo en las líneas que contienen la cadena 'nombre de usuario'.


1
Elegante, eficiente y perfectamente equipado para el estuche. +1
lgeorget

6

Si sedno es un requisito difícil, mejor use una herramienta dedicada en su lugar.

Si su archivo es XML válido (no solo esas 3 etiquetas de aspecto XML), puede usar XMLStarlet :

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

Lo anterior también funcionará en situaciones que serían difíciles de resolver con expresiones regulares:

  • Puede reemplazar los valores de las etiquetas sin especificar sus valores actuales.
  • Puede reemplazar los valores incluso si se han escapado y no están incluidos en CDATA.
  • Puede reemplazar los valores incluso si las etiquetas tienen atributos.
  • Puede reemplazar fácilmente solo ocurrencias de etiquetas, si hay múltiples con el mismo nombre.
  • Puede formatear el XML modificado al sangrarlo.

Breve demostración de lo anterior:

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>

3

Debe citar \[.*^$/en la parte de la expresión regular del scomando y \&/en la parte de reemplazo, además de líneas nuevas. La expresión regular es una expresión regular básica , y además necesita citar el delimitador para el scomando.

Puede elegir un delimitador diferente para evitar tener que citar /. Tendrás que citar ese carácter en su lugar, pero generalmente el punto de cambiar el delimitador es elegir uno que no aparezca en el texto para reemplazar o el texto de reemplazo.

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

Puede usar grupos para evitar repetir algunas partes en el texto de reemplazo, y acomodar la variación en estas partes.

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

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

3
$ sed -e '1s/name/something/2' \
      -e '3s/name/somethingdifferent/2' \
      -e 's/password/somethingelse/2' sample.xml

Simplemente puede usar direcciones como en el número que precede a "s", que indica el número de línea.

Además, el número al final indica sedque reemplace la segunda coincidencia en lugar de reemplazar la primera coincidencia.


1

Para reemplazar la palabra "nombre" con la palabra "algo", use:

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

Eso reemplazará todas las apariciones de la palabra especificada.

Hasta ahora, todo se envía a la salida estándar, puede usar:

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

para guardar los cambios en otro archivo.


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

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

para reemplazar el valor en un archivo de propiedades

sed -i -r 's/MAIL\=(.+)/MAIL\=user@mymail.com/' etc/service.properties 
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.