2012-07-19 25 views
10

Khi kết hợp stderr với stdout, tại sao 2>&1 cần phải đến trước | (ống) nhưng sau > myfile (chuyển hướng đến tệp)?Tại sao 2> & 1 cần phải đến trước | (ống) nhưng sau khi một "> myfile" (chuyển hướng đến tập tin)?

Để chuyển hướng stderr để thiết bị xuất chuẩn cho sản lượng file:

echo > myfile 2>&1 

Để chuyển hướng stderr để thiết bị xuất chuẩn cho một ống:

echo 2>&1 | less 



giả định của tôi là tôi chỉ có thể làm:

echo | less 2>&1 

và nó woul d làm việc, nhưng nó không. Tại sao không?

Trả lời

16

Đường ống là | danh sách giới hạn lệnh. Bất kỳ chuyển hướng nào bạn chỉ định áp dụng cho các lệnh cấu thành (đơn giản hoặc hợp chất), nhưng không áp dụng cho toàn bộ đường dẫn. Mỗi chuỗi ống một lệnh của stdout để stdin của tiếp theo bằng cách ngầm áp dụng một chuyển hướng cho mỗi subshell trước khi bất kỳ chuyển hướng nào liên kết với một lệnh được đánh giá.

cmd 2>&1 | less 

Giá trị đầu tiên của phân đoạn đầu tiên được chuyển hướng đến đường ống mà từ đó less đang đọc. Tiếp theo, chuyển hướng 2>&1 được áp dụng cho lệnh đầu tiên. Việc chuyển hướng stderr sang stdout hoạt động vì stdout đã trỏ vào đường ống.

cmd | less 2>&1 

Ở đây, chuyển hướng áp dụng cho less. Stdout và stderr ít hơn cả hai có lẽ bắt đầu ra chỉ vào thiết bị đầu cuối, do đó, 2>&1 trong trường hợp này không có hiệu lực.

Nếu bạn muốn có một chuyển hướng để áp dụng cho toàn bộ đường ống, để nhóm nhiều lệnh như một phần của đường ống, hoặc ống dẫn tổ, sau đó sử dụng một nhóm lệnh (hoặc bất kỳ khác hợp chất lệnh):

{ { cmd1 >&3; cmd2; } 2>&1 | cmd3; } 3>&2 

Có thể là một ví dụ điển hình. Kết quả cuối cùng là: cmd1cmd2 's stderr ->cmd3; cmd2 's stdout ->cmd3; và mã thông báo của cmd1cmd3 và một số thiết bị đầu cuối -> thiết bị đầu cuối.

Nếu bạn sử dụng ống |& đặc trưng của Bash, mọi thứ trở nên lạ lẫm hơn, vì mỗi chuyển hướng stdout của đường ống vẫn xảy ra trước, nhưng chuyển hướng stderr thực sự đến cuối cùng. Ví dụ:

f() { echo out; echo err >&2; }; f >/dev/null |& cat 

Hiện tại, tất cả đầu ra đều bị ẩn. Đường ra đầu tiên của f đi tới đường ống, đường ra tiếp theo của f được chuyển hướng đến /dev/null và cuối cùng, stderr được chuyển hướng tới thiết bị xuất chuẩn (vẫn còn /dev/null).

Tôi khuyên bạn không bao giờ sử dụng |& trong Bash - nó được sử dụng ở đây để trình diễn.

+3

+1 cũng được giải thích – jordanm

+2

+1. Điều duy nhất tôi muốn thêm là một đường ống là một dấu tách lệnh, giống như dấu chấm phẩy. –

+1

+1 Wow, câu trả lời tuyệt vời @ormaaj! Chính xác những gì tôi đang tìm kiếm - cảm ơn bạn! –

6

Để thêm vào câu trả lời của ormaaj:

Lý do bạn cần phải xác định các nhà khai thác chuyển hướng theo thứ tự đúng là họ đang đánh giá từ trái sang phải. Xem xét các danh sách lệnh sau:

# print "hello" on stdout and "world" on stderr 
{ echo hello; echo world >&2; } 

# Redirect stdout to the file "out" 
# Then redirect stderr to the file "err" 
{ echo hello; echo world >&2; } > out 2> err 

# Redirect stdout to the file "out" 
# Then redirect stderr to the (already redirected) stdout 
# Result: all output is stored in "out" 
{ echo hello; echo world >&2; } > out 2>&1 

# Redirect stderr to the current stdout 
# Then redirect stdout to the file "out" 
# Result: "world" is displayed, and "hello" is stored in "out" 
{ echo hello; echo world >&2; } 2>&1 > out 
2

Câu trả lời của tôi là hiểu mô tả tệp. Mỗi quá trình có một loạt các bộ mô tả tập tin: các mục nhập vào các tệp được mở. Theo mặc định, số 0 là cho stdin, số 1 là cho stdout và số 2 là cho stderr.

Trình chuyển hướng i/o> và < theo mặc định kết nối với các trình mô tả tệp hợp lý nhất, stout và stdin. Nếu bạn định tuyến lại tệp stdout thành tệp (như với foo > bar), khi bắt đầu quá trình 'foo', tệp 'bar' được mở để viết và nối vào số mô tả tệp 1. Nếu bạn chỉ muốn stderr trong 'bar', bạn 'd sử dụng foo 2> bar sẽ mở thanh tập tin và móc nó vào đĩa cứng.

Bây giờ, trình chuyển hướng i/o '2> & 1'. Tôi thường đọc đó là 'đặt bộ mô tả tập tin 2 để giống như bộ mô tả tập tin 1. Trong khi đọc dòng lệnh từ trái sang phải, bạn có thể thực hiện tiếp theo: foo 1>bar 2>&1 1>/dev/tty. Với điều này, tập tin mô tả 1 được đặt thành tập tin 'bar', tập tin mô tả 2 được thiết lập để giống như 1 (do đó 'bar') và sau đó, tập tin mô tả 1 được thiết lập để/dev/tty. Runnning foo đang gửi đầu ra của nó đến/dev/tty và nó stderr vào tệp 'bar'.

Bây giờ đường ống dẫn vào: điều này không làm thay đổi các bộ mô tả tệp, tuy nhiên, nó sẽ kết nối chúng giữa các quá trình: stdout của stdin ot quá trình bên trái của bước tiếp theo. Stderr được truyền lại. Do đó, nếu bạn thích đường ống chỉ hoạt động với chỉ số stderr, bạn sử dụng foo 2| bar, kết nối stderr của foo tới stdin của bar. (Tôi không chắc chắn những gì xảy ra với thiết bị xuất chuẩn của foo.)

Với sự ở trên, nếu bạn sử dụng foo 2>&1 | bar, vì stderr của foo được tái định tuyến tới stdout của foo, cả stdout và stderr của foo đến các stdin của bar.