CHỈNH SỬA: Tôi thấy tôi đã bị trật bánh và cuối cùng đã trả lời một câu hỏi khác với câu hỏi được hỏi. Câu trả lời cho câu hỏi thực sự nằm ở cuối câu trả lời của Paul Tomblin. (Nếu bạn muốn nâng cao giải pháp đó để chuyển hướng stdout và stderr riêng biệt vì một lý do nào đó, bạn có thể sử dụng kỹ thuật tôi mô tả ở đây.)
Tôi đang muốn một câu trả lời giúp duy trì sự khác biệt giữa stdout và stderr. Thật không may, tất cả các câu trả lời được đưa ra cho đến nay mà bảo tồn sự khác biệt đó là dễ bị chủng tộc: chúng có nguy cơ các chương trình nhìn thấy đầu vào không đầy đủ, như tôi đã chỉ ra trong các bình luận.
Tôi nghĩ rằng cuối cùng tôi đã tìm ra một câu trả lời giúp duy trì sự khác biệt, không thiên về chủng tộc, và cũng không quá khó hiểu.
Khối xây dựng đầu tiên: để hoán đổi stdout và stderr:
my_command 3>&1 1>&2 2>&3-
Khối xây dựng thứ hai: nếu chúng ta chỉ muốn lọc (ví dụ: tee) chỉ stderr, chúng ta có thể thực hiện điều đó bằng cách hoán đổi stdout & stderr, lọc và sau đó hoán đổi lại:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
Bây giờ, phần còn lại thật dễ dàng: chúng ta có thể thêm một bộ lọc stdout, ngay từ đầu:
{ { my_command | stdout_filter;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
hoặc ở cuối:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter
Để thuyết phục bản thân rằng cả hai lệnh trên đều hoạt động, tôi đã sử dụng như sau:
alias my_command='{ echo "to stdout"; echo "to stderr" >&2;}'
alias stdout_filter='{ sleep 1; sed -u "s/^/teed stdout: /" | tee stdout.txt;}'
alias stderr_filter='{ sleep 2; sed -u "s/^/teed stderr: /" | tee stderr.txt;}'
Đầu ra là:
...(1 second pause)...
teed stdout: to stdout
...(another 1 second pause)...
teed stderr: to stderr
và lời nhắc của tôi quay lại ngay sau dấu " teed stderr: to stderr
", như mong đợi.
Chú thích cuối trang về zsh :
Giải pháp trên hoạt động trong bash (và có thể một số shell khác, tôi không chắc chắn), nhưng nó không hoạt động trong zsh. Có hai lý do khiến nó không thành công trong zsh:
- cú pháp
2>&3-
không được hiểu bởi zsh; cái đó phải được viết lại thành2>&3 3>&-
- trong zsh (không giống như các shell khác), nếu bạn chuyển hướng một trình mô tả tệp đã được mở, trong một số trường hợp (tôi không hoàn toàn hiểu cách nó quyết định) thay vào đó, nó thực hiện một hành vi giống như tee tích hợp. Để tránh điều này, bạn phải đóng từng fd trước khi chuyển hướng nó.
Vì vậy, ví dụ, giải pháp thứ hai của tôi phải được viết lại cho zsh as {my_command 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stderr_filter;} 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stdout_filter
(cũng hoạt động trong bash, nhưng rất dài dòng).
Mặt khác, bạn có thể tận dụng khả năng phát bóng ngầm được tích hợp sẵn bí ẩn của zsh để có được một giải pháp ngắn hơn nhiều cho zsh, vốn hoàn toàn không chạy phát bóng:
my_command >&1 >stdout.txt 2>&2 2>stderr.txt
(Tôi sẽ không thể đoán được từ các tài liệu mà tôi tìm thấy rằng >&1
và 2>&2
là thứ kích hoạt phát bóng ngầm của zsh; tôi đã phát hiện ra điều đó bằng cách thử và sai.)