Dòng lệnh Unix mạnh nhờ triết lý "công cụ nhỏ ghép lại qua pipe". Nhưng để ghép đúng, phải hiểu một thứ nền tảng mà nhiều người dùng mỗi ngày mà chưa nắm rõ: ba luồng chuẩn (stdin, stdout, stderr) và cách chuyển hướng chúng. Không hiểu nó là dính đủ loại bẫy — điển hình là cmd 2>&1 > file mà lỗi vẫn ra màn hình chứ không vào file. Bài mở đầu sê-ri Unix mổ xẻ và đo thật bằng bash trong container.
Ba luồng chuẩn
Mỗi tiến trình Unix mở sẵn ba "file descriptor" (fd):
- fd 0 = stdin: đầu vào.
- fd 1 = stdout: đầu ra bình thường (kết quả).
- fd 2 = stderr: đầu ra lỗi — tách riêng khỏi stdout có chủ đích, để thông báo lỗi không lẫn vào dữ liệu bạn đang xử lý.
Chính vì stdout và stderr là hai luồng khác nhau, bạn có thể xử lý chúng riêng: đẩy kết quả vào file mà vẫn thấy lỗi trên màn hình, hay ngược lại.
a | b # stdout của a -> stdin của b (pipe)
> file # ghi stdout ra file (đè); >> nối thêm
2> file # ghi stderr ra file
2>/dev/null # vứt lỗi đi
2>&1 # gộp stderr VÀO chỗ stdout đang trỏ tới

Hình 1: Ba luồng stdin(0)/stdout(1)/stderr(2). Pipe nối stdout→stdin; các toán tử chuyển hướng; và 2>&1 nghĩa "stderr trỏ tới nơi stdout đang trỏ" — nên vị trí của nó quyết định kết quả.
Đo thật: tách luồng và bẫy 2>&1
Chạy ls với một file có (cofile.txt) và một file không có (khongco.txt) — ls in tên file có ra stdout và thông báo lỗi ra stderr:

Hình 2: seq 1 5 | wc -l = 5 (pipe). 2>/dev/null giữ lại stdout (cofile.txt), 1>/dev/null giữ lại stderr (lỗi). > out_dung.txt 2>&1 cho file 2 dòng (cả hai); 2>&1 > out_sai.txt cho file 1 dòng (lỗi ra màn hình).
Kết quả làm rõ mọi thứ:
- Pipe:
seq 1 5 | wc -lcho5— stdout củaseq(5 dòng) trở thành stdin củawc. - Tách luồng:
ls ... 2>/dev/nullvứt stderr, chỉ còn stdout (cofile.txt).ls ... 1>/dev/nullvứt stdout, chỉ còn stderr (ls: cannot access 'khongco.txt'). Hai luồng độc lập hoàn toàn. - Bẫy thứ tự
2>&1— điểm quan trọng nhất:ls ... > out_dung.txt 2>&1cho file 2 dòng (cả kết quả lẫn lỗi); nhưngls ... 2>&1 > out_sai.txtcho file chỉ 1 dòng (kết quả), còn lỗi ra màn hình. Vì sao?2>&1nghĩa là "cho stderr trỏ tới nơi stdout đang trỏ ngay lúc đó". Ở lệnh đúng,> out_dung.txtđổi stdout sang file trước, rồi2>&1khiến stderr cũng vào file. Ở lệnh sai,2>&1chạy trước (stdout còn đang là màn hình → stderr trỏ về màn hình), rồi mới> out_sai.txtđổi stdout sang file — stderr đã "chốt" ở màn hình. Thứ tự đọc từ trái sang phải, và vị trí quyết định.
Đánh đổi và lưu ý
&>file là lối tắt gộp cả hai (bash). Muốn cả stdout lẫn stderr vào file, bash có cmd &>file (tương đương > file 2>&1) — gọn và tránh bẫy thứ tự. Nhưng đây là cú pháp bash, không phải POSIX chuẩn; script cần chạy trên sh thuần thì dùng > file 2>&1.
Pipe chỉ nối stdout, không nối stderr. a | b chỉ đưa stdout của a sang b; stderr của a vẫn ra màn hình. Muốn pipe cả stderr: a 2>&1 | b (gộp stderr vào stdout trước khi pipe). Đây là lý do đôi khi lỗi "lọt" qua | grep — vì nó ở stderr.
Mỗi lệnh trong pipe là một tiến trình riêng. a | b | c chạy song song, dữ liệu chảy qua buffer. Hệ quả: biến gán trong lệnh cuối của pipe (trong subshell) không thấy được ở shell cha (echo x | read v; echo $v ra rỗng) — một bẫy hay gặp, sẽ gặp lại ở bài quoting/biến.
Ba ý mang về
- Unix có ba luồng tách biệt: stdin(0), stdout(1), stderr(2) — stderr tách riêng để lỗi không lẫn dữ liệu; đo thật,
2>/dev/nullgiữ stdout,1>/dev/nullgiữ stderr. - Thứ tự
2>&1quyết định kết quả:> file 2>&1gộp cả hai vào file (2 dòng), nhưng2>&1 > filechỉ đưa stdout vào file (1 dòng), lỗi ra màn hình — vì2>&1trỏ stderr tới nơi stdout đang trỏ lúc đó. - Pipe chỉ nối stdout:
a | bkhông đưa stderr củaasangb(dùnga 2>&1 | b); dùng&>file(bash) để gộp gọn, và nhớ mỗi lệnh trong pipe là tiến trình riêng.
Nguồn
- GNU Bash Manual — Redirections: https://www.gnu.org/software/bash/manual/html_node/Redirections.html
- Wikipedia — Standard streams (stdin/stdout/stderr): https://en.wikipedia.org/wiki/Standard_streams
Phần sau ta vào công cụ lọc văn bản dùng nhiều nhất: grep và biểu thức chính quy — cơ bản vs mở rộng -E, các cờ -o -c -v, và cách đọc regex tham lam, đo trên file thật.