[AWK] Phần 1 - Tổng quan

Khi đã làm quen với Linux, chắc chắn bạn sẽ thường xuyên gặp và làm việc với các command grep, sed, awk...Bạn sẽ rất hay gặp awk trong xử lý file text, tuy nhiên, AWK là một ngôn ngữ lập trình mạnh hơn thế. Chúng ta sẽ cùng tìm hiểu chi tiết trong loạt bài viết về AWK.

AWK là gì?

AWK là một ngôn ngữ lập trình hướng dữ liệu, thường được dùng cho việc xử lý dữ liệu text dựa trên việc tìm kiếm mẫu dữ liệu. Dữ liệu đầu vào được chia thành các bản ghi (dòng), mỗi bản ghi được chia thành các trường (cột). AWK thường được dùng để lọc và chuẩn hóa dữ liệu đầu ra từ dữ liệu đầu vào ban đầu.

Lịch sử

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.
Năm 1985, một version mới hơn của AWK được giới thiệu cùng với SVR3.1, bao gồm hỗ trợ nhiều input files, hàm định nghĩa bởi user, regular expression.
Năm 1988, SVR4 phát hành cùng với phiên bản AWK nữa. Phiên bản này là nguồn gốc của tài liệu kỹ thuật chuẩn của AWK được định nghĩa bởi POSIX.

Các phiên bản

Ngoài ra còn một số phiên bản AWK khác nhưng chúng không được sử dụng rộng rãi và không tương thích với POSIX AWK. Phiên bản AWK nguyên thủy vẫn còn được dùng trên một số hệ thống Unices (các bạn có thể tải source code từ link trên và biên dịch để chạy thử). Bài viết sẽ dựa theo tài liệu chuẩn của POSIX AWK, với từng phiên bản cụ thể sẽ có các lưu ý khi cần.

Cấu trúc của một chương trình AWK

Một chương trình AWK được tạo thành từ một hoặc nhiều statement, có dạng:

pattern { action }  

trong đó, phần pattern hoặc { action } có thể lược bỏ:

Nếu pattern bị lược bỏ, { action } sẽ được thực hiện với tất cả các bản ghi:

# In cột đầu tiên của tất cả các dòng
awk '{print $1}' file  

Nếu { action } bị lược bỏ, statement sẽ tương đương với pattern { print }:

# In tất cả các dòng có cột đầu tiên bằng một
awk '$1 == 1' file  

AWK có 2 pattern đặc biệt là BEGINEND.

Tất cả các { action } tương ứng với BEGIN sẽ được thực thi trước khi bản ghi đầu tiên của đầu vào được đọc:

awk 'BEGIN{print "Start"}'  
Start  

Tất cả các { action } tương ứng với END sẽ được thực thi sau khi bản ghi cuối cùng được đọc:

awk 'END{print "End"}'  
End  

Toán tử, điều khiển luồng và một số hàm cơ bản

AWK sử dụng các toán tử như C, với các độ ưu tiên khác nhau, bạn có thể xem đầy đủ tại đây.

Tương tự, việc điều khiển luồng của AWK cũng dựa trên chuẩn ISO C standard, bao gồm if, while, do… while, for, break, continue…

Một số hàm cơ bản của AWK

Biến và một số biến đặc biệt của AWK

Cũng như các ngôn ngữ lập trình khác, AWK cũng hỗ trợ biến. Bạn có thể sử dụng biến trong AWK bằng cách tham chiếu tới chúng, và biến không cần khởi tạo trước khi được sử dụng. Điều này khiến cho AWK trở thành một công cụ cực kỳ mạnh mẽ khi làm việc trên môi trường console.

Tên biến trong AWK phải bắt đầu bằng dấu gạch dưới _ hoặc các chữ cái alphabet [:alpha:] và tiếp theo là dấu gạch dưới hoặc các chữ cái alphabet [:alpha:] hoặc các chữ số [:digit:] trong bảng Portable Characters Set. Tất cả các biến được tham chiếu theo tên, trừ khi tham chiếu các biến trường (cột) thì phải thêm $ ở đầu. Ví dụ: $1 lá giá trị của cột đầu tiên, $2 là giá trị của cột thứ hai…

Một số biến đặc biệt, được định nghĩa sẵn:
  • FS: dùng để tách bản ghi (dòng) thành các trường (cột), mặc định là một khoảng trắng
echo a b c d | awk '{print $1}'  
a  
  • NF: số lượng trường của bản ghi hiện tại
echo a b c d | awk '{print NF}'  
4  
  • RS: dùng để tách đầu vào thành các bản ghi, mặc định là một ký tự xuống dòng \n
awk 'BEGIN{FS="\n";RS="\n\n"};{print $1, $2}' <<\IN  
Alice  
A  
123

Bob  
B  
123  
IN  

Ouput:

Alice A  
Bob B  
  • 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
  • FILENAME: tên của file đầu vào hiện tại
printf '1\n2\n' | awk 'END{  
  print "Number of lines read: " NR
  print "Filename: " FILENAME
}'    
Number of lines read: 2  
Filename: -  

AWK có 2 loại biến là biến scalarassociative array (hash hay dictionary trong các ngôn ngữ khác). Một số biến builtin của AWK là associative array, ví dụ như ARGV:

awk 'BEGIN  {  
  for (i = 1; i < ARGC; ++i)
    printf("Argument %d: %s\n", i, ARGV[i])
}' a b c
Argument 1: a  
Argument 2: b  
Argument 3: c  

Vậy còn ARGV[0]?

ARGV[0] thường chứa tên của awk interpreter, sẽ chứa thông tin đường dẫn đầy đủ của argv[0] trên mọi hệ thống certified UNIX

Tổng kết

Với những kiến thức ở trên, bạn đã có thể viết được những script, dòng lệnh AWK mạnh mẽ và đầy hữu dụng. Hãy đọc lại một lượt tài liệu chuẩn của AWK, và ở bài tới, bạn sẽ cùng tôi tìm hiểu rõ hơn vẻ đẹp và sức mạnh thực sự của AWK.

PS: Cho những ai quá chán khi nhìn toàn chữ

awk '!a[$0]++' file  

Đó là một câu lệnh AWK rất nổi tiếng, đẹp, ngắn gọn. Câu hỏi dành cho bạn:

  • 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?

Tôi sẽ đưa ra câu trả lời trong phần 2 của bài viết về AWK.