[AWK] Phần 2 - Một vài AWK one-liner
Trong phần 1, chúng ta đã biếtAWK là gì. Tôi đã đưa ra câu lệnh awk '!a[$0] ' file
và đặt bốn câu hỏi:
- Câu lệnh trên làm gì?
- Nó thực sự hoạt động ra sao?
- Đó có phải là cách tốt và hiệu năng nhất không?
- Nếu không thì có cách nào tốt hơn và tại sao?
Phần này Bizfly Cloud sẽ cùng trả lời các câu hỏi trên và xem xét thêm một số ứng dụng của AWK trong việc xử lý text file.
I. Xóa dòng trùng lặp trong file
awk '!a[$0] ' file
Ví dụ:
$ printf '1\n2\n1\n2\n4\n3\n' > test.txt $ awk '!a[$0] ' <test.txt 1 2 4 3
- Câu lệnh trên làm gì: đúng như tiêu đề, câu lệnh này dùng để xóa các dòng trùng lặp trong file
- Nó thực sự hoạt động ra sao: Số lần xuất hiện của mỗi dòng sẽ được đếm dựa vào một associative array
a
, với key là nội dung của dòng, giá trị sẽ tăng dần qua mỗi lần xuất hiện. Lần đầu tiên xuất hiện,a[$0]
chưa tồn tại,a[$0]
tăng giá trịa[$0]
thêm 1 và trả về 0, lúc này!
của 0 là 1, là một giá trị boolean true, action mặc địnhprint $0
thực thi. Các lần sau,a[$0]
có giá trị lớn hơn hoặc bằng 1, phủ định của nó luôn trả về 0, là một giá trị boolean false,awk
bỏ qua dòng đó. - Đó có phải là cách tốt và hiệu năng nhất không: Câu trả lời là không. Với mỗi 1 dòng,
awk
sẽ phải làm các việc sau:- Tạo một giá trị mới trong associative array
a
, với key là nội dung và giá trịundef
nếu key chưa tồn tại - Tăng giá trị của key đó thêm 1
- Phủ định giá trị đó, kiểm tra giá trị boolean trả về
- Tạo một giá trị mới trong associative array
Như vậy với cách nay, lúc nào trong bộ nhớ của chương trình cũng phải lưu giá trị hash của dòng (để lưu key của associative array a
) và số lần xuất hiện của dòng.
- Nếu không thì có cách nào tốt hơn và tại sao:
awk '!($0 in a) {a[$0];print}' file
Với cách này, awk
chỉ kiểm tra dòng hiện tại có tồn tại trong các key của associative array a
hay không, nếu có bỏ qua, nếu không sẽ lưu và in dòng hiện tại ra màn hình. Cách này cũng sẽ dùng ít bộ nhớ hơn, khi mọi thời điểm chỉ cần lưu giá trị hash của dòng.
II. Xóa các khoảng trắng ở đầu và cuối dòng:
awk '{$1=$1};1' file
Ví dụ:
$ printf ' 1 2 \n' > test.txt $ od -t a <test.txt 0000000 sp sp sp 1 sp 2 sp sp sp nl 0000012 $ awk '{$1=$1};1' <test.txt | od -t a 0000000 1 sp 2 nl 0000004
Khi một field có thay đổi, awk
sẽ tạo lại $0
bằng cách nối các field bằng giá trị hiện tại của OFS
, mặc định là một khoảng trắng.
Bằng cách sử dụng od, chúng ta có thể thấy ouput đã không còn các khoảng trắng ở đầu và cuối dòng.
III. Thêm dòng trống với mỗi dòng input:
awk 'BEGIN{ORS="\n\n"};1' file
Ví dụ:
$ printf '1\n2\n' > test.txt $ awk 'BEGIN{ORS="\n\n"};1' <test.txt 1 <blank line> 2 <blank line>
ORS
, mặc định là một blank line, là biến awk
dùng để phân cách các dòng output. Bằng cách set giá trị nhiều hơn một blank line, chúng ta có thể thêm bao nhiêu blank line vào tùy ý.
Chú ý là ORS
được set trong BEGIN
block, để tránh phải gán nhiều lần như trong trường hợp sau:
awk 'ORS="\n\n"' file
hoặc một cách khác tương đương với việc dùng BEGIN
block:
awk -vORS='\n\n' 1 file
(Một lưu ý là không sử dụng awk -v var="$shell_var"
khi $shell_var
có chứa các escape sequences, awk
sẽ expand chúng. Để truyền biến từ shell vào awk
một cách tin cậy, an toàn, truyền qua ARGV
hoặc ENVIRON
. Thử echo | awk '{print a,ARGV[1]}' a='\t' | od -t a
để thấy sự khác biệt)
IV. Indent source code:
Bạn có thể sử dụng AWK để format source code, text file đẹp hơn. Giả sử có file có nội dung như sau:
describe "a" do describe "b" do stuff more stuff end end
Chúng ta sẽ thêm indent cho file trên với 2 spaces, với luật là với mỗi dòng bắt đầu là describe
thì tăng indent thêm 2 spaces, với mỗi dòng bắt đầu là end
thì giảm đi 2. Output mong muốn:
describe "a" do describe "b" do stuff more stuff end end
Rất dễ dàng với awk
:
awk ' /^end/ { indent = substr(indent, 3) } { print indent, $0 } /^describe/ { indent = indent" " } ' <file
(Bạn có thể xem ví dụ tại đây và câu trả lời của tôi)
V. Xóa các dòng chẵn trong file:
awk 'FNR%2' file
Ví dụ:
$ seq 10 | awk 'FNR%2' 1 3 5 7 9
Đây là một trong những ví dụ hay về sự đơn giản và gọn gàng của AWK. FNR
là giá trị số thứ tự của dòng trong file hiện tại. Với các dòng lẻ FNR%2
bằng 1, là giá trị true nên awk
sẽ in các dòng lẻ, bằng 0 với các dòng chẵn, awk
bỏ qua.
Chú ý việc sử dụng FNR
(thay vì NR
) giúp bạn làm việc với nhiều file:
awk 'FNR == 1 {print FILENAME}; FNR%2' file_1 file_2 ... file_n
Lời kết
AWK là một ngôn ngữ rất thú vị và rất "fun" để học. Là một System Admin hay một Developer, tôi đều cải thiện được rất nhiều hiệu suất làm việc của mình nhờ AWK.
Hi vọng qua loạt bài viết này, mọi người sẽ cảm thấy hứng thú hơn trong việc tìm hiểu về AWK.
>>Tham khảo thêm seri bài viết về Loadavg : Phần I - Tổng quan Loadavg