Linux cho DE 3 — Xử lý văn bản: grep, sed, awk
Vì sao data engineer phải giỏi text processing
Trong công việc hằng ngày, data engineer chạm vào text nhiều hơn tưởng tượng: log của Airflow, log truy cập của API, file CSV export từ core banking, file cấu hình .env, output của lệnh psql, dump JSON dòng-một-bản-ghi (JSONL). Rất nhiều tình huống không đáng mở Python, viết pandas.read_csv, khởi động một notebook — chỉ để trả lời một câu hỏi đơn giản như "hôm nay có bao nhiêu request lỗi 500?" hay "tổng tiền cột amount trong file này là bao nhiêu?".
Đó là lúc grep, sed, awk toả sáng. Chúng có mặt sẵn trên mọi máy Linux, xử lý file lớn hơn RAM một cách tự nhiên (đọc theo dòng, không nạp cả file vào bộ nhớ), và ghép nối với nhau qua pipe (|) thành những câu lệnh một dòng giải quyết được việc mà đôi khi cả script Python cũng loay hoay.
Bài này là bài lõi của series. Ta đi từ công cụ xem file, sang grep (lọc), sed (biến đổi dòng), awk (xử lý theo cột), rồi các dao nhỏ hỗ trợ (cut/sort/uniq/tr/wc). Toàn bộ ví dụ đều xoay quanh log và dữ liệu ngân hàng để bạn dùng được ngay. Bài này nối tiếp Linux 2 — File & điều hướng và mở đường cho Linux 4 — Pipe & chuyển hướng, nơi ta ghép các mảnh này thành pipeline thực thụ.
Xem file: cat, less, head, tail
Trước khi lọc hay biến đổi, phải nhìn thấy dữ liệu đã.
cat file— in toàn bộ file ra màn hình. Chỉ dùng cho file nhỏ.cat -nthêm số dòng.less file— trình xem có phân trang. Dùng cho file lớn: cuộn bằng mũi tên/Space, tìm kiếm bằng/tu-khoa, thoát bằngq.lesskhông nạp cả file vào RAM, mở file 10 GB vẫn mượt.head -n 20 file— xem 20 dòng đầu. Rất hay dùng để soi header của CSV.tail -n 20 file— xem 20 dòng cuối.
Ngôi sao ở đây là tail -f (follow): theo dõi file log tăng dần theo thời gian thực. Khi ứng dụng ghi thêm dòng mới, chúng hiện ra ngay trên màn hình.
# Theo dõi log Airflow scheduler realtime
tail -f /opt/airflow/logs/scheduler/latest/dag_etl.log
# Theo dõi và chỉ giữ khi có sự kiện lỗi (kết hợp grep)
tail -f /var/log/nginx/access.log | grep --line-buffered ' 500 '
# tail -F (chữ F hoa): vẫn bám file kể cả khi file bị xoay vòng (log rotation)
tail -F /var/log/app/api.log
Mẹo: khi pipe tail -f vào grep, thêm --line-buffered để grep in ngay từng dòng thay vì gom buffer — nếu không, output có thể "đứng hình" một lúc lâu.
grep — tìm dòng khớp mẫu
grep (Global Regular Expression Print) đọc từng dòng, in ra những dòng khớp mẫu. Đây là công cụ được gõ nhiều nhất trong đời một kỹ sư.
Các cờ (flag) cốt lõi
| Cờ | Ý nghĩa |
|---|---|
-i | Bỏ qua hoa/thường (case-insensitive) |
-v | Đảo ngược: in dòng KHÔNG khớp |
-c | Chỉ đếm số dòng khớp, không in nội dung |
-n | Kèm số dòng |
-r (hay -R) | Đệ quy qua cả thư mục |
-o | Chỉ in phần khớp, không in cả dòng |
-E | Regex mở rộng (ERE): `+ ? |
-w | Khớp trọn từ (word boundary) |
-A n / -B n / -C n | In thêm n dòng After/Before/Context quanh dòng khớp |
Regex cơ bản vs mở rộng
grep mặc định dùng regex cơ bản (BRE), ở đó +, ?, |, () là ký tự thường; muốn dùng nghĩa đặc biệt phải escape \+, \?, \|. Rất phiền. Vì thế thực tế gần như luôn dùng grep -E (regex mở rộng — ERE), khi đó các ký tự đó có nghĩa đặc biệt mà không cần escape. grep -E tương đương lệnh cũ egrep.
# Tìm dòng chứa ERROR hoặc FATAL (ERE, dùng dấu |)
grep -E 'ERROR|FATAL' app.log
# Đếm số dòng lỗi, không phân biệt hoa thường
grep -ic 'error' app.log
# Tìm đệ quy trong toàn bộ mã nguồn dòng có "password" (soi lộ bí mật)
grep -rniw 'password' ./src
# In các dòng KHÔNG phải comment và KHÔNG rỗng trong file cấu hình
grep -Ev '^\s*#|^\s*$' airflow.cfg
Trích xuất giá trị với -o
-o cực mạnh khi cần rút một mẩu ra khỏi dòng:
# Rút mọi địa chỉ IP trong access log
grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log
# Rút mọi mã giao dịch dạng TXN-xxxxxxxx
grep -oE 'TXN-[0-9]{8}' transactions.log
Kết hợp grep -oE ... | sort | uniq -c | sort -rn là công thức "đếm tần suất" kinh điển, ta sẽ dùng lại nhiều lần bên dưới.
sed — biên tập dòng theo luồng
sed (Stream EDitor) đọc từng dòng, áp một lệnh biến đổi, rồi in ra. Nó không mở trình soạn thảo — nó sửa theo luồng, hợp cho việc sửa hàng loạt tự động.
Thay thế: s/mẫu/thay/cờ
Cú pháp phổ biến nhất là s (substitute):
# Thay lần khớp ĐẦU TIÊN trên mỗi dòng
sed 's/localhost/db.internal/' config.ini
# Thay TẤT CẢ (cờ g = global) trên mỗi dòng
sed 's/,/;/g' data.csv
# Thay không phân biệt hoa thường (cờ I)
sed 's/error/ERROR/gI' app.log
# Đổi delimiter khi mẫu chứa dấu / — dùng dấu # cho dễ đọc
sed 's#/opt/old/path#/opt/new/path#g' paths.txt
Quan trọng — sed KHÔNG sửa file gốc mặc định, nó chỉ in ra stdout (an toàn để thử). Muốn ghi đè file phải thêm -i:
sed -i 's/DEBUG/INFO/g' logging.conf # ghi đè trực tiếp
sed -i.bak 's/DEBUG/INFO/g' logging.conf # ghi đè, giữ bản sao logging.conf.bak
Cảnh báo:
-isửa thẳng file, không hoàn tác được. Với dữ liệu quan trọng luôn dùng-i.bakđể giữ bản dự phòng, hoặc chạy thử không có-itrước.
Xoá dòng và in khoảng dòng
sed định địa chỉ dòng bằng số dòng, khoảng n,m, hoặc mẫu /regex/:
sed '1d' data.csv # xoá dòng 1 (bỏ header CSV)
sed '$d' file.txt # xoá dòng cuối
sed '/^#/d' config.ini # xoá mọi dòng comment (bắt đầu bằng #)
sed '/^\s*$/d' file.txt # xoá dòng trống
sed -n '10,20p' big.log # in CHỈ dòng 10 đến 20 (-n tắt in mặc định, p = print)
sed -n '/START/,/END/p' app.log # in đoạn từ dòng khớp START tới dòng khớp END
Bộ đôi sed '1d' (bỏ header) rồi đẩy vào awk là mở màn quen thuộc khi xử lý CSV.
awk — xử lý theo cột, sức mạnh phân tích nhanh
Nếu grep là "lọc dòng" và sed là "sửa dòng", thì awk là cả một ngôn ngữ để xử lý dữ liệu theo cột (field). Với data engineer, awk là công cụ đáng đầu tư nhất trong ba anh em: nó thay thế được rất nhiều đoạn Python nhỏ để tính tổng, nhóm, lọc trên file phẳng.
Mô hình pattern { action }
awk tách mỗi dòng thành các trường $1, $2, ... theo dấu phân cách. Chương trình awk là chuỗi các cặp pattern { action }:
- Với mỗi dòng, nếu
patternđúng thì chạyaction. patterntrống → áp cho mọi dòng.actiontrống → mặc định in cả dòng.BEGIN { }chạy trước khi đọc dòng đầu;END { }chạy sau khi hết file — lý tưởng để in tổng kết.
Biến dựng sẵn quan trọng
| Biến | Ý nghĩa |
|---|---|
$0 | Toàn bộ dòng |
$1 ... $NF | Trường thứ 1 ... trường cuối cùng |
NF | Number of Fields — số trường trên dòng hiện tại |
NR | Number of Records — số thứ tự dòng (từ đầu file) |
FS | Field Separator — dấu phân cách đầu vào (mặc định: khoảng trắng) |
OFS | Output Field Separator — dấu phân cách khi in |
Đặt dấu phân cách bằng cờ -F hoặc gán FS:
awk -F',' '{ print $1, $3 }' data.csv # in cột 1 và 3 của CSV
awk -F':' '{ print $1 }' /etc/passwd # in tên user (trường 1, phân cách bằng :)
Ví dụ nền tảng
# In cột 1 và cột CUỐI của mỗi dòng
awk '{ print $1, $NF }' file.txt
# Chỉ in dòng có nhiều hơn 5 trường
awk 'NF > 5' data.tsv
# In dòng 10 đến 20 (dùng NR làm điều kiện)
awk 'NR>=10 && NR<=20' big.log
# Lọc theo giá trị cột: giao dịch có amount (cột 3) > 100 triệu
awk -F',' '$3 > 100000000 { print $0 }' transactions.csv
Tính tổng và nhóm — thế mạnh lớn nhất
Đây là chỗ awk vượt xa grep/sed. Dùng biến tích luỹ và mảng kết hợp (associative array):
# TỔNG cột amount (cột 3) trong CSV, bỏ dòng header (NR>1)
awk -F',' 'NR>1 { sum += $3 } END { print "Tong:", sum }' transactions.csv
# TRUNG BÌNH cột amount
awk -F',' 'NR>1 { s+=$3; n++ } END { printf "TB: %.2f\n", s/n }' transactions.csv
# NHÓM: tổng amount THEO loại giao dịch (cột 2 = kind)
awk -F',' 'NR>1 { total[$2] += $3 } END { for (k in total) print k, total[k] }' transactions.csv
Dòng cuối là một "GROUP BY" thực thụ ngay trên shell: total[$2] là mảng đánh chỉ mục bằng chuỗi (giá trị cột 2), cộng dồn $3 vào đúng nhóm, rồi END duyệt in ra. Đây chính là điều khiến awk thành "SQL bỏ túi" cho file phẳng.
Để in đẹp có phân cách rõ ràng, đặt OFS:
awk -F',' 'BEGIN { OFS="\t" } NR>1 { print $1, $2, $3 }' transactions.csv
Các dao nhỏ: cut, sort, uniq, tr, wc
Đây là những công cụ đơn nhiệm nhưng ghép với nhau qua pipe thì mạnh vô cùng.
cut— cắt cột theo ký tự phân cách. Nhẹ hơn awk khi chỉ cần lấy vài cột:cut -d',' -f1,3 data.csv # cột 1 và 3, phân cách bằng dấu phẩy cut -d':' -f1 /etc/passwd # lấy tên usersort— sắp xếp dòng. Cờ quan trọng:-n(số học, không phải theo chữ),-r(giảm dần),-k(theo cột thứ mấy),-t(đặt dấu phân cách):Lưu ý: mặc địnhsort -t',' -k3 -n data.csv # sắp theo cột 3, so sánh dạng SỐ sort -rn amounts.txt # số, giảm dầnsortso sánh theo chuỗi, nên10đứng trước9. Với số bắt buộc dùng-n.uniq— gộp các dòng liền kề trùng nhau.uniq -cđếm số lần xuất hiện. Phảisorttrước vì uniq chỉ nhìn dòng kề nhau:sort access.log | uniq -c | sort -rn # đếm & xếp hạng dòng trùngtr— thay/xoá ký tự (translate). Hay dùng chuyển hoa-thường hoặc đổi delimiter:tr 'a-z' 'A-Z' < file.txt # viết hoa toàn bộ tr ',' '\n' < row.csv # tách các trường ra thành từng dòng tr -d '\r' < win.txt > unix.txt # xoá ký tự CR (dọn file kiểu Windows)wc— đếm.wc -lđếm số dòng (hay dùng nhất),-wđếm từ,-cđếm byte:wc -l transactions.csv # có bao nhiêu bản ghi (kể cả header) grep -c ERROR app.log # tương đương grep | wc -l nhưng gọn hơn
Pipeline: ghép ba anh em xử lý log
Sức mạnh thật sự đến khi nối các công cụ bằng pipe. Sơ đồ dưới minh hoạ một pipeline điển hình phân tích access log: lọc bằng grep, tách cột bằng awk, gộp và xếp hạng bằng sort/uniq.
Mỗi tầng chỉ làm một việc, dữ liệu chảy qua như trên băng chuyền — đó là triết lý Unix: công cụ nhỏ, làm một việc thật tốt, ghép lại giải quyết việc lớn.
Use case thực tế
Bối cảnh: đội vận hành báo cổng thanh toán chậm và có lỗi trong khung 08:00–09:00 sáng. Bạn được cấp file access.log của Nginx (~2,4 triệu dòng, ~900 MB) với định dạng chuẩn combined log format, trong đó cột 7 là URL và cột 9 là mã trạng thái HTTP. Không có sẵn công cụ dashboard, chỉ có SSH vào server. Ba câu hỏi cần trả lời trong 10 phút.
1. Phân bố mã lỗi HTTP trong giờ cao điểm là gì?
grep '02/Jul/2026:08' access.log \
| awk '{ code[$9]++ } END { for (c in code) print code[c], c }' \
| sort -rn
Kết quả (minh hoạ):
41230 200
8974 500
3120 504
210 401
Ngay lập tức thấy 500 (~9k) và 504 gateway timeout (~3k) cao bất thường — không phải sự cố phía client (401 rất ít). Nghi ngờ backend hoặc DB.
2. Endpoint nào gây lỗi 500 nhiều nhất?
grep '02/Jul/2026:08' access.log \
| awk '$9 == 500 { print $7 }' \
| sort | uniq -c | sort -rn | head -5
7801 /api/v1/payment/verify
900 /api/v1/account/balance
...
Rõ ràng lỗi tập trung ở /api/v1/payment/verify — hướng điều tra thu hẹp về đúng một service.
3. Tổng giá trị giao dịch bị ảnh hưởng là bao nhiêu? Đội backend export file failed_txn.csv (cột: txn_id,kind,amount,account_no). Tính tổng amount và nhóm theo loại giao dịch:
# Tổng amount các giao dịch lỗi (bỏ header)
awk -F',' 'NR>1 { sum += $3 } END { printf "Tong bi anh huong: %.0f VND\n", sum }' failed_txn.csv
# Nhóm theo loại giao dịch
awk -F',' 'NR>1 { t[$2]+=$3; c[$2]++ } END { for (k in t) printf "%-12s %d giao dich %.0f VND\n", k, c[k], t[k] }' failed_txn.csv \
| sort -k2 -rn
Trong ~10 phút, chỉ với ba lệnh một dòng, bạn đã chuyển từ "hệ thống chậm" mơ hồ thành một báo cáo cụ thể: lỗi 500 tập trung ở endpoint payment/verify lúc 08:xx, ảnh hưởng X giao dịch trị giá Y tỉ đồng, chủ yếu loại transfer — đủ dữ kiện cho đội backend hành động và cho quản lý ra quyết định. Đây chính là lý do text processing là kỹ năng sống còn.
Khi phân tích cần đi xa hơn (join nhiều nguồn, tính toán phức tạp), đây là lúc chuyển sang Python — xem PyDE — Tổng quan Python cho DE. Nhưng cho bước "khám nghiệm hiện trường" tức thời, grep/awk gần như luôn nhanh hơn.
Ghi nhớ
- Xem file:
lesscho file lớn,head/tailxem đầu/cuối,tail -ftheo dõi log realtime (-Fđể bám qua log rotation). - grep = lọc dòng. Nhớ
-i(bỏ hoa/thường),-v(đảo),-c(đếm),-r(đệ quy),-o(rút mẩu khớp), và dùng-Eđể khỏi escape regex. - sed = sửa dòng theo luồng.
s/x/y/gthay tất cả;-ighi đè file (nguy hiểm — dùng-i.bak);/regex/dxoá dòng;-n '10,20p'in khoảng dòng. - awk = xử lý theo cột, mạnh nhất trong ba.
$1..$NF,NF/NR, đặt-F','cho CSV;pattern{action}vớiBEGIN/END; mảngtotal[$key]+=$valchính là GROUP BY ngay trên shell. - Công thức đếm tần suất kinh điển:
... | sort | uniq -c | sort -rn. Luônsorttrướcuniq. - sort với số phải có
-n, chọn cột bằng-k, đặt delimiter bằng-t. - Triết lý Unix: công cụ nhỏ ghép qua pipe (
|). Với việc "khám nghiệm" log/CSV tức thời, một dòng shell thường nhanh và gọn hơn cả một script Python.
Bài viết liên quan
Container vs máy ảo, image/layer, Dockerfile, volume, network, Docker Compose và best practices.
Continuous Integration/Delivery/Deployment, cấu trúc pipeline, ví dụ GitHub Actions và chiến lược release.
Vì sao cần orchestration; Pod, Deployment, Service, Ingress; scaling, self-healing và cấu hình.
Bản chất DevOps: phá bỏ rào cản Dev–Ops, vòng lặp vô tận, CALMS, và vì sao tự động hoá.