Nếu grep lọc dòng và sed biến đổi văn bản, thì awk là công cụ để hiểu dữ liệu theo cột. Rất nhiều lập trình viên chỉ biết awk '{print $2}' để lấy một cột rồi dừng lại — bỏ lỡ sự thật rằng awk là một ngôn ngữ lập trình hoàn chỉnh, có biến, điều kiện, vòng lặp và mảng, được thiết kế riêng cho dữ liệu bảng. Với nó, bạn lọc theo giá trị số của cột, tính toán trên các cột, và thậm chí group-by tính tổng — những việc mà nhiều người vội mở Python hay import vào CSDL để làm. Bài này (phần 4 loạt Unix) chạy awk thật trên một bảng bán hàng để cho thấy điều đó.

Mô hình: mỗi dòng thành các trường $1 $2 ...

Với mỗi dòng đầu vào, awk tự tách thành các trường theo dấu phân cách và gán vào $1, $2, ...; $0 là cả dòng. Kèm theo là các biến dựng sẵn: NF (số trường/cột trên dòng), NR (số thứ tự dòng đang xử lý).

awk -F"\t" "NR>1 {print \$2}"   # -F đặt phân cách (tab); NR>1 bỏ dòng tiêu đề

Cấu trúc chương trình awk là các cặp mẫu { hành động } — với mỗi dòng, nếu mẫu đúng thì chạy hành động. Bỏ mẫu = chạy mọi dòng; bỏ hành động = in dòng khớp.

Ảnh chụp đoạn mã nền tối minh hoạ awk xử lý dữ liệu theo cột vượt xa sed cho dữ liệu bảng, mô hình mỗi dòng tách thành trường đô 1 đô 2 theo dấu phân cách đô 0 cả dòng đô 1 đô 2 cột 1 2 NF số cột NR số dòng awk -F tab dấu phân cách đây là tab awk NR lớn hơn 1 print đô 2 bỏ dòng tiêu đề in cột 2, cấu trúc mẫu hành động chạy cho mỗi dòng awk điều kiện hành động lọc theo cột mạnh hơn grep vì hiểu cột số awk -F tab NR lớn hơn 1 và đô 4 lớn hơn bằng 4 print đô 2 đô 3 đô 4 tính toán ngay trên cột awk NR lớn hơn 1 printf đô 2 đô 4 nhân đô 5, nhóm và tổng bằng mảng kết hợp cộng BEGIN END awk tong đô 2 cộng bằng đô 4 nhân đô 5 END for nv in tong print nv tong nv tong đô 2 mảng theo tên nhân viên group-by ngay trên luồng BEGIN chạy trước dòng đầu END sau dòng cuối

Hình 1: awk tách mỗi dòng thành trường $1 $2... (biến NF/NR); cấu trúc mẫu { hành động }; lọc theo giá trị số của cột, tính toán trên cột, và group-by bằng mảng kết hợp với BEGIN/END.

Đo thật: từ lọc tới group-by trên bảng bán hàng

Mình chạy trên một bảng TSV 6 dòng (ngày, nhân viên, sản phẩm, số lượng, đơn giá). Kết quả thật:

Ảnh chụp bảng kết quả chạy thật awk trên bảng bán hàng TSV output thật 6 dòng ngay nhanvien sanpham soluong dongia, một lọc theo cột soluong đô 4 lớn hơn bằng 4 binh tra 5 chi cafe 4 an tra 6 lọc theo giá trị số của cột grep không làm được, hai tính thành tiền bằng soluong nhân dongia đô 4 nhân đô 5 an cafe 75000 binh tra 90000 an banh 80000 chi cafe 100000 binh cafe 25000 an tra 108000, ba nhóm và tổng doanh thu mỗi nhân viên mảng kết hợp binh bằng 115000 chi bằng 100000 an bằng 263000 group-by ngay trên luồng không cần CSDL, bốn BEGIN END cộng biến tích luỹ bat dau Tong doanh thu 478000 qua 6 giao dich 263000 cộng 115000 cộng 100000 bằng 478000 khớp

Hình 2: Chạy thật — lọc $4 >= 4 (theo giá trị số của cột); tính thành tiền $4*$5; group-by tổng doanh thu mỗi nhân viên bằng mảng kết hợp (an=263000, binh=115000, chi=100000); BEGIN/END cho tổng toàn bộ 478000 qua 6 giao dịch.

  • Lọc theo cột ($4 >= 4): awk so giá trị số của cột 4 — điều grep không làm được, vì grep chỉ khớp văn bản chứ không hiểu "cột thứ 4 lớn hơn 4". Đây là khác biệt cốt lõi giữa awk và grep.
  • Tính toán ($4*$5): awk coi trường số là số và tính ngay — thành tiền = số lượng × đơn giá, in ra bằng printf có định dạng.
  • Group-by bằng mảng kết hợp: tong[$2] += $4*$5 — tong là mảng kết hợp (dictionary) khoá theo tên nhân viên. Sau khi duyệt hết, END { for (nv in tong) ... } in tổng mỗi người. Đây là GROUP BY ... SUM() của SQL, chạy ngay trên luồng văn bản, không cần nạp vào CSDL.
  • BEGIN/END + biến tích luỹ: BEGIN{} chạy trước dòng đầu (in tiêu đề, đặt giá trị ban đầu), END{} chạy sau dòng cuối (in tổng kết). Tổng toàn bộ 478.000 qua 6 giao dịch — khớp với tổng ba nhân viên.

Vì sao awk hơn hẳn cho dữ liệu bảng

grep không hiểu khái niệm cột; sed biến đổi văn bản theo dòng nhưng không tính toán được. awk lấp đúng khoảng đó: nó hiểu cấu trúc cột và có đủ công cụ lập trình (số học, điều kiện, mảng) để phân tích. Với log, CSV/TSV, output của các lệnh khác (ps, df, ls -l), awk cho phép lọc–tính–tổng hợp trong một dòng, thay vì viết cả một script. Đó là lý do nó vẫn không thể thay thế sau gần 50 năm.

Đánh đổi cần cân nhắc

awk mặc định tách theo khoảng trắng — cẩn thận với dữ liệu có dấu phân cách trong trường. Không có -F, awk tách theo chuỗi khoảng trắng (gộp nhiều space/tab). Với CSV thật (trường có dấu phẩy trong ngoặc kép, như "Hà Nội, VN"), awk cơ bản không phân tích đúng — nó không hiểu quy tắc trích dẫn CSV. Dùng TSV, hoặc công cụ CSV chuyên dụng (csvkit, hoặc awk có thư viện CSV) cho dữ liệu phức tạp.

Khi logic vượt vài dòng, cân nhắc script thật. awk tuyệt cho biến đổi một dòng, nhưng chương trình awk dài (nhiều hàm, cấu trúc phức tạp) khó đọc và khó debug hơn Python. Ranh giới thực dụng: nếu cần vài phép lọc/tính/tổng thì awk gọn nhất; nếu cần cấu trúc dữ liệu phức tạp, thư viện, hay test, chuyển sang Python.

Chú ý biến thể awk. Có awk gốc, nawk, mawk (nhanh), và gawk (GNU, nhiều mở rộng). Phần lớn cú pháp POSIX chạy ở mọi bản, nhưng các mở rộng của gawk (như hàm gensub, mảng nhiều chiều thật) không chạy trên mawk. Giữ script trong phạm vi POSIX nếu cần chạy nhiều nơi.

Ba ý mang về

  1. awk hiểu dữ liệu theo cột: mỗi dòng tách thành $1 $2..., có NF/NR, cấu trúc mẫu { hành động }. Đo thật lọc $4 >= 4 theo giá trị số của cột — điều grep không làm được vì grep chỉ khớp văn bản.
  2. Tính toán và group-by ngay trên luồng: đo thật tính thành tiền $4*$5, và mảng kết hợp tong[$2] += $4*$5 cho tổng doanh thu mỗi nhân viên (an=263000...) — chính là GROUP BY SUM của SQL mà không cần CSDL.
  3. BEGIN/END cho khởi tạo và tổng kết: đo thật tổng toàn bộ 478000 qua 6 giao dịch; awk lấp khoảng giữa grep (lọc) và sed (biến đổi) — nhưng dùng TSV thay CSV có trích dẫn, và chuyển sang script thật khi logic phức tạp.

Nguồn

Phần sau ta xem cách biến output của lệnh này thành đối số cho lệnh kia: xargs — vì sao ls | rm không chạy, cách xử lý tên file có khoảng trắng an toàn, và chạy song song với -P.