Menggunakan sed untuk menemukan dan mengganti string yang kompleks (lebih disukai dengan regex)

85

Saya punya file dengan konten berikut:

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

dan saya perlu membuat skrip yang mengubah "nama" di baris pertama menjadi "sesuatu", "kata sandi" di baris kedua menjadi "sesuatu yang lain", dan "nama" di baris ketiga menjadi "sesuatu yang berbeda". Saya tidak bisa mengandalkan urutan ini terjadi dalam file, jadi saya tidak bisa hanya mengganti kemunculan pertama "nama" dengan "sesuatu" dan kemunculan kedua "nama" dengan "sesuatu yang berbeda". Saya benar-benar perlu melakukan pencarian untuk string di sekitarnya untuk memastikan saya menemukan dan mengganti hal yang benar.

Sejauh ini saya telah mencoba perintah ini untuk menemukan dan mengganti nama "nama" pertama:

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

Namun itu tidak berfungsi jadi saya pikir beberapa karakter ini mungkin perlu melarikan diri, dll.

Idealnya, saya ingin dapat menggunakan regex untuk mencocokkan hanya dua kejadian "nama pengguna" dan hanya mengganti "nama". Sesuatu seperti ini tetapi dengan sed:

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

dan ganti konten di dalam tanda kurung dengan "sesuatu".

Apakah ini mungkin?

Harry Muscle
sumber
2
Perhatikan saja bahwa hampir semua solusi berbasis regexp, kecuali jika dibikin sangat rumit, akan berisiko melanggar kapan saja format input berubah. Regexps adalah pilihan yang buruk untuk berurusan dengan XML, SGML atau turunan (yang terlihat bagi saya).
CVn
Disetujui! Pertimbangkan menggunakan XQuery sebagai contoh: w3schools.com/xquery/default.asp . Ini adalah standar W3C untuk mengambil dan memanipulasi konten XML.
lgeorget

Jawaban:

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

Ini, saya pikir, apa yang Anda cari.

Penjelasan:

  • tanda kurung di bagian pertama mendefinisikan grup (string sebenarnya) yang dapat digunakan kembali di bagian kedua
  • \1,, \2dll. di bagian kedua adalah referensi ke grup ke-i yang ditangkap di bagian pertama (penomoran dimulai dengan 1)
  • -Ememungkinkan ekspresi reguler yang diperluas (diperlukan +dan dikelompokkan).
lororget
sumber
21
+1 untuk opsi -E
slackmart
4
ia meninggalkan file cadangan, dengan namanya (original name) + "-E".
Sarge Borsch
4
Pada OSX saya mendapatkan 'sed: 1: "s / (<username>. +) Name (. + ...": \ 1 tidak didefinisikan dalam RE'. Saya menempelkan contoh yang tepat dari pertanyaan ini ke dalam file. Lalu saya menjalankan perintah dari jawaban ini pada file itu. Mungkin OSX memiliki sintaks yang berbeda?
deweydb
1
Versi gnu dari sed mendukung parameter "-E", tetapi tidak resmi. Bahkan tidak disebutkan di halaman manual. Jika Anda ingin menggunakan regex yang diperluas, Anda harus menggunakan parameter "-r".
Ikem Krueger
3
@eweydb Menurut jawaban ini , Anda harus menggunakan \(dan \)bukannya (dan ).
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

The /username/sebelum smemberitahu sed hanya bekerja pada baris yang mengandung string 'username'.

Evilsoup
sumber
1
Elegan, efisien dan sangat cocok untuk case ini. +1
lgeorget
6

Jika sedbukan persyaratan yang sulit, lebih baik gunakan alat khusus sebagai gantinya.

Jika file Anda adalah XML yang valid (bukan hanya 3 tag yang tampak XML), maka Anda dapat menggunakan XMLStarlet :

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

Hal di atas juga akan berfungsi dalam situasi yang sulit dipecahkan dengan ekspresi reguler:

  • Dapat mengganti nilai tag tanpa menentukan nilainya saat ini.
  • Dapat mengganti nilai bahkan jika mereka hanya lolos dan tidak terlampir dalam CDATA.
  • Dapat mengganti nilai meskipun tag memiliki atribut.
  • Dapat dengan mudah mengganti hanya kemunculan tag, jika ada beberapa dengan nama yang sama.
  • Dapat memformat XML yang dimodifikasi dengan indentasi itu.

Demonstrasi singkat di atas:

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>
manatwork
sumber
3

Anda perlu mengutip \[.*^$/bagian ekspresi reguler dari sperintah dan \&/di bagian pengganti, ditambah baris baru. Ekspresi reguler adalah ekspresi reguler dasar , dan sebagai tambahan Anda perlu mengutip pembatas untuk sperintah.

Anda dapat memilih pembatas yang berbeda untuk menghindari kutip /. Anda harus mengutip karakter itu, tetapi biasanya titik mengubah pembatas adalah untuk memilih yang tidak terjadi pada teks yang akan diganti atau teks pengganti.

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

Anda dapat menggunakan grup untuk menghindari pengulangan beberapa bagian dalam teks pengganti, dan mengakomodasi variasi pada bagian-bagian ini.

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
sumber
3
$ sed -e '1s/name/something/2' \
      -e '3s/name/somethingdifferent/2' \
      -e 's/password/somethingelse/2' sample.xml

Anda cukup menggunakan alamat seperti pada angka sebelumnya "s" yang menunjukkan nomor baris.

Juga angka pada akhirnya memberitahu seduntuk mengganti pertandingan kedua alih-alih mengganti pertandingan pertama.

A. Dara
sumber
1

Untuk mengganti kata "nama" dengan kata "sesuatu", gunakan:

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

Itu akan menggantikan semua kemunculan kata yang ditentukan.

Sejauh ini semua dikeluarkan ke output standar, Anda dapat menggunakan:

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

untuk menyimpan perubahan ke file lain.

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

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

jadi untuk mengganti nilai dalam file properti

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