Usare sed per trovare e sostituire stringhe complesse (preferibilmente con regex)


85

Ho un file con i seguenti contenuti:

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

e ho bisogno di creare uno script che cambi il "nome" nella prima riga in "qualcosa", la "password" nella seconda riga in "qualcosa" e il "nome" nella terza riga in "qualcosa di diverso". Non posso fare affidamento sull'ordine in cui questi si verificano nel file, quindi non posso semplicemente sostituire la prima occorrenza di "nome" con "qualcosa" e la seconda occorrenza di "nome" con "qualcosa di diverso". In realtà ho bisogno di fare una ricerca per le stringhe circostanti per essere sicuro di trovare e sostituire la cosa corretta.

Finora ho provato questo comando per trovare e sostituire la prima occorrenza del "nome":

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

tuttavia non funziona, quindi sto pensando che alcuni di questi personaggi potrebbero aver bisogno di scappare, ecc.

Idealmente, mi piacerebbe poter usare regex solo per abbinare le due occorrenze "username" e sostituire solo il "nome". Qualcosa del genere ma con sed:

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

e sostituisci il contenuto tra parentesi con "qualcosa".

È possibile?


2
Basta notare che praticamente qualsiasi soluzione basata su regexp, se non estremamente elaborata, rischierà di rompersi ogni volta che cambia il formato di input. Regexps è una cattiva scelta per gestire XML, SGML o derivati ​​(che mi sembra).
un CVn

Approvato! Prendi in considerazione l'utilizzo di XQuery, ad esempio: w3schools.com/xquery/default.asp . Questo è lo standard W3C per il recupero e la manipolazione del contenuto XML.
Lgeorget,

Risposte:


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

Questo è, penso, quello che stai cercando.

Spiegazione:

  • le parentesi nella prima parte definiscono gruppi (stringhe in effetti) che possono essere riutilizzati nella seconda parte
  • \1, \2ecc. nella seconda parte sono riferimenti all'i-esimo gruppo acquisito nella prima parte (la numerazione inizia con 1)
  • -Eabilita espressioni regolari estese (necessarie +e raggruppate).

21
+1 per l'opzione -E
slackmart

4
lascia un file di backup, con il nome (original name) + "-E".
Sarge Borsch,

4
Su OSX ottengo 'sed: 1: "s / (<nomeutente>. +) Nome (. + ...": \ 1 non definito in RE'. Ho incollato l'esempio esatto da questa domanda in un file. Quindi ho eseguito il comando da questa risposta su quel file. Forse OSX ha una sintassi diversa?
deweydb

1
La versione gnu di sed supporta il parametro "-E", ma non ufficiale. Non è nemmeno menzionato nella manpage. Se vuoi usare il regex esteso, devi invece usare il parametro "-r".
Ikem Krueger,

3
@deweydb Secondo questa risposta , dovresti usare \(e \)invece di (e ).
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

Il /username/prima sdice a sed di lavorare solo su righe che contengono la stringa "username".


1
Elegante, efficiente e perfettamente adattato alla custodia. +1
lgeorget

6

Se sednon è un requisito difficile, utilizzare invece uno strumento dedicato.

Se il tuo file è XML valido (non solo quei 3 tag dall'aspetto XML), puoi usare XMLStarlet :

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

Quanto sopra funzionerà anche in situazioni che sarebbero difficili da risolvere con espressioni regolari:

  • Può sostituire i valori dei tag senza specificare i loro valori correnti.
  • Può sostituire i valori anche se sono appena sfuggiti e non inclusi in CDATA.
  • Può sostituire i valori anche se i tag hanno attributi.
  • Può sostituire facilmente solo le occorrenze dei tag, se ce ne sono più con lo stesso nome.
  • Può formattare l'XML modificato rientrandolo.

Breve dimostrazione di quanto sopra:

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

Devi citare \[.*^$/nella parte dell'espressione regolare del scomando e \&/nella parte sostitutiva, più le nuove righe. L'espressione regolare è un'espressione regolare di base e inoltre è necessario citare il delimitatore per il scomando.

Puoi scegliere un delimitatore diverso per evitare di dover citare /. Dovrai invece citare quel carattere, ma di solito il punto di cambiare il delimitatore è sceglierne uno che non si presenta né nel testo da sostituire né nel testo sostitutivo.

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

È possibile utilizzare i gruppi per evitare di ripetere alcune parti nel testo sostitutivo e adattarsi alle variazioni su queste parti.

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

Puoi semplicemente usare gli indirizzi come nel numero che precede la "s" che indica il numero di riga.

Inoltre, il numero alla fine indica seddi sostituire la seconda corrispondenza invece di sostituire la prima corrispondenza.


1

Per sostituire la parola "nome" con la parola "qualcosa", utilizzare:

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

Questo sostituirà tutte le occorrenze della parola specificata.

Finora tutto è prodotto in output standard, è possibile utilizzare:

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

per salvare le modifiche in un altro file.


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

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

quindi per sostituire il valore in un file delle proprietà

sed -i -r 's/MAIL\=(.+)/MAIL\=user@mymail.com/' etc/service.properties 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.