sed (stream editor) là công cụ biến đổi văn bản theo dòng mạnh nhất trong bộ Unix cơ bản. Hầu hết lập trình viên biết đúng một câu lệnh: sed 's/cũ/mới/'. Nhưng sed làm được nhiều hơn thế rất nhiều — chọn dòng theo điều kiện, xoá, chèn, dùng nhóm bắt để sắp xếp lại dữ liệu. Bài này (phần 3 loạt Unix) chạy sed thật trên một file cấu hình mẫu để mở khoá những tính năng đó, và cảnh báo một cái bẫy khiến vô số script "chạy trên máy tôi" nhưng hỏng trên máy khác: sed -i.

Lệnh cốt lõi: s/cũ/mới/cờ

Cấu trúc quen thuộc s/mẫu/thay/cờ. Điều nhiều người không để ý: mặc định nó chỉ thay lần đầu tiên trên mỗi dòng. Các cờ đổi hành vi:

sed "s/port/cong/"      # chỉ lần ĐẦU mỗi dòng
sed "s/port/cong/g"     # g = mọi lần trên dòng
sed "s/aaa/X/2"         # chỉ lần thứ 2
sed "s/hello/hi/gI"     # I = không phân biệt hoa/thường (g = toàn bộ)

Một mẹo quan trọng: dấu phân cách đổi được. Nếu mẫu chứa dấu / (như đường dẫn), viết s#/var/log#/tmp# thay vì phải thoát từng dấu \/. sed nhận ký tự ngay sau s làm dấu phân cách.

Ảnh chụp đoạn mã nền tối minh hoạ sed stream editor biến đổi văn bản theo dòng, lệnh cốt lõi s cũ mới cờ sed s port cong thay lần đầu mỗi dòng sed s port cong g g mọi lần trên dòng sed s aaa X 2 chỉ lần thứ 2 sed s hello hi gI I không phân biệt hoa thường dấu phân cách đổi được s a b hữu ích khi mẫu có dấu gạch chéo, địa chỉ chọn dòng nào để tác động sed debug s true false chỉ dòng khớp debug sed mũ thăng d xoá dòng bắt đầu bằng thăng sed 2 3 p -n chỉ in dòng 2 đến 3 sed 3d xoá dòng 3, nhóm bắt 1 2 và sửa file tại chỗ -i sed -E s nhóm chữ bằng nhóm 2 mũi tên 1 đảo key value bẫy -i GNU Linux sed -i không hậu tố BSD macOS sed -i rỗng bắt buộc viết sed -i chấm bak chạy được cả hai tạo bản sao bak

Hình 1: Lệnh s/// với cờ g/N/I; địa chỉ chọn dòng (/mẫu/, số dòng, khoảng); nhóm bắt \1 \2; và bẫy sed -i khác nhau giữa GNU (Linux) và BSD (macOS).

Địa chỉ: chỉ tác động lên dòng bạn chọn

Đây là sức mạnh bị bỏ quên nhất của sed. Trước mỗi lệnh, bạn đặt một địa chỉ giới hạn dòng nào bị tác động: một số dòng, một /mẫu/, hay một khoảng. Chạy thật trên file cấu hình:

Ảnh chụp bảng kết quả chạy thật sed GNU trên Linux output thật, một cờ g N gI aaa bbb aaa bbb aaa qua s aaa X g ra X bbb X bbb X aaa bbb aaa bbb aaa qua s aaa X 2 ra aaa bbb X bbb aaa Hello HELLO hello qua s hello hi gI ra hi hi hi, hai địa chỉ debug chỉ đổi dòng khớp port 8080 port 9090 debug false chỉ dòng debug bị đổi port giữ nguyên, ba mũ thăng d xoá dòng comment host localhost port 8080 port 9090 debug true name coffee code dòng ghi chu đã biến mất, bốn nhóm bắt 1 2 và in chọn lọc -i host localhost qua s nhóm bằng nhóm 2 mũi tên 1 ra localhost mũi tên host sed -n 2 3p ra port 8080 port 9090 sed thay grep sed -i s debug true debug false file ra file có debug false

Hình 2: Chạy thật — cờ g/2/gI cho kết quả khác nhau; /debug/ s/true/false/ chỉ đổi dòng debug (port giữ nguyên); /^#/d xoá dòng comment; nhóm bắt đảo host=localhost → localhost <- host; -n "2,3p" in chọn lọc; sed -i sửa file tại chỗ.

  • /debug/ s/true/false/: chỉ dòng chứa debug bị đổi true→false; các dòng port giữ nguyên. Không có địa chỉ thì sed áp lệnh lên mọi dòng — địa chỉ cho phép nhắm chính xác.
  • /^#/d: lệnh d (delete) kết hợp địa chỉ /^#/ xoá mọi dòng bắt đầu bằng # — cách gọn nhất để lọc comment khỏi file cấu hình.
  • -n "2,3p": -n tắt in mặc định, p in dòng được chọn; kết hợp 2,3 (khoảng dòng) biến sed thành công cụ trích dòng như grep/head.

Nhóm bắt: sắp xếp lại dữ liệu

Với -E (ERE, như bài grep), bạn dùng nhóm bắt (...) và tham chiếu \1 \2 trong phần thay để sắp xếp lại:

echo "host=localhost" | sed -E "s/([a-z]+)=(.+)/\2 <- \1/"
# -> localhost <- host

([a-z]+) bắt key, (.+) bắt value, rồi ghép lại theo thứ tự đảo. Đây là cách sed làm được những biến đổi cấu trúc, không chỉ thay chuỗi đơn giản — ví dụ đổi định dạng ngày 2026-09-22 → 22/09/2026 bằng ba nhóm bắt.

Cái bẫy sed -i: Linux khác macOS

sed -i sửa file tại chỗ (in-place) thay vì in ra màn hình — cực tiện, nhưng đây là nguồn của một lỗi kinh điển khi script chạy trên nhiều máy:

  • GNU sed (Linux): sed -i "s/a/b/" file — -i không cần đối số.
  • BSD sed (macOS): sed -i "s/a/b/" file báo lỗi — BSD đòi -i phải có đối số hậu tố sao lưu, đúng là sed -i "" "s/a/b/" file (chuỗi rỗng nghĩa là không sao lưu).

Script viết trên Linux dùng sed -i "..." sẽ hỏng trên macOS với thông báo khó hiểu, và ngược lại. Cách viết chạy được cả hai: sed -i.bak "s/a/b/" file — luôn tạo một bản sao .bak (cả GNU lẫn BSD đều hiểu -i.bak). Nếu không muốn giữ .bak, xoá nó sau, hoặc tách nhánh theo hệ điều hành.

Đánh đổi cần cân nhắc

sed -i không có "undo" — luôn thử trước khi sửa tại chỗ. Chạy lệnh không -i trước để xem kết quả in ra màn hình, kiểm đúng rồi mới thêm -i. Một mẫu regex sai với -i có thể phá hỏng file cấu hình sản xuất trong tích tắc. Thói quen tốt: sed -i.bak để luôn có bản lùi.

sed xử lý theo dòng — không hợp cho dữ liệu nhiều dòng hay có cấu trúc. Sửa JSON, XML, YAML bằng sed là cái bẫy: một trường có thể trải nhiều dòng, thứ tự thay đổi, và regex không hiểu cấu trúc lồng nhau. Dùng công cụ hiểu định dạng (jq cho JSON, yq cho YAML) thay vì sed cho dữ liệu có cấu trúc.

Khi logic phức tạp, awk hoặc script thật rõ hơn. sed với nhiều lệnh nối bằng ;, nhãn, và vòng lặp trở nên khó đọc rất nhanh. Nếu bạn thấy mình viết sed dài hơn hai ba lệnh, awk (bài sau) hoặc một script Python/Perl thường dễ bảo trì hơn — sự cô đọng của sed là ưu điểm cho việc nhỏ, nhược điểm cho việc lớn.

Ba ý mang về

  1. sed mạnh hơn nhiều so với s/a/b/: đo thật cờ g (mọi lần), N (lần thứ N), I (bỏ hoa/thường); địa chỉ /mẫu/ và khoảng dòng để nhắm chính xác dòng cần tác động; d xoá, -n ...p in chọn lọc như grep.
  2. Nhóm bắt \1 \2 để sắp xếp lại dữ liệu: đo thật đảo host=localhost → localhost <- host bằng -E "s/([a-z]+)=(.+)/\2 <- \1/" — sed làm được biến đổi cấu trúc, không chỉ thay chuỗi.
  3. Bẫy sed -i Linux vs macOS: GNU nhận -i không đối số, BSD đòi -i "" — viết sed -i.bak "..." để chạy được cả hai; và luôn thử không -i trước vì sửa tại chỗ không undo được.

Nguồn

Phần sau ta sang công cụ xử lý dữ liệu theo cột mạnh nhất dòng lệnh: awk — vì sao nó vượt xa sed cho dữ liệu bảng, cách dùng biến, điều kiện, và tính tổng/nhóm ngay trên luồng.