Featured image of post Text Processing trong Bash: grep, awk, sed cho DevOps

Text Processing trong Bash: grep, awk, sed cho DevOps

Hướng dẫn xử lý text trong Bash với grep, awk và sed. Kèm ví dụ DevOps: lọc log Nginx, đếm IP unique và chỉnh config an toàn.

Text processing trong Bash cho DevOps

bài trước chúng ta đã xử lý file: đọc, ghi, redirect, tee, findxargs. Nhưng file text chỉ thật sự hữu ích khi bạn biết lọc, trích xuất và biến đổi dữ liệu trong đó.

Trong DevOps, grep, awksed xuất hiện ở khắp nơi: lọc log lỗi, lấy status code từ access log, đếm IP truy cập nhiều nhất, thay đổi config trước khi deploy, hoặc tạo report nhanh từ output của command. Bài này đi qua ba công cụ theo hướng thực dụng, có ví dụ gần với vận hành hằng ngày.


grep: tìm dòng khớp pattern

grep đọc input và in ra những dòng khớp pattern. Đây thường là bước đầu tiên khi bạn cần tìm nhanh thông tin trong log hoặc config.

1
2
grep "ERROR" app.log
grep "server_name" /etc/nginx/conf.d/*.conf

Một số option hay dùng:

1
2
3
4
5
6
grep -n "ERROR" app.log          # in kèm line number
grep -i "timeout" app.log        # ignore case
grep -v "healthcheck" app.log    # loại trừ dòng khớp
grep -r "DATABASE_URL" ./config  # tìm đệ quy trong thư mục
grep -E "ERROR|WARN" app.log     # extended regex
grep -F "[literal]" app.log      # fixed string, không coi là regex

Theo GNU grep, -E dùng extended regular expression, còn -F dùng fixed strings. Khi pattern là chuỗi literal đơn giản, grep -F giúp tránh lỗi do ký tự như ., [, * bị hiểu là regex.


grep context: xem dòng trước và sau lỗi

Khi debug log, chỉ một dòng lỗi thường chưa đủ. GNU grep hỗ trợ context:

1
2
3
grep -A 3 "ERROR" app.log  # 3 dòng sau match
grep -B 3 "ERROR" app.log  # 3 dòng trước match
grep -C 3 "ERROR" app.log  # 3 dòng trước và sau match

Ví dụ xem lỗi deploy kèm ngữ cảnh:

1
grep -n -C 5 "deploy failed" ./logs/deploy.log

Khi có nhiều nhóm match cách xa nhau, grep mặc định chèn dòng -- để phân tách context group. Điều này hữu ích khi đọc terminal, nhưng nếu output đưa vào script khác, bạn nên nhớ dòng separator này có thể xuất hiện.


grep trong pipeline DevOps

Ví dụ lọc log ứng dụng, bỏ healthcheck và chỉ xem lỗi nghiêm trọng:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/usr/bin/env bash
set -euo pipefail

LOG_FILE="${1:-./logs/app.log}"

if [[ ! -r "${LOG_FILE}" ]]; then
  echo "ERROR: Cannot read log file: ${LOG_FILE}"
  exit 1
fi

grep -E "ERROR|FATAL" "${LOG_FILE}" | grep -v "healthcheck" || true

grep trả exit code 1 khi không có match, pipeline có thể làm script dừng nếu đang dùng set -e. Trong tình huống “không có lỗi” là bình thường, thêm || true ở cuối pipeline là chấp nhận được. Với pipeline quan trọng hơn, hãy xử lý exit code rõ ràng bằng if grep ...; then ... fi.


awk: xử lý theo dòng và theo cột

awk đọc input theo record, mặc định mỗi dòng là một record. Mỗi dòng được tách thành field: $1, $2, $3… Toàn bộ dòng là $0.

1
2
awk '{ print $1 }' access.log
awk '{ print $1, $9 }' access.log

Một số biến built-in quan trọng:

  • NR: số record đã đọc từ đầu input.
  • NF: số field của record hiện tại.
  • $0: toàn bộ dòng hiện tại.
  • $1, $2, …: field thứ nhất, thứ hai, …
  • FS: field separator input.
  • OFS: output field separator.

Ví dụ in số dòng và số field:

1
awk '{ print NR, NF, $0 }' app.log

Theo GNU awk manual, NR tăng mỗi khi awk đọc record mới, còn NF là số field của record hiện tại. Đây là hai biến rất hữu ích khi cần validate dữ liệu text nhanh.


awk BEGIN, END và điều kiện

BEGIN chạy trước khi đọc input. END chạy sau khi đọc hết input. Phần giữa là rule áp dụng cho từng dòng.

1
awk 'BEGIN { print "status,count" } { count[$9]++ } END { for (code in count) print code "," count[code] }' access.log

Ví dụ dễ đọc hơn, đếm HTTP status code từ Nginx access log dạng phổ biến:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
awk '
  BEGIN {
    print "status,count"
  }
  {
    status[$9]++
  }
  END {
    for (code in status) {
      print code "," status[code]
    }
  }
' access.log

Trong access log phổ biến, $1 thường là IP client, $7 là path và $9 là HTTP status. Tuy nhiên format log có thể khác theo cấu hình Nginx/Apache, nên hãy kiểm tra vài dòng mẫu trước khi hardcode vị trí field.


awk với delimiter tùy chỉnh

Mặc định awk tách field theo whitespace. Với file dạng key-value hoặc CSV đơn giản, dùng -F để chọn delimiter.

1
2
awk -F= '{ print $1, $2 }' app.env
awk -F: '{ print $1, $7 }' /etc/passwd

Ví dụ đọc file .env và bỏ comment/dòng rỗng:

1
2
3
4
5
6
7
awk -F= '
  /^[[:space:]]*#/ { next }
  NF < 2 { next }
  {
    print "key=" $1
  }
' .env

Với CSV phức tạp có quote, comma bên trong field hoặc escape, awk -F, không đủ an toàn. Khi đó nên dùng parser CSV đúng nghĩa bằng Python, Go hoặc công cụ chuyên dụng.


sed: thay thế và chỉnh text theo dòng

sed là stream editor: đọc input, áp dụng script, rồi ghi output. Lệnh nổi tiếng nhất là substitute:

1
2
sed 's/old/new/' file.txt      # thay match đầu tiên mỗi dòng
sed 's/old/new/g' file.txt     # thay tất cả match trên mỗi dòng

Theo GNU sed manual, cú pháp cơ bản là s/regexp/replacement/flags. Flag g thay tất cả match trong dòng thay vì chỉ match đầu tiên.

Ví dụ đổi endpoint trong file config và ghi ra file mới:

1
sed 's|http://localhost:8080|https://api.example.com|g' app.conf > app.conf.new

Ở đây dùng delimiter | thay vì / để tránh phải escape nhiều dấu / trong URL.


sed theo address và in-place edit

Bạn có thể giới hạn sed chỉ tác động trên dòng khớp pattern hoặc range.

1
2
sed '/^LOG_LEVEL=/s/=.*$/=debug/' app.env
sed '10,20s/enabled=false/enabled=true/' feature.conf

GNU sed hỗ trợ -i để chỉnh file tại chỗ. Nếu truyền suffix, sed tạo backup trước khi rename file tạm thành file gốc:

1
sed -i.bak 's/^LOG_LEVEL=.*/LOG_LEVEL=info/' app.env

Cẩn thận với sed -i:

  • Luôn test không -i trước để xem output.
  • Dùng suffix như .bak khi chỉnh file quan trọng.
  • Khác biệt cú pháp sed -i giữa GNU sed và BSD/macOS sed có thể làm script kém portable.

Thực hành DevOps: parse Nginx log

Giả sử access log có format phổ biến:

1
2
3
203.0.113.10 - - [08/Jun/2026:10:12:01 +0700] "GET /api/health HTTP/1.1" 200 12
198.51.100.23 - - [08/Jun/2026:10:12:02 +0700] "POST /api/login HTTP/1.1" 401 64
203.0.113.10 - - [08/Jun/2026:10:12:03 +0700] "GET /api/users HTTP/1.1" 200 532

Lọc lỗi 4xx/5xx bằng awk:

1
awk '$9 ~ /^[45][0-9][0-9]$/ { print $1, $7, $9 }' access.log

Đếm top IP truy cập nhiều nhất:

1
2
3
awk '{ count[$1]++ } END { for (ip in count) print count[ip], ip }' access.log \
  | sort -nr \
  | head -n 10

Đếm status code:

1
2
awk '{ status[$9]++ } END { for (code in status) print code, status[code] }' access.log \
  | sort -n

Nếu bạn muốn bỏ các request healthcheck khỏi thống kê:

1
2
3
grep -v '"GET /api/health ' access.log \
  | awk '{ status[$9]++ } END { for (code in status) print code, status[code] }' \
  | sort -n

Thực hành DevOps: đổi config trước khi deploy

Ví dụ script cập nhật LOG_LEVELFEATURE_FLAG trong file .env, có backup trước khi sửa.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env bash
set -euo pipefail

ENV_FILE="${1:-./app.env}"
LOG_LEVEL="${LOG_LEVEL:-info}"
FEATURE_FLAG="${FEATURE_FLAG:-false}"

if [[ ! -f "${ENV_FILE}" ]]; then
  echo "ERROR: env file not found: ${ENV_FILE}"
  exit 1
fi

if [[ ! -w "${ENV_FILE}" ]]; then
  echo "ERROR: env file is not writable: ${ENV_FILE}"
  exit 1
fi

cp -- "${ENV_FILE}" "${ENV_FILE}.bak.$(date +%Y%m%d%H%M%S)"

sed -i.bak \
  -e "s/^LOG_LEVEL=.*/LOG_LEVEL=${LOG_LEVEL}/" \
  -e "s/^FEATURE_FLAG=.*/FEATURE_FLAG=${FEATURE_FLAG}/" \
  "${ENV_FILE}"

if ! grep -q '^LOG_LEVEL=' "${ENV_FILE}"; then
  echo "LOG_LEVEL=${LOG_LEVEL}" >> "${ENV_FILE}"
fi

if ! grep -q '^FEATURE_FLAG=' "${ENV_FILE}"; then
  echo "FEATURE_FLAG=${FEATURE_FLAG}" >> "${ENV_FILE}"
fi

Script này minh họa cách phối hợp công cụ:

  • cp tạo backup rõ ràng trước khi chỉnh.
  • sed thay giá trị nếu key đã tồn tại.
  • grep -q kiểm tra key có tồn tại chưa.
  • >> append key còn thiếu.

Lưu ý: không đưa secret thật vào ví dụ hoặc commit. Với secret, ưu tiên biến môi trường, secret manager hoặc CI/CD secret store.


Thực hành DevOps: report lỗi từ log

Ví dụ tạo report ngắn gồm tổng số lỗi và top endpoint trả 5xx:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env bash
set -euo pipefail

ACCESS_LOG="${1:-./access.log}"
REPORT_FILE="${2:-./error-report.txt}"

if [[ ! -r "${ACCESS_LOG}" ]]; then
  echo "ERROR: Cannot read access log: ${ACCESS_LOG}"
  exit 1
fi

{
  echo "Error report generated at $(date -Is)"
  echo
  echo "Total 5xx responses:"
  awk '$9 ~ /^5[0-9][0-9]$/ { total++ } END { print total + 0 }' "${ACCESS_LOG}"
  echo
  echo "Top 5 endpoints with 5xx:"
  awk '$9 ~ /^5[0-9][0-9]$/ { count[$7]++ } END { for (path in count) print count[path], path }' "${ACCESS_LOG}" \
    | sort -nr \
    | head -n 5
} > "${REPORT_FILE}"

echo "Wrote ${REPORT_FILE}"

Đây là dạng script rất hữu ích để chạy từ cron hoặc CI job sau test load, miễn là bạn hiểu rõ format log đầu vào.


Sai sót thường gặp

  • Dùng regex khi chỉ cần literal: Nếu pattern có ký tự đặc biệt như [, ., *, cân nhắc grep -F.
  • Quên grep exit code: Không có match là exit code 1, không nhất thiết là lỗi nghiệp vụ.
  • Hardcode field log mà không kiểm tra format: $9 là status code với format phổ biến, nhưng không phải mọi log đều như vậy.
  • Dùng awk -F, cho CSV phức tạp: CSV có quote/comma bên trong field cần parser đúng nghĩa.
  • Chạy sed -i ngay trên file quan trọng: Test output trước, dùng backup suffix hoặc copy file trước khi sửa.
  • Không quote biến trong script: Khi truyền file path vào grep, awk, sed, luôn quote "${FILE}".

Ghi chú triển khai

  • Khi áp dụng vào dự án của bạn, chọn công cụ theo mục tiêu:
    • Tìm/lọc dòng → grep.
    • Trích cột, tính tổng, đếm nhóm → awk.
    • Thay thế text theo dòng/pattern → sed.
  • Best practices:
    • Dùng grep -n khi debug để biết line number.
    • Dùng grep -C khi cần context quanh lỗi.
    • Dùng awk với BEGIN/END để tạo report có header/footer.
    • Dùng delimiter khác trong sed khi xử lý URL/path, ví dụ s|old|new|g.
    • Với chỉnh sửa config, backup trước và verify sau bằng grep hoặc test config của service.
  • Troubleshooting:
    • Pipeline dừng vì grep không match? → Xử lý exit code bằng if grep ... hoặc || true khi hợp lý.
    • awk in sai cột? → In thử awk '{ print NR, NF, $0 }' để kiểm tra field.
    • sed không thay gì? → Kiểm tra pattern có anchor đúng không, delimiter có cần escape không.

🎯 Lời kết

grep, awksed là bộ ba nền tảng để biến Bash thành công cụ xử lý log/config cực nhanh. grep giúp tìm đúng dòng, awk giúp phân tích theo cột và tổng hợp số liệu, còn sed giúp chỉnh sửa text có kiểm soát. Khi kết hợp với kỹ năng xử lý file ở bài trước, bạn đã có thể tạo nhiều script DevOps nhỏ nhưng rất hữu ích.

Ở bài tiếp theo, chúng ta sẽ đi vào function trong Bash: tách logic thành hàm, truyền tham số, dùng local, xử lý return code và xây dựng thư viện logging nhỏ để tái sử dụng. 🚀


Tài liệu tham khảo

  • GNU Grep Manual — Tài liệu chính thức về grep, regex, -E, -F, -n, -A, -B, -C.
  • GNU Awk Manual — Tài liệu chính thức về field, NR, NF, BEGIN, END và awk program.
  • GNU Sed Manual — Tài liệu chính thức về s/regexp/replacement/flags, address và -i.
  • GNU Coreutils Manual — sort — Tham khảo thêm cho các pipeline thống kê với sort.