Lệnh awk: in trường, dấu phân cách -F và cách dùng mẫu điều kiện

Môi trường thử nghiệmHọ RHELAlmaLinux 9.8Tài khoản rootKiểm tra ngày 2026-10-29

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

  • ↪️ Số trường và print

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.txt
alice   pass  82
bob     fail  47
carol   pass  91
dave    pass  68
erin    fail  55

Dấ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.txt
alice 82
bob 47
carol 91
dave 68
erin 55

Nế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.txt
alice82
bob47
carol91
dave68
erin55

NR 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.txt
1 3 82
2 3 47
3 3 91
4 3 68
5 3 55

Nhậ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ườngGiá trị ở dòng đầuÝ nghĩa
$1192.0.2.10IP máy khách
$4[29/Oct/2026:00:01:12Ngày và giờ yêu cầu
$6"GETPhương thức yêu cầu (có dấu nháy kép phía trước)
$7/index.htmlURL yêu cầu
$9200Mã trạng thái
$105120Số 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.log
192.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 200

Cộ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 -3
15 x86_64)"
15 x86_64)"
15 x86_64)"

2️⃣ Chỉ định dấu phân cách bằng awk -F

  • ↪️ Dấu phân cách hai chấm, dấu phẩy và biểu thức chính quy

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.txt
root: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/nologin

Dấ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.txt
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
nobody /sbin/nologin
user /bin/bash
deploy /bin/bash
backup /sbin/nologin

Tệ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.csv
item 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.csv
item
"cable
keyboard

Vớ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.csv
item
"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.txt
root bash
bin nologin
daemon nologin
nobody nologin
user bash
deploy bash
backup nologin

3️⃣ Mẫu điều kiện của lệnh awk

  • ↪️ Biểu thức chính quy, phép so sánh và NR

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.log
203.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 404

Giá 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.log
203.0.113.25 /wp-login.php
203.0.113.25 /wp-login.php
203.0.113.25 /.env

Khi 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.txt
alice
carol
dave

So 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.txt
nobody 65534
user 1000
deploy 1001
backup 1002

Hai đ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.txt
user /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.txt
root
user
deploy
awk -F: '$7 !~ /nologin/ {print $1, $7}' users.txt
root /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.csv
keyboard 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.txt

Khi 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.txt

4️⃣ awk BEGIN, END, tổng và trung bình

  • ↪️ Biến và định dạng đầu ra printf

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.

🔁 Thứ tự awk xử lý sales.csv từng dòng
Sơ đồ giải thích
🔁 Thứ tự xử lý
$ awk -F, 'NR > 1 {t += $3 * $4} END {printf "total %.2f\n", t}' sales.csv
① Dòng đã đọcNR = 1date,item,qty,priceNR = 22026-10-01,keyboard,2,45.00NR = 32026-10-01,mouse,5,19.90NR = 82026-10-04,monitor,2,189.00Không còn dòng để đọcDòng 8 là dòng cuối cùng.
② Chia trường (-F,)$1date$2item$3qty$4priceDòng tiêu đề cũng được chia theo cùng cách.$12026-10-01$2keyboard$32$445.00$3 số lượng, $4 đơn giá$12026-10-01$2mouse$35$419.90$3 số lượng, $4 đơn giá$12026-10-04$2monitor$32$4189.00$3 số lượng, $4 đơn giáKhông có dòng để chiaKhối END không đọc dòng.
③ Mẫu và hành độngNR > 1 saiBỏ qua hành động.t = (trống)NR > 1 đúngt += 2 * 45.00t = 90NR > 1 đúngt += 5 * 19.90t = 189.5NR > 1 đúngt += 2 * 189.00t = 886.3Chạy ENDtotal 886.30printf in t đến hai chữ số thập phân.
Dòng đầu là dòng tiêu đề nên NR > 1 là sai, và awk chuyển sang dòng tiếp theo mà không chạy hành động.
Từ dòng thứ hai, mẫu đúng và 90, tích của số lượng và đơn giá, được cộng vào biến t.
Biến t giữ giá trị dù chuyển dòng, nên tổng đến dòng thứ ba là 189.5.
Các dòng 4 đến 7 cũng xử lý theo cùng thứ tự, và đến dòng cuối là dòng 8, t đạt 886.3.
Sau khi xử lý dòng cuối, khối END chạy một lần và printf in total 886.30.

Tổ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.log
41298

Nế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.csv
2 90
3 189.5
4 378.5
5 423.5
6 468.5
7 508.3
8 886.3

Trung 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.txt
343 5 68.6

BEGIN 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.csv
item total
keyboard 90
mouse 99.5
monitor 189
keyboard 45
cable 45
mouse 39.8
monitor 378

Dấ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.txt
root,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.csv
keyboard     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.00

Khá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.csv
keyboard mouse monitor keyboard cable mouse monitor

5️⃣ Phân tích nhật ký và tùy chọn của lệnh awk

  • ↪️ Thống kê IP máy khách và mã trạng thái, tóm tắt tùy chọn

🔲 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 -rn
4 192.0.2.10
3 203.0.113.25
3 198.51.100.7
2 192.0.2.44

Thứ 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 | sort
200 6
301 1
304 1
404 3
500 1

Chỉ 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.log
404 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=awk

Tổ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 -rn
31340 192.0.2.10
5120 192.0.2.44
4250 198.51.100.7
588 203.0.113.25

Trườ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ọnChức năngKết quả trong bài này
-F fsChỉ đị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=valGán giá trị cho biến trước khi chạy chương trìnhTruyề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 inplaceLưu đầu ra vào tệp gốc thay vì màn hìnhSau 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
--versionIn thông tin phiên bảnDò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ọnChứ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-textDù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 fileGiố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 #!
--traditionalChạy ở chế độ tương thích, không nhận các phần mở rộng GNU
--posixBậ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
--lintCả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-variablesGhi 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.

Trình mô phỏng lệnh awkGNU Awk 5
[root@server work]#
Trình thông dịch awk nhỏ chỉ chạy trong trình duyệt của bạn. Bốn tệp thử nghiệm giống các tệp dùng khi đo thực tế, và ↑↓ gọi lại các lệnh trước đó.

🔢 FAQ & nội dung đề xuất

$min trong dấu nháy đơn không được shell thay thế mà đến awk nguyên vẹn, nên hãy truyền bằng tùy chọn -v. Khi đặt 1000 vào biến shell min rồi truyền -v min=$min, sẽ có sáu dòng có số byte đã gửi từ 1000 trở lên. Nếu bọc chương trình awk trong dấu nháy kép, shell sẽ thay cả $1 của [print $1] bằng giá trị trống trước, và cả dòng được in ra.

Khi thêm -i inplace, GNU Awk lưu đầu ra vào tệp gốc thay vì màn hình. Khi chạy chương trình in trường thứ nhất và thứ ba trên bản sao scores.txt bằng gawk -i inplace, màn hình không hiện gì và tệp đổi thành hai cột tên và điểm. Vì tệp gốc bị ghi đè ngay, thêm -v inplace::suffix=.bak sẽ giữ nội dung trước khi đổi trong tệp .bak.

Trên máy chủ AlmaLinux 9, /usr/bin/awk là liên kết tượng trưng tới gawk, và dòng đầu của [awk --version] là phiên bản GNU Awk 5. Trên máy chủ này, hai tên chạy cùng một chương trình. Trước khi dùng tính năng GNU Awk như -i inplace hay FPAT, hãy kiểm tra bằng [awk --version] xem có phải GNU Awk không.

ℹ️ 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á.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Prove your humanity: 10   +   9   =