Đây có phải là một lỗi đánh máy trong phần chuyển hướng của Bash không?


13
Note that the order of redirections is significant.  For example, the command

          ls > dirlist 2>&1

   directs both standard output and standard error to the file dirlist, 
   while the command

          ls 2>&1 > dirlist

   directs  only  the  standard  output  to  file  dirlist,  because the 
   standard error was duplicated from the standard output before the standard
   output was redirected to dirlist.

Bây giờ, phần cuối cùng đó là khó hiểu với tôi. Trong trường hợp đó, mọi lỗi tiêu chuẩn sẽ được in ra thiết bị đầu cuối và mọi STDOUT sẽ chuyển đến tệp dirlist. Đó là những gì sẽ xảy ra, nhưng đó không phải là cách tôi hiểu hướng dẫn.

Có vẻ như nó phải nói "vì lỗi tiêu chuẩn được sao chép từ đầu ra tiêu chuẩn SAU, đầu ra tiêu chuẩn được chuyển hướng đến dirlist". Nếu STDERR được gửi đến STDOUT trước khi STDOUT được chuyển hướng đến một tệp, thì tệp đó có chứa STDOUT VÀ STDERR không?

Ai đó có thể xin vui lòng làm rõ điều này cho tôi? Có phải chỉ là đọc hiểu kém về phía tôi? Việc sử dụng từ trùng lặp có vẻ hơi lạ đối với tôi trong bối cảnh này. Có lẽ đó là ném tôi.



1
Một trường hợp cổ điển về việc trộn lẫn các hoạt động "theo giá trị" so với "theo tham chiếu". Khi bạn sao chép một mô tả tập tin, đó là một hoạt động theo giá trị . Trong lập trình, sau khi a = 1; b = a; a = 2bạn mong đợi a == 2 && b == 1là đúng. Chuyển hướng 2>&1tương tự như b = achuyển nhượng - đó là theo giá trị, không phải bằng tham chiếu. 2>&1không kết hợp mô tả tệp 2 với mô tả tệp 1 vĩnh viễn - chúng vẫn là 2 mô tả tệp riêng biệt, những người tình cờ trỏ đến cùng một tệp.
jw013

Câu trả lời:


23

Sao chép thực sự là phần quan trọng ở đây.

Chúng ta hãy xem nơi mô tả tập tin sẽ đi đến trước khi chuyển hướng. Đây thường là thiết bị đầu cuối hiện tại, ví dụ:

STDOUT ---> /dev/pts/1
STDERR ---> /dev/pts/1

Bây giờ, nếu chúng ta gọi ls -lmà không chuyển hướng, đầu ra và thông báo lỗi đi đến thiết bị đầu cuối của tôi dưới /dev/pts/1.

Nếu trước tiên chúng ta chuyển hướng STDOUTtệp đến một tệp ( ls -l > dirlist), nó sẽ trông như thế này:

STDOUT ---> /home/bon/dirlist
STDERR ---> /dev/pts/1

Khi chúng tôi sau đó chuyển hướng STDERRđến một bản sao của STDOUT's mô tả tập tin ( ls -l > dirlist 2>&1), STDERRđi đến một bản sao của /home/bon/dirlist:

STDOUT ---> /home/bon/dirlist
STDERR ---> /home/bon/dirlist

Nếu trước tiên chúng tôi sẽ chuyển hướng STDERRđến một bản sao STDOUTmô tả tệp ( ls -l 2>&1):

STDOUT ---> /dev/pts/1
STDERR ---> /dev/pts/1

sau đó STDOUT đến một tệp ( ls -l 2>&1 > dirlist), chúng ta sẽ nhận được điều này:

STDOUT ---> /home/bon/dirlist
STDERR ---> /dev/pts/1

Đây, STDERR vẫn đang đi đến nhà ga.

Bạn thấy đấy, thứ tự trong trang man là chính xác.


Kiểm tra chuyển hướng

Bây giờ, bạn có thể tự kiểm tra điều đó. Sử dụng ls -l /proc/$$/fd/, bạn thấy nơi STDOUT(với fd 1) và STDERR(với fd 2), sẽ diễn ra cho quy trình hiện tại:

$ ls -l /proc/$$/fd/
total 0
lrwx------ 1 bon bon 64 Jul 24 18:19 0 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 18:19 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 07:41 2 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 18:19 255 -> /dev/pts/1

Hãy tạo một tập lệnh shell nhỏ cho biết nơi mô tả tệp của bạn được trỏ. Bằng cách này, chúng ta luôn có được trạng thái khi gọi ls, bao gồm mọi chuyển hướng từ vỏ gọi.

$ cat > lookfd.sh
#!/bin/sh
ls -l /proc/$$/fd/
^D
$ chmod +x lookfd.sh

(Với CtrlD, bạn gửi một tập tin cuối và vì vậy hãy dừngcat lệnh đọc từ đó STDIN.)

Bây giờ, gọi tập lệnh này với các kết hợp chuyển hướng khác nhau:

$ ./lookfd.sh 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:08 0 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:08 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:08 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:08 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh > foo.out
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:10 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:10 1 -> /home/bon/foo.out
lrwx------ 1 bon bon 64 Jul 24 19:10 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:10 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh 2>&1 > foo.out
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:10 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:10 1 -> /home/bon/foo.out
lrwx------ 1 bon bon 64 Jul 24 19:10 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:10 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh > foo.out 2>&1
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:11 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:11 1 -> /home/bon/foo.out
l-wx------ 1 bon bon 64 Jul 24 19:11 2 -> /home/bon/foo.out
lr-x------ 1 bon bon 64 Jul 24 19:11 255 -> /home/bon/lookfd.sh

Bạn có thể thấy rằng các mô tả tập tin 1 (cho STDOUT) và 2 (cho STDERR) khác nhau. Để giải trí, bạn cũng có thể chuyển hướng STDINvà xem kết quả:

$ ./lookfd.sh < /dev/zero
total 0
lr-x------ 1 bon bon 64 Jul 24 19:18 0 -> /dev/zero
lrwx------ 1 bon bon 64 Jul 24 19:18 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:18 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:18 255 -> /home/bon/lookfd.sh

(Câu hỏi còn lại cho người đọc: Mô tả tệp 255 điểm ở đâu? ;-))


+1 - câu trả lời xuất sắc. Ví dụ rất tốt bằng văn bản và tuyệt vời. Cảm ơn bạn!!!
slm

Tôi hiểu, tôi nghĩ rằng sự hiểu lầm của tôi là việc chuyển hướng sẽ tồn tại dai dẳng cho tất cả các lệnh sau, do đó, bất kỳ STDERR nào cho phần còn lại của dòng sẽ chuyển sang STDOUT.
Gregg Leventhal

2

Không, hướng dẫn là đúng.

Nếu lúc đầu 1 điểm đến thiết bị đầu cuối và 2 cũng đến thiết bị đầu cuối, thì:

command  2>&1   1>somewhere

đánh giá chuyển hướng sẽ xảy ra từ trái sang phải.

Vì vậy, nó sẽ đánh giá FIRST 2>&1, và do đó FIRST sao chép những gì fd đã 1sử dụng để trỏ đến (tức là bộ mô tả tệp the terminal, thường là / dev / tty) vào fd 2.

Vì vậy, tại thời điểm đó fd 2bây giờ chỉ đến nơi fd 1được sử dụng để trỏ đến ( the terminal)

Và THEN nó đánh giá 1>somewherephần đó, và do đó sẽ sao chép mô tả tệp của somewherefd 1(vì vậy tại thời điểm đó, fd 1bây giờ trỏ đến somewherevà fd2 vẫn trỏ đến the terminal)

Vì vậy, nó thực sự in 1 thành "ở đâu đó" và 2 vào thiết bị đầu cuối, vì 2 đã được sao chép từ 1 TRƯỚC 1 đã được thay đổi.

Thứ tự khác:

command  1>somewhere 2>&1

đầu tiên sẽ chuyển hướng fd 1đến somewhere, sau đó sao chép cùng tham chiếu đó vào fd 2, vì vậy ở cuối 2 cũng trỏ đếnsomewhere . Nhưng họ không "liên kết" từ bây giờ. Mỗi vẫn có thể được chuyển hướng riêng.

Ví dụ:

command  1>somewhere 2>&1
exec 2>/dev/null

Vào cuối của một, fd 1trỏ đến somewherevà fd 2được hướng đến/dev/null

Tên thông thường cho fd 1là STDOUT (đầu ra tiêu chuẩn) và tên thông thường cho fd 2là STDERR (lỗi tiêu chuẩn, vì nó thường được sử dụng để hiển thị lỗi mà không can thiệp vào STDOUT)


@ Michael-mrozek: cảm ơn vì đã chỉnh sửa, nhưng tôi khăng khăng nói "sao chép" thay vì "trùng lặp" là "trùng lặp" có thể khiến người ta tin rằng từ bây giờ cả hai đều là "cùng một thứ", điều đó không đúng. ex :: cmd 1>somewhere 2>&1 ; exec 2>/dev/nullsau khi thực thi, chỉ có 2 được chuyển hướng đến / dev / null (1 vẫn sẽ chuyển đến "một nơi nào đó"). Tôi cần giúp đỡ để đưa ra cách nói "những gì 1 điểm" thay vì "fd 1", tuy nhiên ... vì điều đó cũng khó hiểu ...
Olivier Dulac

1
Tôi không chắc ý của bạn là gì; bạn là người đã thay đổi nó từ "bản sao" thành "bản sao". Tất cả những gì tôi đã làm là viết hoa và định dạng mọi thứ, tôi đã không thay đổi một từ
Michael Mrozek

đừng ... ^^ xin lỗi. Và tôi đã chỉnh sửa một lần nữa để điều chỉnh lại để làm cho chính xác hơn những gì được sao chép vào những gì ^^
Olivier Dulac

1

Tôi nghĩ phần khó hiểu ở đây là sự hiểu sai khi chuyển hướng stderr sang stdout thực sự kết nối hai luồng.

Một ý tưởng hoàn toàn hợp lý nhưng những gì xảy ra khi bạn viết 2>&1là stderr sẽ xem nhanh những gì thiết bị xuất chuẩn được viết và viết vào cùng một vị trí. Do đó, nếu sau đó bạn bảo stdout đi viết ở một nơi khác thì nó không ảnh hưởng đến đích của stderr đã được di chuyển.

Tôi nghĩ rằng đó là một chút trái ngược với bản thân mình nhưng đó là cách nó hoạt động. Thiết lập nơi bạn muốn viết lên đầu tiên sau đó nói với mọi người "sao chép tôi". Hy vọng rằng làm rõ ...


0

KHAI THÁC ...

là quan trọng, nhưng đúng hơn theo nghĩa nó là nguồn gốc của nhiều sự nhầm lẫn . Nó thực sự khá đơn giản. Câu trả lời này chỉ là một minh họa "triệt để".

Câu trả lời được chấp nhận là tốt, nhưng quá dài và nó nhấn mạnh "sự trùng lặp".

Q kết thúc một cách khôn ngoan với:

Việc sử dụng từ trùng lặp có vẻ hơi lạ đối với tôi trong bối cảnh này. Có lẽ đó là ném tôi.

Tôi sử dụng ký hiệu bash và định nghĩa các biến "một" và "hai" dưới dạng tệp "1" và "2". Toán tử chuyển hướng (đầu ra) >là một phép gán =. &$ có nghĩa là "giá trị" của.

Các ví dụ bash man (đã thêm "1" mặc định)

ls 1>dirlist 2>&1      # both to dirlist
ls 2>&1 1>dirlist      # 1 to dirlist, 2 stays on tty/screen 

trở nên:

one=dirlist  two=$one

two=$one   one=dirlist

Và thậm chí điều này là không tự động đối với tôi, và một số người khác tôi đoán. Dòng đầu tiên để lại cho bạn $one$two cả hai đều chứa "dirlist". Tất nhiên.

Dòng thứ hai bắt đầu với một nhiệm vụ vô ích. Cả hai đều bắt đầu bằng định nghĩa với "TTY" (một chút tượng trưng) là hướng của chúng ; không có giá trị nào được thay đổi bởi phép gán này và với các biến như với filehandles, không có gì được liên kết một cách kỳ diệu. Biến twokhông bị ảnh hưởng bởi những điều sau đây one=dirlist. Dĩ nhiên là không.

Sombody ở đây (6 năm trước) đã đề xuất "trỏ đến" thay vì "sao chép" hoặc "sao chép", và sau đó nhận ra: điều đó cũng sẽ gây nhầm lẫn.

Sự trùng lặp hoặc ngữ nghĩa con trỏ này thậm chí không cần thiết. Có lẽ đó là dấu và cần được chú ý nhiều hơn. "Giá trị của" toán tử / mã thông báo / bất cứ điều gì.

Nếu - và chỉ khi - bạn đang tìm cách để có được số công việc đáng ngạc nhiên trên bảng điều khiển của mình , thì thông báo "đã hoàn thành" cộng với phần thưởng là một tệp có tên "2", sau đó bạn đi:

ls 1>2& 2>/dev/null

Nó đọc tự nhiên là " sao chép" / "sao chép" 1 đến 2, và sau đó cả hai cùng nhau thành null . Nhưng ý tưởng là sai, và cũng là cú pháp. (nhưng không có lỗi cú pháp, nó hợp lệ)

Cách đúng đắn để lập kế hoạch là chuyển hướng bất kỳ một trong hai thành null và sau đó chuyển hướng KHÁC sang vị trí CÙNG:

ls 1>/dev/null 2>&1
# or 
ls 2>/dev/null 1>&2

("1" hàng đầu có thể bị bỏ lại)

(OK acc. A không quá dài, nhưng quá nhiều danh sách - hoặc: trực quan rất tốt, giải thích không tốt lắm)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.