- Cú pháp redirect dùng để gộp standard error (stderr) và standard output (stdout) vào cùng một stream
- Các số 1 là stdout, 2 là stderr, còn
& được dùng để biểu thị tham chiếu tới file descriptor
2>&1 có nghĩa là “gửi stderr tới nơi mà stdout hiện đang hướng đến”, và kết quả có thể thay đổi tùy theo thứ tự output
- Ví dụ,
command >file 2>&1 sẽ gửi cả hai stream vào file, còn command 2>&1 >file thì chỉ stderr còn lại trên console
- Đây là cú pháp redirect cốt lõi thường được dùng trong Bash và POSIX shell khi gộp output, lưu log, xử lý pipe
File descriptor và khái niệm cơ bản
- 0, 1, 2 lần lượt tương ứng với stdin, stdout, stderr
- Được định nghĩa trong
/usr/include/unistd.h
#define STDIN_FILENO 0, #define STDOUT_FILENO 1, #define STDERR_FILENO 2
> là redirect output, `` là ghi mới file, >> là thêm vào file
- Ký hiệu
& cho biết đang tham chiếu tới descriptor chứ không phải tên file
- Vì vậy
2>1 sẽ redirect vào file có tên là 1, còn 2>&1 thì sao chép stderr sang stdout
Cách 2>&1 hoạt động
2> có nghĩa là redirect stderr, còn &1 là tham chiếu tới file descriptor của stdout
- Kết quả là stderr sẽ được gửi tới cùng đích với stdout
- Ví dụ:
ls -ld /tmp /tnt >/dev/null 2>&1 → cả hai output đều bị bỏ vào /dev/null
ls -ld /tmp /tnt 2>&1 >/dev/null → chỉ stderr còn lại trên console
- Redirect được xử lý từ trái sang phải, nên nếu thứ tự khác thì kết quả cũng khác
Tầm quan trọng của thứ tự redirect
command >file 2>&1
- Trước tiên gửi stdout vào file, rồi sao chép stderr sang stdout → cả hai stream đều vào file
command 2>&1 >file
- Trước tiên sao chép stderr sang stdout hiện tại (console), rồi chỉ gửi stdout vào file → stderr vẫn tiếp tục được in ra console
- Bash xử lý redirect theo đúng thứ tự, nên khi viết lệnh cần đặc biệt chú ý thứ tự
Nhiều ví dụ redirect khác nhau
echo test >file.txt → redirect stdout vào file
echo test 2>file.txt → redirect stderr vào file
echo test 1>&2 → redirect stdout sang stderr
command &>file hoặc command >&file → redirect cả stdout và stderr vào file (cú pháp rút gọn của Bash)
command 2>&1 | tee -a file.txt → xuất đồng thời cả hai stream ra file và terminal
Cách dùng nâng cao và tính năng từ Bash 4.0 trở đi
- Từ Bash 4.0 có thể tách output bằng process substitution
ls -ld /tmp /tnt 2> >(sed 's/^/E: /') > >(sed 's/^/O: /')
- Chuyển stdout và stderr qua các filter khác nhau
|& là dạng rút gọn của 2>&1 |, dùng để gộp hai stream rồi chuyển qua pipe
- Tùy chọn
set -o noclobber giúp tránh ghi đè file có sẵn, và có thể dùng >| để ngoại lệ
Ví dụ ứng dụng thực tế
g++ main.cpp 2>&1 | head → chỉ xem phần output đầu tiên, bao gồm cả lỗi biên dịch
perl test.pl > debug.log 2>&1 → lưu toàn bộ output và lỗi vào file log
foo 2>&1 | grep ERROR → tìm chuỗi ERROR trong cả stdout lẫn stderr
docker logs container 2>&1 | grep "some log" → chuyển toàn bộ log qua pipe
Tóm tắt cốt lõi
2>&1 là cú pháp chuẩn POSIX để sao chép stderr sang stdout
- Thứ tự redirect quyết định kết quả, nên cần cẩn thận khi viết lệnh
- Trong Bash có thể dùng
&> để xử lý đồng thời hai stream, và cú pháp này là công cụ thiết yếu trong nhiều script tự động hóa cho quản lý log, xử lý pipe, gộp lỗi
1 bình luận
Ý kiến trên Hacker News
Xét từ góc độ syscall API của Unix,
2>&1có ý nghĩa tương đương vớidup2(1, 2)Trong shell Unix cổ điển thì chỉ có vậy, nhưng ở các shell hiện đại còn có thêm bookkeeping nội bộ để theo dõi trạng thái
redirection được thực thi tuần tự từ trái sang phải, còn toán tử pipe hoạt động bằng tổ hợp fork và dup
Tuy vậy, nếu hiểu
dup2(2, 1)như2<1thì tuy có vẻ trực quan nhưng lại là cách diễn giải sai về mặt ngữ nghĩa I/ONó nằm giữa tài liệu dup2 của man7 và tài liệu dup2 của Arch Linux
Thật ngạc nhiên khi bot đang đọc thứ này
Quá nhiều cú pháp “đường” che mất cơ chế bên trong
Không giống các ngôn ngữ như Lisp, nơi cấu trúc đơn giản được mở rộng bằng macro, shell có quy tắc cú pháp phức tạp và kém trực quan
Cuối cùng có vẻ như chính xung đột tự ái giữa lập trình viên và quản trị hệ thống đã sinh ra những lời phàn nàn này
Tuy nhiên nếu không mở trước thì sẽ báo lỗi “Bad file descriptor”
redirection dùng dup trước exec, còn pipe dùng hai lần fork và syscall
pipeSổ tay BASH rất tốt, nên tham khảo tài liệu chính thức
Nhưng trong các ngôn ngữ hiện đại hoặc các ngôn ngữ ngoài hệ sinh thái Unix thì cảm giác đó đã biến mất
Cuối cùng thì cách chắc chắn nhất vẫn là tự đọc tài liệu chính thức (RTFM)
Sổ tay Bash Redirections
Phần lớn mọi người tìm câu trả lời bằng Google, và chỉ khi các câu hỏi như vậy tích lũy đủ nhiều thì kết quả tìm kiếm mới xuất hiện
Nhiều góc nhìn khác nhau trên Stack Overflow lại hữu ích hơn cho người mới bắt đầu
Người dùng thông thường rất khó tìm được thông tin mình cần
Một câu trả lời trên Stack Overflow diễn đạt đúng suy nghĩ của tôi nên tôi trích nguyên văn
Lý do là
2>&1chứ không phải&2>&1, vì&chỉ mang nghĩa file descriptor trong ngữ cảnh redirectionThật thú vị khi PowerShell cũng giữ nguyên cú pháp này
Liên kết tài liệu chính thức
Thứ tự của
2>&1 > filengược với Unix nên không cho ra kết quả như mong muốnTrước phiên bản 7.4 còn có cả vấn đề hỏng byte stream
Tài liệu liên quan
>chỉ định file descriptor nào sẽ được redirect>foocũng giống như1>fooNếu viết kiểu
2>>&1thì nó sẽ tạo ra tên file1, nên không có ý nghĩa>là stdout,2>là stderr, còn&1là stdoutfile1>file2cũng không đối xứng/dev/stderr>/dev/stdoutlà cách tương ứng trực tiếp hơnCách giải thích của Claude là dễ hiểu nhất
2>&1có nghĩa là “gửi output lỗi tới cùng nơi với output thông thường”2là output lỗi,>là “gửi đi”, và&1là “nơi stdout hiện đang trỏ tới”2là file descriptor 2,>là phép gán, còn&1là file descriptor 1Bấm trực tiếp vào liên kết còn hiệu quả hơn là đi lấy câu trả lời từ LLM
Tôi thấy nhớ thời kỳ Stack Overflow khi còn có thể hỏi con người
Nhưng giờ thì khó mà quay lại thời đó được nữa
Nhưng ngay cả thời đó cũng đầy gatekeeping và bầu không khí mỉa mai
Sự cộng tác lấy con người làm trung tâm không phải lúc nào cũng lãng mạn
Không có phần dẫn nhập thừa thãi, đi thẳng vào trọng tâm
Khi hỏi con người sẽ có gánh nặng xã hội như phải nhìn sắc mặt, bị đánh giá, cạnh tranh
Còn LLM thì đưa ra phản hồi trung lập và lịch sự mà không tạo ra áp lực đó
Cách shell hoạt động phụ thuộc vào ngữ cảnh, nên ý nghĩa của
&thay đổi tùy vị tríNó có thể được áp dụng cục bộ chỉ trong một dòng như
IFS=\| read A B C <<< "first|second|third"&ở cuối dòng là chạy nền, còn&ở giữa lại mang nghĩa redirectionNhững mẫu như vậy khó học, nhưng rốt cuộc vẫn là phần phải học
Nó khiến tôi lại cảm nhận rõ hệ thống chúng ta dùng cổ xưa đến mức nào
Việc xử lý file descriptor bằng số chẳng khác nào đưa thẳng con trỏ cho người dùng
Sẽ tốt hơn nếu có thể truy cập dựa trên tên
&đóng vai trò báo rằng đó không phải file mà là descriptor<đã được dùng cho input redirection nên không thể thay thế2>/dev/stdoutthì gần giống2>&1, nhưng không hoàn toàn giống nhau/dev/stdoutlà một kiểu truy cập dựa trên tên quen thuộc hơnScript viết từ 15 năm trước đến giờ vẫn chạy nguyên vẹn
redirection thực sự là một tính năng rất thú vị
Ví dụ tôi hay dùng thay thế tiến trình như
diff <(seq 1 20) <(seq 1 10)Nếu có thể truyền trực tiếp file, stream, socket vào tiến trình thì sẽ mạnh hơn nhiều
Nếu Bash có thể mở socket trực tiếp rồi chuyển cho chương trình khác thì sandboxing cũng sẽ dễ hơn
[^1]: Có
/dev/tcp, nhưng chức năng bị giới hạnTrên thực tế nó được triển khai bằng named pipe, nên không thể seek
Vì vậy Zsh mới thêm cú pháp
=(command)dùng file tạmTôi đã ghi nhớ
2>&1như kiểu “2 đi vào địa chỉ của 1”, nên hiểu được nóVề ‘2>&1’ và redirection, có các bài viết đi sâu như sau
Understanding Linux's File Descriptors: A Deep Dive Into '2>&1' and Redirection
Liên kết thảo luận liên quan
Liên kết sách