Thao tác với Luồng Văn bản trên môi trường Linux với sed

5 năm trước

 

Mở đầu

sed ( stream editor) là một công cụ chỉnh sửa mạnh mẽ có thể tương tác với lượng lớn dữ liệu chỉ với một vài thao tác ngắn gọn.

Bài viết này sẽ hướng dẫn một số thao tác nâng cao với sed trên Linux.

 

Chạy chuỗi lệnh edit

 

Trong một vài trường hợp mà bạn có thể muốn chuyển nhiều lệnh để sed xử lí trong cùng một thời điểm. Có một vài cách để thực hiện điều này.

Nếu bạn chưa có các tập tin trong tầm tay, hãy roll back môi trường Linux về lần gần nhất để lấy một số file để thao tác với lệnh:

cd
cp /usr/share/common-licenses/BSD .
cp /usr/share/common-licenses/GPL-3 .
echo "this is the song that never ends
yes, it goes on and on, my friend
some people started singing it
not knowing what it was
and they'll continue singing it forever
just because..." > annoying.txt

Bởi vì sed hoạt động thông qua standard input và output, ta có thể truyền nhiều dữ liệu đến sed thông qua một pipeline (chú ý kí tự "&", nó có nghĩa là "một mẫu khớp hoàn toàn" )

sed 's/and/\&/' annoying.txt | sed 's/people/horses/'
this is the song that never ends
yes, it goes on & on, my friend
some horses started singing it
not knowing what it was
& they'll continue singing it forever
just because...

Lệnh này cho kết quả đúng nhưng tạo ra chi phí không cần thiết với lệnh gọi đến sed, chiếm nhiều bộ nhớ hơn, và không tận dụng tính năng được xây dựng sẵn trong sed .

Ta có thể thực thi nhiều lệnh trên sed với cờ "-e" phía trước lệnh. Cờ này cho phép lệnh đi kèm với nó được rewrite.

sed -e 's/and/\&/' -e 's/people/horses/' annoying.txt

Một cách tiếp cận cho việc xâu chuỗi các lệnh với nhau là sử dụng dấu chấm phẩy (;) để tách các lệnh riêng biệt. Cách này tương tự với cách trên, nhưng "-e" là không cần thiết.

sed 's/and/\&/;s/people/horses/' annoying.txt

Lưu ý rằng khi sử dụng cờ "-e" , bạn cần phân nhóm các lệnh khác nhau. Tuy nhiên, khi tách các lệnh với một dấu chấm phẩy, tất cả các lệnh được đặt hiểu là một lệnh đơn.

'=' thêm vào một dòng đánh số thứ tự ở ngay trên mỗi dòng như sau:

sed '=' annoying.txt
1
this is the song that never ends
2
yes, it goes on and on, my friend
3
some people started singing it
4
not knowing what it was
5
and they'll continue singing it forever
6
just because...

Ta sẽ không thế thay đổi định dạng của các số được tạo bởi lệnh này bằng cách chỉnh sửa văn bản.

Thêm vào lệnh "G" sẽ chèn giữa các dòng kí tự một dòng khoảng trắng để cho việc phân dòng dễ thấy hơn:

sed 'G' annoying.txt
this is the song that never ends
yes, it goes on and on, my friend
some people started singing it
not knowing what it was
and they'll continue singing it forever
just because...

Khi kết hợp hai lệnh trên, ta mong đợi kết quả thu được sẽ là đoạn văn bản chứa khoảng xuống dòng giữa các dòng kí tự thông thường và các dòng số.

Tuy nhiên kết quả khi kết hợp hai lệnh trên sẽ không giống như vậy.

sed '=;G' annoying.txt
1
this is the song that never ends
2
yes, it goes on and on, my friend
3
some people started singing it
4
not knowing what it was
. . .
. . .

Như trên ouput, ta không thể phân tách được các dòng số và dòng văn bản. Lí do là toán tử "=" operator thực hiện thay đổi trực tiếp trên luồng ouput nên nó không quan tâm đến các dòng đánh số được thêm vào.

Để thu được kết quả như mong đợi, ta sẽ sử dụng hai lệnh sed lần lượt như sau:

sed '=' annoying.txt | sed 'G'
1
this is the song that never ends
2
yes, it goes on and on, my friend
3
some people started singing it
. . .
. . .
 

Đánh chỉ mục Nâng cao

 

Ưu điểm của các lệnh đánh địa chỉ của sed là các regex( biểu thức chính quy) có thể được sử dụng như điều kiện lọc, nghĩa là ta không bị giới hạn việc xử lí trên các dòng văn bản thông thường.

sed '1,3s/.*/Hello/' annoying.txt
Hello
Hello
Hello
not knowing what it was
and they'll continue singing it forever
just because...

Ta cũng có thể sử dụng regex để xử lí với các dòng khớp với một điều kiện (mẫu) cho trước bằng cách đặt nó giữa hai dấu (/) :

sed '/singing/s/it/& loudly/' annoying.txt
this is the song that never ends
yes, it goes on and on, my friend
some people started singing it loudly
not knowing what it was
and they'll continue singing it loudly forever
just because...

Ở ví dụ trên, ta thay thế "singing it"  thành "singing it loudly".

Biểu thức để đánh chỉ mục có thể phức tạp hơn, giúp việc thực thi các lệnh linh động hơn.

Ví dụ, lệnh sau đây xóa các dòng trắng:

sed '/^$/d' GPL-3
 GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.  Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
. . .
. . .

Chú ý rằng các regex có thể được sử dụng trên một đoạn văn bản trong file. Lệnh sau thực hiện xóa tất cả các dòng nằm giữa từ "START" và "END" :

sed '/^START$/,/^END$/d' inputfile

Lệnh này xóa các dòng bắt đầu từ chuỗi "START" đầu tiên đến chuỗi "END" đầu tiên, sau đó lặp lại nếu gặp "START" lần thứ hai.

Nếu muốn thao tác trên các dòng không khớp với regex, ta có thể thêm vào dấu ( ! ) phía trước lệnh như sau :

sed '/^$/!d' GPL-3
 

 

Sử dụng Hold Buffer

Một phần trong các chức năng nâng cao khả năng xử lí nhiều dòng cùng lúc của sed là "hold buffer". Như các buffer khác, hold buffer là một vùng nhớ tạm thời được sử dụng để lưu dữ liệu.

Ý tưởng của việc sử dụng hold buffer là ta có thể nạp các dòng vào trong khi đang xử lí dòng khác và xử lí chúng trên mỗi buffer nếu cần.

Một vài lệnh với hold buffer:

  • h: Copy dòng hiện tại vào holding buffer ( lệnh này ghi đè giá trị hiện tại của hold buffer).
  • H: Thêm pattern buffer hiện tại vào holding buffer như một dòng mới và không ghi đè lên giá trị hiện tại của buffer.
  • g: Copy giá trị hiện tại của holding buffer lên pattern buffer. Pattern buffer trước bị xóa.
  • G: Thêm holding buffer hiện tại vào pattern buffer như một dòng mới và không ghi đè lên giá trị hiện tại của buffer. 
  • x: Tráo đổi giá trị hiện tại của pattern và holding buffer.

Thành phần trong holding buffer không thể được xử lí nếu không được đưa sang pattern buffer.

Hãy làm rõ chức năng của các lệnh trên với một ví dụ.

Đây là một ví dụ  về cách làm thế nào để kết nối các dòng liền kề (thực ra sed có một lệnh được tạo sẵn để xử lí việc này đó là lệnh "N". Ta sẽ thực hiện nó một cách thủ công để làm ví dụ):

sed -n '1~2h;2~2{H;g;s/\n/ /;p}' annoying.txt
this is the song that never ends yes, it goes on and on, my friend
some people started singing it not knowing what it was
and they'll continue singing it forever just because...

Hãy phân tích từng lệnh được thực hiện.

Đầu tiên là tùy chọn "-n" để chặn việc in. Sed sẽ chỉ in output khi ta ra lệnh cho nó.

Đoạn đầu tiên là "1~2h". Phần đầu biểu diễn rằng các hoạt động bắt đầu từ dòng đầu tiên, cùng với các dòng tương tự đi sau nó (ở đây là các dòng mang số thứ tự lẻ). "h" như ở trên đã nói là lệnh copy dòng hiện tại đến holding buffer.

Nửa còn lại của lệnh sẽ phức tạp hơn một chút. Đầu tiên, nó sẽ xác định dòng để thao tác, ở đây là các dòng số chẵn. Sau đó các dòng đó sẽ thừa kế phần vừa được xử lí. Nếu không có dấu ngoặc " { ", chỉ có lệnh "H" sẽ thừa kế địa chỉ trước, còn các lệnh còn lại sẽ được thực hiện trên các dòng tiếp theo đó mà không được kế thừa, dẫn đến kết quả bị sai.

Lệnh "H" sẽ copy một kí tự để phân tách các dòng, theo sau là pattern buffer hiện tại vào cuối holding pattern.

Holding pattern này (gồm các dòng lẻ, theo sau là kí tự tách dòng và dòng chẵn) sẽ được copy trở lại pattern buffer với lệnh "g".

Tiếp theo, kí tự dùng để phân tách các dòng sẽ được thay thế bởi dấu cách, và ouput sẽ được in ra màn hình với lệnh "p".

Thực tế ta sẽ dùng lệnh "N" như sau để đơn giản hóa lệnh:

sed -n 'N;s/\n/ /p' annoying.txt
this is the song that never ends yes, it goes on and on, my friend
some people started singing it not knowing what it was
and they'll continue singing it forever just because...
 

Dùng các Script của Sed

Khi bắt đầu sử dụng các lệnh phức tạp, bạn nên soạn chúng sẵn với một trình soạn thảo rồi lưu lại để sử dụng vào các lần sau. Việc này khá hữu dụng, đặc biệt là khi bạn muốn áp dụng nhiều lệnh cho cùng một đối tượng xử lí.

Ví dụ, nếu cần định dạng lại một bản thảo thường về định dạng chuẩn, sử dụng sed script sẽ giúp tiết kiệm rất nhiều thời gian.

Thay vì phải gõ nhiều lệnh, bạn có thể đặt hết các lệnh vào một script rồi triển khai nó như một tham số với sed. Về cấu trúc, một sed script đơn giản chỉ là danh sách các lệnh sed đơn lẻ .

Ví dụ một đoạn script như sau:

s/this/that/g
s/snow/rain/g
1,5s/pinecone/apricot/g

Nạp một script vào với lệnh

sed -f sedScriptName fileToEdit

Tổng kết

 

Bài viết này cung cấp một số thao tác nâng cao hơn với sed để giúp bạn xử lí các tác vụ nhanh chóng hơn.

Các lệnh sed nói chung thường khá khó hiểu, và sẽ cần có kinh nghiệm để thao tác với tiện ích này. Vì vậy hãy tập làm quen với việc xử lí văn bản từ ngay bây giờ. 

Hy vọng đến đây bạn đã bắt đầu hiểu được sức mạnh mà sed có thể cung cấp cho bạn nếu sử dụng thành thục nó. Càng tận dụng được nó nhiều bao nhiêu, khối lượng công việc của bạn sẽ càng giảm đi bấy nhiêu.