Sử dụng sed để tìm và thay thế chuỗi phức tạp (tốt nhất là bằng regex)


84

Tôi có một tập tin với các nội dung sau:

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

và tôi cần tạo một tập lệnh thay đổi "tên" trong dòng đầu tiên thành "cái gì đó", "mật khẩu" trên dòng thứ hai thành "cái gì đó" và "tên" trong dòng thứ ba thành "cái gì đó khác biệt". Tôi không thể dựa vào thứ tự của những điều này xảy ra trong tệp, vì vậy tôi không thể thay thế lần xuất hiện đầu tiên của "tên" bằng "một cái gì đó" và lần xuất hiện thứ hai của "tên" bằng "một cái gì đó khác nhau". Tôi thực sự cần phải thực hiện tìm kiếm các chuỗi xung quanh để đảm bảo rằng tôi đang tìm và thay thế đúng.

Cho đến nay tôi đã thử lệnh này để tìm và thay thế lần xuất hiện "tên" đầu tiên:

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

tuy nhiên nó không hoạt động nên tôi nghĩ một số nhân vật này có thể cần phải trốn thoát, v.v.

Lý tưởng nhất, tôi rất thích có thể sử dụng regex để chỉ khớp hai lần xuất hiện của "tên người dùng" và chỉ thay thế "tên". Một cái gì đó như thế này nhưng với sed:

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

và thay thế nội dung trong ngoặc bằng "cái gì đó".

Điều này có thể không?


2
Chỉ cần lưu ý rằng hầu như bất kỳ giải pháp dựa trên regrec nào, trừ khi cực kỳ khó khăn, sẽ có nguy cơ phá vỡ bất cứ khi nào định dạng đầu vào thay đổi. Regexps là một lựa chọn kém để xử lý XML, SGML hoặc các dẫn xuất (cái này đối với tôi).
một CVn

Tán thành! Xem xét sử dụng XQuery chẳng hạn: w3schools.com/xquery/default.asp . Đây là tiêu chuẩn W3C để truy xuất và thao tác nội dung XML.
lgeorget

Câu trả lời:


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

Đây là, tôi nghĩ, những gì bạn đang tìm kiếm.

Giải trình:

  • dấu ngoặc đơn trong phần đầu xác định các nhóm (thực tế là chuỗi) có thể được sử dụng lại trong phần thứ hai
  • \1, \2v.v. trong phần thứ hai là các tham chiếu đến nhóm thứ i được chụp trong phần đầu tiên (việc đánh số bắt đầu bằng 1)
  • -Echo phép mở rộng các biểu thức chính quy (cần thiết cho +và nhóm).

21
+1 cho tùy chọn -E
slackmart

4
Nó để lại một tập tin sao lưu, với tên (original name) + "-E".
Sange Borsch

4
Trên OSX tôi nhận được tên 'sed: 1: "s / (<tên người dùng>. +) (. + ...": \ 1 không được xác định trong RE'. Tôi đã dán ví dụ chính xác từ câu hỏi này vào một tệp. Tôi đã chạy lệnh từ câu trả lời này trên tập tin đó. Có lẽ OSX có cú pháp khác?
deweydb

1
Phiên bản gnu của sed hỗ trợ tham số "-E", nhưng không chính thức. Nó thậm chí không được đề cập trong trang. Nếu bạn muốn sử dụng regex mở rộng, bạn phải sử dụng tham số "-r".
Ikem Krueger

3
@deweydb Theo câu trả lời này , bạn nên sử dụng \(\)thay vì ().
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

Các /username/trước khi snói với sed để chỉ hoạt động trên các dòng có chứa chuỗi 'username'.


1
Thanh lịch, hiệu quả và hoàn hảo phù hợp cho các trường hợp. +1
lgeorget

6

Nếu sedkhông phải là một yêu cầu khó khăn, tốt hơn là sử dụng một công cụ chuyên dụng thay thế.

Nếu tệp của bạn là XML hợp lệ (không chỉ 3 thẻ trông giống XML đó), thì bạn có thể sử dụng XMLStarlet :

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

Ở trên cũng sẽ làm việc trong các tình huống khó giải quyết bằng các biểu thức thông thường:

  • Có thể thay thế các giá trị của các thẻ mà không chỉ định giá trị hiện tại của chúng.
  • Có thể thay thế các giá trị ngay cả khi chúng chỉ được thoát và không được bao trong CDATA.
  • Có thể thay thế các giá trị ngay cả khi các thẻ có thuộc tính.
  • Có thể dễ dàng thay thế sự xuất hiện của các thẻ, nếu có nhiều tên có cùng tên.
  • Có thể định dạng XML đã sửa đổi bằng cách thụt lề nó.

Trình bày ngắn gọn về những điều trên:

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

Bạn cần trích dẫn \[.*^$/trong phần biểu thức chính quy của slệnh và \&/trong phần thay thế, cộng với dòng mới. Biểu thức chính quy là một biểu thức chính quy cơ bản và ngoài ra, bạn cần trích dẫn dấu phân cách cho slệnh.

Bạn có thể chọn một dấu phân cách khác nhau để tránh phải trích dẫn /. Thay vào đó, bạn sẽ phải trích dẫn ký tự đó, nhưng thông thường, điểm thay đổi dấu phân cách là chọn một ký tự không xuất hiện trong văn bản để thay thế hoặc văn bản thay thế.

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

Bạn có thể sử dụng các nhóm để tránh lặp lại một số phần trong văn bản thay thế và điều chỉnh biến thể trên các phần này.

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

Bạn chỉ có thể sử dụng các địa chỉ như trong số "s" trước đó cho biết số dòng.

Ngoài ra, số cuối cùng sedsẽ thay thế trận đấu thứ hai thay vì thay thế trận đấu đầu tiên.


1

Để thay thế từ "tên" bằng từ "cái gì đó", hãy sử dụng:

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

Điều đó sẽ thay thế tất cả các lần xuất hiện của từ được chỉ định.

Cho đến nay, tất cả đã được đưa ra đầu ra tiêu chuẩn, bạn có thể sử dụng:

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

để lưu các thay đổi vào một tập tin khác.


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

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

vì vậy để thay thế giá trị trong một tệp thuộc tính

sed -i -r 's/MAIL\=(.+)/MAIL\=user@mymail.com/' etc/service.properties 
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.