Lệnh awk: in trường, dấu phân cách -F và cách dùng mẫu điều kiện
Lệnh awk là công cụ Linux đọc tệp văn bản từng dòng, chia mỗi dòng thành các trường theo khoảng trắng hoặc dấu phân cách bạn chỉ định, rồi chỉ chọn các trường của những dòng khớp điều kiện để in ra hoặc tính toán. Trên máy chủ AlmaLinux 9, chúng tôi đã thống kê số yêu cầu theo IP và số lượng theo mã trạng thái từ tệp nhật ký truy cập mẫu của máy chủ web bằng một dòng lệnh nối với sort qua pipe, và thấy cả ba phản hồi 404 trong mười hai dòng đều là yêu cầu wp-login.php và .env từ cùng một IP.
Đầu ra bên dưới có được khi chạy bằng tài khoản root trong thư mục làm việc trên máy chủ AlmaLinux 9.8. Nhật ký truy cập, danh sách tài khoản, bản ghi bán hàng và tệp điểm là các tệp mẫu tạo ra để thử nghiệm, địa chỉ IP và tên tài khoản bên trong cũng là giá trị ví dụ.
1️⃣ Cách dùng cơ bản lệnh awk
awk chia dòng đọc được theo khoảng trắng và đánh số các trường từ đầu là $1, $2, $3. Chúng tôi kiểm tra bằng scores.txt, trong đó tên, kết quả và điểm được ngăn cách bằng khoảng trắng.
cat scores.txtalice pass 82
bob fail 47
carol pass 91
dave pass 68
erin fail 55Dấu ngoặc nhọn trong dấu nháy đơn là hành động chạy trên mỗi dòng, và nếu liệt kê số trường sau print, cách nhau bằng dấu phẩy, chỉ các trường đó được in ra. In trường thứ nhất và thứ ba thì chỉ còn tên và điểm.
awk '{print $1, $3}' scores.txtalice 82
bob 47
carol 91
dave 68
erin 55Nếu bỏ dấu phẩy giữa các trường và viết $1 $3, hai giá trị sẽ dính liền không có khoảng trắng. Dấu phẩy của print chèn dấu phân cách đầu ra (mặc định là một khoảng trắng), còn các giá trị viết cạnh nhau không có dấu phẩy sẽ nối thành một chuỗi.
awk '{print $1 $3}' scores.txtalice82
bob47
carol91
dave68
erin55NR là số dòng đã đọc đến lúc đó, NF là số trường của dòng đó. $NF nghĩa là trường cuối cùng, nên bạn có thể chọn cột cuối dù số trường mỗi dòng khác nhau.
awk '{print NR, NF, $NF}' scores.txt1 3 82
2 3 47
3 3 91
4 3 68
5 3 55Nhật ký truy cập máy chủ web cũng được ngăn cách bằng khoảng trắng nên bạn chọn trường theo cùng cách. Số trường của dòng đầu trong nhật ký thử nghiệm như sau.
| Trường | Giá trị ở dòng đầu | Ý nghĩa |
|---|---|---|
| $1 | 192.0.2.10 | IP máy khách |
| $4 | [29/Oct/2026:00:01:12 | Ngày và giờ yêu cầu |
| $6 | "GET | Phương thức yêu cầu (có dấu nháy kép phía trước) |
| $7 | /index.html | URL yêu cầu |
| $9 | 200 | Mã trạng thái |
| $10 | 5120 | Số byte đã gửi |
| $11 | "-" | Trang trước (Referer) |
| $12~$15 | "Mozilla/5.0 (X11; Linux x86_64)" | User agent (bị chia ở mỗi khoảng trắng) |
Chúng tôi chọn in $1, $7 và $9, tương ứng với IP máy khách, URL yêu cầu và mã trạng thái.
awk '{print $1, $7, $9}' access.log192.0.2.10 /index.html 200
198.51.100.7 /about.html 200
192.0.2.10 /images/logo.png 200
203.0.113.25 /wp-login.php 404
203.0.113.25 /wp-login.php 404
192.0.2.10 /contact.html 200
198.51.100.7 /old-page.html 301
203.0.113.25 /.env 404
192.0.2.44 /index.html 200
198.51.100.7 /api/search?q=awk 500
192.0.2.44 /images/logo.png 304
192.0.2.10 /index.html 200Cột có khoảng trắng trong giá trị như user agent bị chia thành nhiều trường. NF của ba dòng đầu đều là 15, và $NF là mảnh cuối của user agent.
awk '{print NF, $NF}' access.log | head -315 x86_64)"
15 x86_64)"
15 x86_64)"2️⃣ Chỉ định dấu phân cách bằng awk -F
Với tệp có cột ngăn cách bằng ký hiệu khác khoảng trắng, hãy viết ký hiệu đó sau -F. Khi in $1 mà không có -F từ danh sách tài khoản users.txt có cùng dạng với /etc/passwd, hầu hết các dòng hiện ra nguyên vẹn.
awk '{print $1}' users.txtroot:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
nobody:x:65534:65534:Kernel
user:x:1000:1000:Example
deploy:x:1001:1001:Deploy
backup:x:1002:1002::/home/backup:/sbin/nologinDấu phân cách mặc định là khoảng trắng nên dòng chỉ nối bằng dấu hai chấm chỉ có một trường. Chỉ các dòng nobody, user và deploy có khoảng trắng ở cột mô tả mới bị cắt tại khoảng trắng đó.
Khi chỉ định dấu hai chấm làm dấu phân cách bằng -F:, tên tài khoản ($1) và shell đăng nhập ($7) được tách ra.
awk -F: '{print $1, $7}' users.txtroot /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
nobody /sbin/nologin
user /bin/bash
deploy /bin/bash
backup /sbin/nologinTệp CSV ngăn cách bằng dấu phẩy được chia bằng -F,. Khi in mặt hàng và số lượng từ bản ghi bán hàng sales.csv, tiêu đề ở dòng đầu (item qty) cũng được chia theo cùng cách và hiện cùng.
awk -F, '{print $2, $3}' sales.csvitem qty
keyboard 2
mouse 5
monitor 1
keyboard 1
cable 10
mouse 2
monitor 2-F, không phù hợp với CSV có dấu phẩy trong cột bọc bằng dấu nháy kép. Với quoted.csv có dấu phẩy ở cột mặt hàng, các trường cũng bị chia tại dấu phẩy trong dấu nháy, nên trường thứ hai chỉ còn phần đầu của tên mặt hàng.
awk -F, '{print $2}' quoted.csvitem
"cable
keyboardVới các tệp như vậy, tài liệu chính thức của GNU Awk hướng dẫn dùng biến FPAT. Nếu viết hình dạng của một trường bằng biểu thức chính quy thay cho dấu phân cách, cột bọc trong dấu nháy sẽ ra thành một trường.
awk -v FPAT='([^,]*)|("[^"]*")' '{print $2}' quoted.csvitem
"cable, usb"
keyboardĐể dùng nhiều dấu phân cách, hãy viết thành biểu thức chính quy trong ngoặc vuông. Khi chỉ định cả dấu hai chấm và dấu gạch chéo làm dấu phân cách, $NF trở thành tên cuối của đường dẫn shell đăng nhập (bash, nologin).
awk -F'[:/]' '{print $1, $NF}' users.txtroot bash
bin nologin
daemon nologin
nobody nologin
user bash
deploy bash
backup nologin3️⃣ Mẫu điều kiện của lệnh awk
Dù đã chọn trường, mọi dòng vẫn được in, nên để chỉ giữ các dòng cần thiết, hãy đặt điều kiện (mẫu) trước dấu ngoặc nhọn. Chuỗi đặt giữa hai dấu gạch chéo chỉ chọn các dòng chứa chuỗi đó, và nếu bỏ hành động thì cả dòng được in ra.
awk '/wp-login/' access.log203.0.113.25 - - [29/Oct/2026:00:04:51 +0000] "POST /wp-login.php HTTP/1.1" 404 196 "-" "Mozilla/5.0 (X11; Linux x86_64)"
203.0.113.25 - - [29/Oct/2026:00:04:52 +0000] "POST /wp-login.php HTTP/1.1" 404 196 "-" "Mozilla/5.0 (X11; Linux x86_64)"Kết quả chọn dòng giống với lệnh grep, và awk có thể chọn luôn các trường từ những dòng đó rồi in ra. Khi chuyển hai dòng đó đã chọn bằng grep sang awk qua pipe, chỉ còn IP và mã trạng thái.
grep wp-login access.log | awk '{print $1, $9}'203.0.113.25 404
203.0.113.25 404Giá trị trường được so sánh bằng ==, !=, >, >=, <, <=. Chúng tôi chỉ chọn các dòng có mã trạng thái ($9) là 404 rồi in IP và URL yêu cầu.
awk '$9 == 404 {print $1, $7}' access.log203.0.113.25 /wp-login.php
203.0.113.25 /wp-login.php
203.0.113.25 /.envKhi so sánh với chuỗi, hãy bọc giá trị trong dấu nháy kép. Chúng tôi chỉ chọn các tên có trường thứ hai là pass.
awk '$2 == "pass" {print $1}' scores.txtalice
carol
daveSo sánh số dùng khi chọn theo khoảng, như số tài khoản (UID). Khi chọn tài khoản có UID từ 1000 trở lên, cùng với các tài khoản thường còn có nobody với UID 65534.
awk -F: '$3 >= 1000 {print $1, $3}' users.txtnobody 65534
user 1000
deploy 1001
backup 1002Hai điều kiện được nối bằng &&. Khi thêm điều kiện nhỏ hơn 65534, nobody bị loại và chỉ còn ba tài khoản thường cùng thư mục home.
awk -F: '$3 >= 1000 && $3 < 65534 {print $1, $6}' users.txtuser /home/user
deploy /home/deploy
backup /home/backup[~] kiểm tra trường có khớp biểu thức chính quy không, [!~] kiểm tra không khớp. Khi chọn lần lượt các tài khoản có shell đăng nhập ($7) kết thúc bằng bash và các tài khoản không phải nologin, kết quả là cùng ba tài khoản.
awk -F: '$7 ~ /bash$/ {print $1}' users.txtroot
user
deployawk -F: '$7 !~ /nologin/ {print $1, $7}' users.txtroot /bin/bash
user /bin/bash
deploy /bin/bashĐiều kiện NR chọn theo số dòng. Khi bỏ dòng tiêu đề của CSV bằng NR > 1, chỉ còn lại bản ghi bán hàng.
awk -F, 'NR > 1 {print $2, $3}' sales.csvkeyboard 2
mouse 5
monitor 1
keyboard 1
cable 10
mouse 2
monitor 2Đầu ra của lệnh khác cũng có thể chuyển qua pipe và chia theo cùng cách. Khi chọn kích thước tệp ($5) và tên ($9) từ đầu ra ls -l, dòng đầu hiện trống vì dòng đầu của ls -l là [total 24] không có trường thứ năm và thứ chín.
ls -l | awk '{print $5, $9}'
1567 access.log
79 quoted.csv
207 sales.csv
85 scores-copy.txt
85 scores.txt
315 users.txtKhi thêm NR > 1, dòng đầu bị bỏ và chỉ còn kích thước cùng tên tệp.
ls -l | awk 'NR > 1 {print $5, $9}'1567 access.log
79 quoted.csv
207 sales.csv
85 scores-copy.txt
85 scores.txt
315 users.txt4️⃣ awk BEGIN, END, tổng và trung bình
Khối BEGIN chạy một lần trước khi đọc dòng đầu, khối END chạy một lần sau khi xử lý dòng cuối. Để tính tổng, hãy cộng vào biến ở mỗi dòng rồi in trong END. Sơ đồ dưới đây cho thấy thứ tự awk đi qua ở mỗi dòng khi tính tổng tiền trong bản ghi bán hàng.
$ awk -F, 'NR > 1 {t += $3 * $4} END {printf "total %.2f\n", t}' sales.csvTổng số byte đã gửi ($10) trong nhật ký truy cập là 41298 byte. Biến dùng lần đầu bắt đầu từ giá trị trống (dạng số là 0), nên không cần gán 0.
awk '{sum += $10} END {print sum}' access.log41298Nếu in tổng trong hành động chạy ở mỗi dòng thay vì END, mỗi dòng sẽ hiện tổng tích lũy đến lúc đó. Khi in số dòng và t mỗi lần cộng tích số lượng và đơn giá vào t, sẽ có bảy dòng không tính tiêu đề, và 886.3 ở dòng cuối là tổng toàn bộ.
awk -F, 'NR > 1 {t += $3 * $4; print NR, t}' sales.csv2 90
3 189.5
4 378.5
5 423.5
6 468.5
7 508.3
8 886.3Trung bình được tính trong END bằng cách chia tổng cho số dòng. Tổng năm điểm là 343 và trung bình là 68.6.
awk '{total += $3; n++} END {print total, n, total / n}' scores.txt343 5 68.6BEGIN dùng để in một lần dòng tiêu đề phía trên kết quả. Phía trên mặt hàng và số tiền (số lượng × đơn giá), dòng item total chỉ xuất hiện một lần.
awk -F, 'BEGIN {print "item", "total"} NR > 1 {print $2, $3 * $4}' sales.csvitem total
keyboard 90
mouse 99.5
monitor 189
keyboard 45
cable 45
mouse 39.8
monitor 378Dấu phân cách đầu ra được đổi bằng biến OFS trong BEGIN. Khi đặt OFS là dấu phẩy, vị trí dấu phẩy của print sẽ là dấu phẩy thay cho khoảng trắng, và danh sách tài khoản ra ở dạng CSV.
awk 'BEGIN {FS = ":"; OFS = ","} {print $1, $3, $7}' users.txtroot,0,/bin/bash
bin,1,/sbin/nologin
daemon,2,/sbin/nologin
nobody,65534,/sbin/nologin
user,1000,/bin/bash
deploy,1001,/bin/bash
backup,1002,/sbin/nologinĐể căn cột, hãy dùng printf. %-10s là chuỗi căn trái 10 ô, %3d là số nguyên 3 ô, và %8.2f là số 8 ô có hai chữ số thập phân.
awk -F, 'NR > 1 {printf "%-10s %8.2f\n", $2, $3, $3 * $4}' sales.csvkeyboard 2 90.00
mouse 5 99.50
monitor 1 189.00
keyboard 1 45.00
cable 10 45.00
mouse 2 39.80
monitor 2 378.00Khác với print, printf không xuống dòng nên hãy viết [\n] ở cuối định dạng. Nếu chạy không có [\n], tên mặt hàng sẽ nối liền trên một dòng.
awk -F, 'NR > 1 {printf "%s ", $2}' sales.csvkeyboard mouse monitor keyboard cable mouse monitor5️⃣ Phân tích nhật ký và tùy chọn của lệnh awk
🔲 Phân tích nhật ký
Cộng vào một biến chỉ cho một tổng, nên để đếm riêng theo IP máy khách hoặc mã trạng thái, hãy gom giá trị vào mảng. n[$1]++ cộng 1 vào ô mảng có khóa là IP, và for (ip in n) trong END lấy từng ô đã gom ra.
awk '{n[$1]++} END {for (ip in n) print n[ip], ip}' access.log | sort -rn4 192.0.2.10
3 203.0.113.25
3 198.51.100.7
2 192.0.2.44Thứ tự for (ip in n) lấy ra không được xác định (theo tài liệu GNU Awk), nên chúng tôi sắp xếp theo số yêu cầu bằng sort -rn. 192.0.2.10 nhiều nhất với 4 yêu cầu.
Số lượng theo mã trạng thái cũng đếm theo cùng cách. Có 3 mã 404 và 1 mã 500.
awk '{n[$9]++} END {for (s in n) print s, n[s]}' access.log | sort200 6
301 1
304 1
404 3
500 1Chỉ lấy mã trạng thái rồi chuyển cho sort và uniq -c cũng ra cùng số lượng.
awk '{print $9}' access.log | sort | uniq -c 6 200
1 301
1 304
3 404
1 500Để chỉ xem phản hồi lỗi, hãy đặt điều kiện cho mã trạng thái. Các dòng từ 400 trở lên là ba yêu cầu tìm wp-login.php và .env cùng một lỗi 500.
awk '$9 >= 400 {print $9, $1, $7}' access.log404 203.0.113.25 /wp-login.php
404 203.0.113.25 /wp-login.php
404 203.0.113.25 /.env
500 198.51.100.7 /api/search?q=awkTổng số byte đã gửi theo IP cũng được tính bằng cách cộng vào mảng. 192.0.2.10 nhận nhiều nhất với 31340 byte.
awk '{b[$1] += $10} END {for (ip in b) print b[ip], ip}' access.log | sort -rn31340 192.0.2.10
5120 192.0.2.44
4250 198.51.100.7
588 203.0.113.25Trường thời gian ($4) có thể so sánh như chuỗi. Giữa các dòng cùng ngày và định dạng, thứ tự chuỗi trùng với thứ tự thời gian, nên chúng tôi chỉ chọn bốn yêu cầu từ 00:05 đến trước 00:10.
awk '$4 >= "[29/Oct/2026:00:05" && $4 < "[29/Oct/2026:00:10" {print $4, $7}' access.log[29/Oct/2026:00:05:30 /contact.html
[29/Oct/2026:00:07:02 /old-page.html
[29/Oct/2026:00:07:15 /.env
[29/Oct/2026:00:09:48 /index.html🔲 Tùy chọn của lệnh awk
📌 Tùy chọn chính: kết quả chạy trong bài này
| Tùy chọn | Chức năng | Kết quả trong bài này |
|---|---|---|
| -F fs | Chỉ định dấu phân cách trường đầu vào | -F: tách tên tài khoản ($1) và shell đăng nhập ($7) của users.txt, -F, in mặt hàng và số lượng của sales.csv cùng tiêu đề (item qty) |
| -v var=val | Gán giá trị cho biến trước khi chạy chương trình | Truyền biến shell min bằng -v min=$min sẽ in sáu dòng có số byte đã gửi từ 1000 trở lên, -v FPAT=… khiến cột bọc trong dấu nháy của quoted.csv ra thành một trường |
| -i inplace | Lưu đầu ra vào tệp gốc thay vì màn hình | Sau khi chạy gawk -i inplace, màn hình không hiện gì và bản sao scores.txt đổi thành hai cột tên và điểm, thêm -v inplace::suffix=.bak thì nội dung trước khi đổi được giữ trong tệp .bak |
| --version | In thông tin phiên bản | Dòng đầu của [awk --version] là phiên bản GNU Awk 5 |
📎 Tùy chọn khác: theo tài liệu hướng dẫn chính thức
| Tùy chọn | Chức năng |
|---|---|
| -f program-file | Đọc chương trình AWK từ tệp thay vì đối số dòng lệnh đầu tiên, có thể chỉ định -f nhiều lần |
| -e program-text | Dùng văn bản trên dòng lệnh làm mã nguồn chương trình AWK, có thể dùng cùng hàm thư viện nạp bằng -f hoặc --include |
| -E file | Giống -f nhưng được xử lý cuối cùng và chặn gán biến trên dòng lệnh, dành cho script #! |
| --traditional | Chạy ở chế độ tương thích, không nhận các phần mở rộng GNU |
| --posix | Bật chế độ tương thích và thêm giới hạn, ví dụ không nhận func, từ đồng nghĩa của function |
| --lint | Cảnh báo cấu trúc đáng ngờ hoặc khó chuyển sang bản AWK khác, với --lint=fatal cảnh báo thành lỗi nghiêm trọng |
| --dump-variables | Ghi danh sách đã sắp xếp của biến toàn cục cùng kiểu và giá trị cuối vào tệp, không chỉ định tệp thì là awkvars.out |
🔲 Trình mô phỏng lệnh awk
Trong trình mô phỏng bên dưới, bạn có thể tự nhập lệnh awk trên cùng các tệp thử nghiệm.
🔢 FAQ & nội dung đề xuất
Sắp xếp
Lệnh sort trên Linux: sắp xếp tệp
Lệnh sort sắp xếp nội dung tệp theo bảng chữ cái hoặc theo số. Dùng tiếp theo để xem kết quả tổng hợp của awk từ nhiều đến ít.
Loại bỏ trùng lặp
Lệnh uniq trên Linux
Lệnh uniq gộp các dòng giống nhau liền kề thành một và đếm số lần lặp bằng -c.
Xem tệp
Lệnh head và lệnh tail trên Linux
Lệnh head và tail chỉ xem vài dòng đầu hoặc cuối của tệp. Thường dùng khi kiểm tra nhật ký truy cập của máy chủ.
ℹ️ Thông báo tiếp thị liên kết
Nội dung trên trang này có chứa liên kết affiliate (tiếp thị liên kết). Khi khách truy cập mua sản phẩm hoặc dịch vụ qua các liên kết này, trang sẽ nhận hoa hồng từ bên bán. Trong quá trình đó, số tiền người mua phải trả (sẽ giảm khi có chương trình khuyến mãi ↓) không hề tăng lên. Giá, khuyến mãi và tình trạng hàng được đăng theo thời điểm viết bài và có thể khác với thực tế, vì vậy hãy kiểm tra lại lần cuối với bên bán trước khi mua. Việc chọn và đánh giá sản phẩm dựa trên tiêu chí riêng của trang; việc có nhận hoa hồng hay không không ảnh hưởng đến thứ tự giới thiệu hay nội dung đánh giá.