Chương 05 — Text processing: sed, awk, cut, sort, uniq, jq
Đây là lúc CLI thực sự đáng giá — xử lý hàng triệu dòng trong vài giây.
Bạn sẽ dùng cut, sort, uniq, sed, awk, jq
như những "lego" lắp với pipe để biến đống log lộn xộn thành insight.
1. cut, paste, tr, column — cắt & biến đổi cột text
cut — cắt theo cột
$ cut -d, -f1,3 data.csv # delimiter dấu phẩy, lấy cột 1 và 3
$ cut -d: -f1 /etc/passwd # lấy username từ /etc/passwd
$ cut -c1-10 file.txt # 10 ký tự đầu mỗi dòng
$ cut -f2 data.tsv # mặc định delimiter là TAB
tr — translate / squeeze / delete
$ echo "Hello World" | tr 'a-z' 'A-Z' # HELLO WORLD
$ tr -d ' ' < file.txt # xóa toàn bộ dấu cách
$ tr -s ' ' < file.txt # squeeze: nhiều space liên tiếp → 1 space
$ cat file.txt | tr ',' '\n' # thay , bằng newline (CSV → 1 cột)
paste — ghép cột
$ paste names.txt ages.txt # ghép 2 file thành 2 cột (tab giữa)
$ paste -d, a.txt b.txt # dùng "," làm delimiter
column — căn cột cho đẹp
$ cat data.csv | column -t -s, # bảng gọn gàng, tách bằng dấu phẩy
$ mount | column -t # output mount hiển thị thẳng cột
cut không hỗ trợ delimiter "nhiều khoảng trắng" — nếu file có nhiều space ngăn cách (output của ls -l, ps...), hãy dùng awk hoặc tr -s ' ' để gom space lại trước.
2. sort & uniq — nâng cao
sort — sắp xếp
$ sort file.txt # mặc định: alphabet, A-Z
$ sort -r file.txt # reverse: Z-A
$ sort -n numbers.txt # numeric: 2 < 10 < 100 (KHÔNG so chuỗi)
$ sort -rn numbers.txt # giảm dần theo số
$ sort -k2 data.txt # sort theo cột 2
$ sort -t, -k3 -n data.csv # delimiter ",", sort cột 3 dạng số
$ sort -u file.txt # unique: bỏ dòng trùng (như sort | uniq)
uniq — lọc dòng trùng (dòng phải LIỀN nhau!)
$ sort file.txt | uniq # bỏ dòng trùng
$ sort file.txt | uniq -c # đếm số lần xuất hiện mỗi dòng
$ sort file.txt | uniq -d # chỉ in dòng bị trùng
$ sort file.txt | uniq -u # chỉ in dòng KHÔNG bị trùng
uniq CẦN input đã sortuniq chỉ so sánh dòng hiện tại với dòng liền trước. Nếu input chưa sort, các dòng giống nhau ở xa nhau sẽ không bị gộp. Luôn sort | uniq, không bao giờ uniq đơn lẻ trừ khi bạn biết input đã sort.
Idiom kinh điển: top-N
$ sort file.txt | uniq -c | sort -rn | head
# Đếm dòng trùng → sort giảm dần theo count → 10 dòng đầu
# → đây là cách tìm "top N item phổ biến" trong mọi dataset text
3. sed — stream editor
sed đọc input theo dòng, áp dụng quy tắc, in ra. Dùng nhiều nhất cho tìm & thay thế.
Substitution — s/cũ/mới/
$ sed 's/foo/bar/' file.txt # thay foo → bar (dòng đầu tiên gặp)
$ sed 's/foo/bar/g' file.txt # g = global, thay TẤT CẢ trên mỗi dòng
$ sed 's|/usr/bin|/opt/bin|g' conf # dùng | làm delimiter khi chuỗi có /
$ sed -E 's/[0-9]+/N/g' file.txt # -E: regex extended; thay mọi số bằng N
In-place editing — -i
$ sed -i 's/old/new/g' config.txt # Linux: sửa trực tiếp file
$ sed -i '' 's/old/new/g' config.txt # macOS: BẮT BUỘC '' sau -i (BSD sed)
$ sed -i.bak 's/old/new/g' config.txt # tạo backup config.txt.bak (Linux)
sed -ised -i '' 's/.../.../' file — phải có chuỗi rỗng '' ngay sau -i, nếu không sed sẽ tưởng đối số kế tiếp là extension và tạo file backup tên kỳ quặc. Linux (GNU sed) thì sed -i 's/.../.../' file chạy bình thường. Đây là một trong những khác biệt khó chịu nhất khi viết script chạy ở cả hai bên.
Xóa dòng & chỉ in dòng khớp
$ sed '/^#/d' config.conf # xóa mọi dòng comment (bắt đầu bằng #)
$ sed '/^$/d' file.txt # xóa dòng trống
$ sed -n '/ERROR/p' app.log # -n: tắt in mặc định; chỉ in dòng có ERROR
$ sed -n '10,20p' file.txt # in dòng 10-20
$ sed '5d' file.txt # xóa dòng 5
4. awk — mini language cho text
Nghĩ về awk như "Python mini cho text" — nó đọc từng dòng, tách thành các field,
rồi cho bạn viết logic xử lý. Một dòng awk đôi khi tương đương 20 dòng Python.
Bảng tham chiếu biến built-in
| Biến | Ý nghĩa |
|---|---|
$0 | Toàn bộ dòng hiện tại |
$1, $2, $3... | Field thứ 1, 2, 3... (mặc định cách bằng whitespace) |
NR | Number of Records — số dòng đã đọc (line number hiện tại) |
NF | Number of Fields — số cột của dòng hiện tại |
FS | Field Separator (mặc định: whitespace) |
OFS | Output Field Separator (mặc định: space) |
Cú pháp cơ bản
$ awk '{print $1}' file.txt # in cột đầu
$ awk '{print $1, $3}' file.txt # cột 1 và 3 (cách bằng OFS)
$ awk '{print NR, $0}' file.txt # thêm số dòng phía trước
$ awk '{print NF}' file.txt # in số cột mỗi dòng
Field separator — -F
$ awk -F, '{print $2}' data.csv # delimiter là dấu phẩy
$ awk -F: '{print $1}' /etc/passwd # : là field separator
awk tách bằng whitespace (1 hoặc nhiều space/tab) — mạnh hơn cut.
Nếu file dùng delimiter cụ thể (vd CSV), luôn nhớ -F. Quên -F, với CSV là lỗi rất thường gặp:
$1 sẽ là cả dòng vì không có space.
BEGIN / END — chạy trước & sau khi đọc
$ awk 'BEGIN {print "Bắt đầu"} {print $1} END {print "Tổng:", NR}' file.txt
Filter (như WHERE trong SQL)
$ awk '$3 > 100' data.txt # in dòng có cột 3 > 100
$ awk '/ERROR/ {print $0}' app.log # dòng chứa ERROR
$ awk 'NR>1' data.csv # bỏ dòng tiêu đề (skip header)
Ví dụ thực tế 1 — Xử lý CSV
# sales.csv:
# date,product,amount
# 2026-01-15,coffee,45000
# 2026-01-15,tea,30000
# 2026-01-16,coffee,50000
$ awk -F, 'NR>1 {print $2, $3}' sales.csv
coffee 45000
tea 30000
coffee 50000
Ví dụ thực tế 2 — Tính tổng & trung bình cột
$ awk -F, 'NR>1 {sum += $3} END {print "Tổng:", sum}' sales.csv
Tổng: 125000
$ awk -F, 'NR>1 {sum += $3; n++} END {print "Trung bình:", sum/n}' sales.csv
Trung bình: 41666.7
Ví dụ thực tế 3 — Log analysis (đếm theo nhóm)
$ awk '{count[$1]++} END {for (ip in count) print count[ip], ip}' access.log | sort -rn
# Đếm số request theo IP (cột 1), sort giảm dần
4521 192.168.1.10
1203 10.0.0.5
847 172.16.0.99
5. jq — JSON xử lý
Mọi REST API hôm nay trả JSON. jq là awk cho JSON: query, filter, transform.
Cài: brew install jq.
Bảng tham chiếu cú pháp jq
| Cú pháp | Ý nghĩa |
|---|---|
. | Identity — trả nguyên input (pretty-print) |
.field | Lấy giá trị của field trong object |
.[] | Iterate — duyệt mọi phần tử trong array |
.[0] | Phần tử thứ 0 của array |
select(cond) | Chỉ giữ phần tử thoả điều kiện |
map(expr) | Áp expr cho mỗi phần tử array |
length | Độ dài array / số ký tự string / số key của object |
keys | Danh sách key của object |
| | Pipe trong jq (giống pipe shell, nhưng giữa các expression) |
Ví dụ với REST API
$ curl -s https://api.github.com/users/torvalds | jq '.'
# Pretty-print toàn bộ JSON, tô màu
$ curl -s https://api.github.com/users/torvalds | jq '.name'
"Linus Torvalds"
$ curl -s https://api.github.com/users/torvalds/repos | jq '.[].name'
# Lấy tên mọi repo (array → từng phần tử → field name)
$ curl -s https://api.github.com/users/torvalds/repos | jq '.[] | select(.stargazers_count > 1000) | .name'
# Chỉ repo có > 1000 sao
$ curl -s https://api.github.com/users/torvalds/repos | jq '[.[] | {name, stars: .stargazers_count}]'
# Map sang object mới gọn hơn → wrap thành array
$ curl -s https://api.github.com/users/torvalds/repos | jq 'length'
7
Ví dụ với file JSON local
# users.json: [{"name":"Alice","age":30},{"name":"Bob","age":25}]
$ jq '.[].name' users.json
"Alice"
"Bob"
$ jq 'map(.age) | add' users.json # tổng age = 55
$ jq -r '.[].name' users.json # -r: raw output, bỏ dấu nháy
6. Pipeline thực chiến — gộp tất cả
Bài toán 1: Top 10 IP gọi nhiều nhất từ access.log
# access.log dạng nginx: 192.168.1.10 - - [08/May/2026...] "GET /api ..." 200
$ awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
4521 192.168.1.10
1203 10.0.0.5
847 172.16.0.99
...
Bài toán 2: Top 10 URL được gọi nhiều nhất
$ awk '{print $7}' access.log | sort | uniq -c | sort -rn | head
# $7 trong nginx log là path của request (/api, /login...)
Bài toán 3: Đếm response status code
$ awk '{print $9}' access.log | sort | uniq -c | sort -rn
18203 200
452 304
189 404
23 500
Bài toán 4: Top contributor trong git log
$ git log --format='%an' | sort | uniq -c | sort -rn | head
312 Linus Torvalds
147 Greg KH
89 ...
Bài toán 5: Lọc CSV phức tạp
# Tìm các giao dịch > 100k, sort theo amount giảm dần, lấy top 5
$ awk -F, 'NR>1 && $3 > 100000 {print $0}' sales.csv | sort -t, -k3 -rn | head -5
head giữa) để xem dữ liệu mỗi bước trông thế nào.
7. Thử ngay trong terminal giả lập
Gợi ý các lệnh để thử (terminal demo có thể chỉ giả lập một phần — phần còn lại làm trên Terminal thật):
echo "apple,banana,cherry" | tr ',' '\n'echo "Hello World" | tr 'a-z' 'A-Z'printf "3\n1\n2\n10\n" | sort -nprintf "a\nb\na\nb\nc\n" | sort | uniq -cecho "id,name,age" | awk -F, '{print NF}'(kết quả: 3)echo "hello sed" | sed 's/sed/awk/'
8. Bài tập về nhà
Làm trên Terminal thật. Tạo một thư mục chuong5-practice và đặt mọi file đầu vào trong đó.
-
Tạo file
names.csvvới nội dung:
Dùngid,name,score 1,Alice,87 2,Bob,72 3,Charlie,95 4,Diana,68 5,Evan,91awkin ra tên và điểm (cột 2 và 3), bỏ dòng tiêu đề. - Vẫn từ
names.csv: tính tổng điểm và điểm trung bình bằngawk. - Lọc người có điểm
> 80bằngawk -F, '...'. -
Tạo
fruits.txtvới 10 dòng (có nhiều dòng trùng), ví dụ mỗi dòng 1 loại quả:apple,banana,apple,orange,apple,banana,kiwi,kiwi,apple,orange. Dùng pipelinesort | uniq -c | sort -rnđể liệt kê loại quả nào xuất hiện nhiều nhất. -
Phân tích log thật: tải một file access log mẫu (hoặc tự bịa 50 dòng theo format nginx).
Dùng pipeline
awk + sort + uniq + sort -rn + headđể tìm:- Top 5 IP gọi nhiều nhất.
- Đếm số request theo status code.
-
Bài
sed: tạo fileconfig.txtchứa vài dòng cólocalhost:3000. Dùngsedthay tất cả thànhexample.com:443và lưu thẳng vào file (chú ý macOS cầnsed -i ''). -
Vẫn
config.txt: dùngsedxóa mọi dòng comment (bắt đầu bằng#) và mọi dòng trống. -
Bài
jq: chạycurl -s https://api.github.com/users/torvalds/repos > repos.json. Dùngjqđể:- In số repo (
length). - Liệt kê tên repo và số sao, sort giảm dần theo sao.
- Chỉ in repo có ngôn ngữ là
"C".
- In số repo (
-
Kết hợp: dùng
git log --format='%an'trong một repo bất kỳ + pipeline đếm xem ai commit nhiều nhất top 5.
9. Quiz — Kiểm tra kiến thức
10. Tổng kết
- ✅ Cắt cột:
cut -d, -f1,3/ biến đổi:tr,column -t - ✅ Sắp xếp:
sort -n,sort -k,sort -t, - ✅ Đếm dòng trùng:
sort | uniq -c | sort -rn(idiom phải nhớ) - ✅
sed 's/old/new/g'+ lưu ý macOSsed -i '' - ✅
awk:$1,NR,NF,-F,BEGIN/END, sum/avg, filter - ✅
jq:.field,.[],select(),map(),length - ✅ Pipeline thực chiến: log analysis, CSV, JSON API
Chương 6 chúng ta sẽ chuyển sang Process & Job control — quản lý tiến trình đang chạy:
ps, top/htop, kill, foreground/background, jobs, nohup, &.
Sau khi đã thuần thục text, chúng ta sẽ điều khiển những thứ đang chạy trong máy.