Gần đây khi phải xử lý dữ liệu là các file text lớn, AWK đã giúp mình rất nhiều. Vì vậy, mình muốn viết 1 chút về nó, để note lại tham khảo sau này.
Vậy, AWK là gì?
AWK là một ngôn ngữ lập trình thông dịch (interpreted programming language). Là một công cụ mạnh mẽ và được thiết kế đặc biệt cho việc xử lý dữ liệu text.
AWK được đặt tên dựa theo 3 chữ cái đầu tiên của những tác giả, Alfred V. Aho, Peter J. Weinberger, và Brian W. Kernighan. Phiên bản đầu tiên của AWK được viết vào năm 1977 tại AT&T Bell Lab.
Các phiên bản của AWK
- AWK − Phiên bản nguyên thủy AWK từ AT & T Laboratory
- NAWK − Phiên bản AWK mới với nhiều cải tiến từ AT & T Laboratory
- GAWK − Nó là GNU AWK (AWK cho GNU). Tất cả GNU/Linux distributions đều dùng GAWK. Và nó hoàn toàn tương thích với AWK và NAWK
Rất nhiều task có thể thực hiện được với AWK. Sau đây là 1 số ví dụ:
- Text processing
- Producing formatted text reports
- Performing arithmetic operations
- Performing string operations, and many more
Cài đặt?
Thông thường, AWK được cài đặt mặc định ở hầu hết GNU/Linux distributions. Nếu không có, có thể tham khảo link sau để cài đặt.
https://www.tutorialspoint.com/awk/awk_environment.htm
Workflow
Khá là đơn giản. Read, Execute, and Repeat.
Basic
Sử dụng AWK thì khá đơn giản. Có thể thực thi trực tiếp từ command line hoặc từ script file.
# AWK Command Line
awk [options] file ...
awk '{print}' file.txt
# AWK Program File
awk [options] -f file ....
awk -f command.awk file.txt
Ngoài ra AWK cũng cung cấp rất nhiều option mặc định nữa. Chạy awk –help
để xem chi tiết.
Ta sẽ thử thực hiện một số ví dụ cơ bản, với file text **marks.txt **sau. Mình lấy luôn ví dụ từ trang https://www.tutorialspoint.com
Nội dung file text
$ cat marks.txt
1) Amit Physics 80
2) Rahul Maths 90
3) Shyam Biology 87
4) Kedar English 85
5) Hari History 89
Và, thử:
# Printing Column or Field
# Ở đây dữ liệu được chia thành các cột
# và cột số 3 là $3, tương tự $4 là cột số 4
# $0 là toàn bộ text của 1 dòng
$ awk '{print $3 "\t" $4}' marks.txt
Physics 80
Maths 90
Biology 87
English 85
History 89
# Printing All Lines
# Ta dùng pattern /a/ (có chữ a) và in ra các dòng match với pattern này
$ awk '/a/' marks.txt
2) Rahul Maths 90
3) Shyam Biology 87
4) Kedar English 85
5) Hari History 89
# Counting
# Đếm số dòng có điểm >= 90
$ awk '{ if($4 >= 90) ++cnt } END {print ">= 90 ppoints:", cnt}' marks.txt
>= 90 ppoints: 1
Built-in
AWK còn hỗ trợ nhiều Built-in Variables rất hữu ích khi viết AWK script. Ví dụ như:
- FILENAME: tên của file đầu vào hiện tại
- NF: số lượng trường của bản ghi hiện tại
- NR: thứ tự hiện tại của bản ghi so với khởi điểm của đầu vào
- FNR: thứ tự hiện tại của bản ghi trong file
- ...
Có thể tham khảo thêm ở: AWK - Built-in Variables
Đồng thời là nhiều Built-in Functions như các hàm toán học, hàm xử lý chuỗi, thời gian... Có thể tham khảo tại: AWK - Built-in Functions
Một số bài toán thực tế trong dự án
Giả sử khi muốn biết thời gian xử lý của 1 phần nào đó trong ứng dụng. Ta có thể thêm log cho ứng dụng như sau
long startTime = System.currentTimeMillis();
// Phần muốn xác định thời gian xử lý
long endTime = System.currentTimeMillis();
long executeTime = endTime - startTime;
log.info("executeTime:" + executeTime);
// Và log sẽ có dạng
yyyy-mm-dd hh:mm:ss INFO CLASSNAME - executeTime:xxx
Đã có log thời gian xử lý cần biết, tuy nhiên, ứng dụng của bạn có phần xử lý đó được request rất nhiều lần với nhiều server, có nghĩa là sẽ có 1 lượng lớn log như trên. Làm thế nào để có được thông tin có ý nghĩa?
Đầu tiên, ta nghĩ đến việc xác định giá trị thời gian xử lý ngắn nhất và dài nhất. Để thực hiện ví dụ, ta sẽ dùng dữ liệu input như sau (Tất nhiên log thực tế sẽ có nhiều loại log hơn, ta có thể dùng câu lệnh grep của linux để lọc ra các log cần thiết).
$ cat app.2017-01-22.log
yyyy-mm-dd hh:mm:ss INFO CLASSNAME - executeTime:1
yyyy-mm-dd hh:mm:ss INFO CLASSNAME - executeTime:2
yyyy-mm-dd hh:mm:ss INFO CLASSNAME - executeTime:3
yyyy-mm-dd hh:mm:ss INFO CLASSNAME - executeTime:4
yyyy-mm-dd hh:mm:ss INFO CLASSNAME - executeTime:2
yyyy-mm-dd hh:mm:ss INFO CLASSNAME - executeTime:1
yyyy-mm-dd hh:mm:ss INFO CLASSNAME - executeTime:10
yyyy-mm-dd hh:mm:ss INFO CLASSNAME - executeTime:15
yyyy-mm-dd hh:mm:ss INFO CLASSNAME - executeTime:3
yyyy-mm-dd hh:mm:ss INFO CLASSNAME - executeTime:1
Giá trị thời gian xử lý ở cột thứ 6, tuy nhiên nó còn chứa text nên ta sẽ tách ra và chỉ lấy giá trị thời gian xử lý.
$ awk '{ split($6, time, ":"); print time[2] }' app.2017-01-22.log
1
2
3
4
2
1
10
15
3
1
# Tìm giá trị lớn nhất, nhỏ nhất
$ awk '{ split($6, time, ":"); if(max < time[2]) max=time[2] } END { print max }' app.2017-01-22.log
15
$ awk 'BEGIN { min = 99999999 } { split($6, time, ":"); if(min > time[2]) min=time[2] } END { print min }' app.2017-01-22.log
1
# Có thể kết hợp các linux command khác để tìm giá trị lớn nhất, nhỏ nhất
$ awk '{ split($6, time, ":"); print time[2] }' app.2017-01-22.log | sort -n | head -n 1
1
$ awk '{ split($6, time, ":"); print time[2] }' app.2017-01-22.log | sort -n | tail -n 1
15
Sau đó, ta có thể nghĩ tới giá trị thời gian xử lý trung bình hay thời gian xử lý trung vị (Số trung vị) sau khi sắp xếp.
# Trung bình
$ awk '{ split($6, time, ":"); sum += time[2] }; END { print sum / NR }' app.2017-01-22.log
4.2
# Giá trị ở giữa sau khi sắp xếp
$ awk '{ split($6, time, ":"); print time[2] }' app.2017-01-22.log | sort -n
1
1
1
2
2
3
3
4
10
15
$ awk '{ split($6, time, ":"); print time[2] }' app.2017-01-22.log | sort -n | awk '{ time_list[NR] = $1 }; END { mid = int(NR / 2); print "Mid index:", mid; print "Mid time:", time_list[mid] }'
Mid index: 5
Mid time: 2
Ngoài ra độ lệch chuẩn cũng là 1 khái niệm giúp cho thông tin về thời gian xử lý rõ ràng hơn. Công thức tính độ lệch chuẩn như sau
Ta có thể tính toán nó bằng AWK
$ awk '{ split($6, time, ":"); sum+=time[2]; sumsq+=time[2]*time[2] } END { print sqrt(sumsq/NR - (sum/NR)**2) }' app.2017-01-22.log
4.4
Kết luận
AWK là một công cụ mạnh mẽ và được thiết kế đặc biệt cho việc xử lý dữ liệu text.
Thường được cài sẵn trong hầu hết GNU/Linux distributions.
Sử dụng thành thạo AWK sẽ giúp ích khá nhiều khi làm các tác vụ liên quan đến file log trên unix.