stdin
, stdout
và stderr
là các luồng được đính kèm với các mô tả tệp 0, 1 và 2 tương ứng của một quy trình.
Theo dấu nhắc của trình bao tương tác trong trình giả lập thiết bị đầu cuối hoặc thiết bị đầu cuối, tất cả 3 mô tả tệp đó sẽ tham chiếu đến cùng một mô tả tệp mở có được bằng cách mở tệp thiết bị đầu cuối hoặc giả thiết bị đầu cuối (giống như /dev/pts/0
) trong read + write chế độ.
Nếu từ lớp vỏ tương tác đó, bạn bắt đầu tập lệnh của mình mà không sử dụng bất kỳ chuyển hướng nào, tập lệnh của bạn sẽ kế thừa các mô tả tệp đó.
Trên Linux, /dev/stdin
, /dev/stdout
, /dev/stderr
là các liên kết tượng trưng cho /proc/self/fd/0
, /proc/self/fd/1
, /proc/self/fd/2
tương ứng, tự liên kết tượng trưng đặc biệt đến tập tin thực tế đó là mở cửa vào những file descriptor.
Chúng không phải là stdin, stdout, stderr, chúng là các tệp đặc biệt xác định các tệp stdin, stdout, stderr đi tới (lưu ý rằng nó khác với các hệ thống khác ngoài Linux có các tệp đặc biệt đó).
đọc một cái gì đó từ stdin có nghĩa là đọc từ mô tả tệp 0 (sẽ chỉ ra một nơi nào đó trong tệp được tham chiếu bởi /dev/stdin
).
Nhưng trong $(</dev/stdin)
, shell không đọc từ stdin, nó mở một bộ mô tả tệp mới để đọc trên cùng một tệp với tệp mở trên stdin (vì vậy đọc từ đầu tệp chứ không phải nơi stdin hiện đang trỏ đến).
Ngoại trừ trong trường hợp đặc biệt của các thiết bị đầu cuối mở ở chế độ đọc + ghi, thiết bị xuất chuẩn và thiết bị xuất chuẩn thường không mở để đọc. Chúng có nghĩa là các luồng mà bạn viết . Vì vậy, đọc từ mô tả tập tin 1 thường sẽ không hoạt động. Trên Linux, mở /dev/stdout
hoặc /dev/stderr
để đọc (như trong $(</dev/stdout)
) sẽ hoạt động và sẽ cho phép bạn đọc từ tệp mà thiết bị xuất chuẩn đi tới (và nếu thiết bị xuất chuẩn là một ống, nó sẽ đọc từ đầu kia của ống và nếu đó là ổ cắm , nó sẽ thất bại khi bạn không thể mở một ổ cắm).
Trong trường hợp tập lệnh của chúng tôi chạy mà không chuyển hướng theo dấu nhắc của trình bao tương tác trong thiết bị đầu cuối, tất cả / dev / stdin, / dev / stdout và / dev / stderr sẽ là tập tin thiết bị đầu cuối / dev / pts / x.
Đọc từ các tệp đặc biệt đó trả về những gì được gửi bởi thiết bị đầu cuối (những gì bạn gõ trên bàn phím). Viết cho họ sẽ gửi văn bản đến thiết bị đầu cuối (để hiển thị).
echo $(</dev/stdin)
echo $(</dev/stderr)
sẽ giống nhau. Để mở rộng $(</dev/stdin)
, shell sẽ mở / dev / pts / 0 và đọc những gì bạn gõ cho đến khi bạn nhấn ^D
vào một dòng trống. Sau đó, họ sẽ vượt qua bản mở rộng (những gì bạn đã nhập tước dòng mới và theo phân tách + global) echo
sau đó sẽ xuất nó trên thiết bị xuất chuẩn (để hiển thị).
Tuy nhiên trong:
echo $(</dev/stdout)
trong bash
( và bash
duy nhất ), điều quan trọng là phải nhận ra rằng bên trong $(...)
, thiết bị xuất chuẩn đã được chuyển hướng. Bây giờ nó là một đường ống. Trong trường hợp bash
, một quá trình shell con đang đọc nội dung của tệp (ở đây /dev/stdout
) và ghi nó vào ống, trong khi cha mẹ đọc từ đầu kia để tạo ra sự mở rộng.
Trong trường hợp này khi quá trình bash con đó mở ra /dev/stdout
, nó thực sự đang mở đầu đọc của ống. Sẽ không có gì đến từ đó, đó là một tình huống bế tắc.
Nếu bạn muốn đọc từ tệp được chỉ ra bởi thiết bị xuất chuẩn, bạn sẽ làm việc với nó:
{ echo content of file on stdout: "$(</dev/fd/3)"; } 3<&1
Điều đó sẽ nhân đôi fd 1 lên fd 3, vì vậy / dev / fd / 3 sẽ trỏ đến cùng một tệp là / dev / stdout.
Với một kịch bản như:
#! /bin/bash -
printf 'content of file on stdin: %s\n' "$(</dev/stdin)"
{ printf 'content of file on stdout: %s\n' "$(</dev/fd/3)"; } 3<&1
printf 'content of file on stderr: %s\n' "$(</dev/stderr)"
Khi chạy như:
echo bar > err
echo foo | myscript > out 2>> err
Bạn sẽ thấy out
sau đó:
content of file on stdin: foo
content of file on stdout: content of file on stdin: foo
content of file on stderr: bar
Nếu như trái ngược với đọc từ /dev/stdin
, /dev/stdout
, /dev/stderr
, bạn muốn đọc từ stdin, stdout và stderr (mà sẽ làm cho thậm chí còn ít ý nghĩa), bạn sẽ làm:
#! /bin/sh -
printf 'what I read from stdin: %s\n' "$(cat)"
{ printf 'what I read from stdout: %s\n' "$(cat <&3)"; } 3<&1
printf 'what I read from stderr: %s\n' "$(cat <&2)"
Nếu bạn bắt đầu tập lệnh thứ hai đó một lần nữa như:
echo bar > err
echo foo | myscript > out 2>> err
Bạn sẽ thấy trong out
:
what I read from stdin: foo
what I read from stdout:
what I read from stderr:
và trong err
:
bar
cat: -: Bad file descriptor
cat: -: Bad file descriptor
Đối với thiết bị xuất chuẩn và thiết bị xuất chuẩn, cat
thất bại vì các bộ mô tả tệp chỉ mở để viết , không đọc, việc mở rộng $(cat <&3)
và $(cat <&2)
trống.
Nếu bạn gọi nó là:
echo out > out
echo err > err
echo foo | myscript 1<> out 2<> err
(nơi <>
mở ở chế độ đọc + ghi mà không cắt ngắn), bạn sẽ thấy trong out
:
what I read from stdin: foo
what I read from stdout:
what I read from stderr: err
và trong err
:
err
Bạn sẽ nhận thấy rằng không có gì được đọc từ thiết bị xuất chuẩn, bởi vì trước đó printf
đã bị ghi đè nội dung của out
với what I read from stdin: foo\n
và rời khỏi vị trí stdout trong tập tin đó ngay sau. Nếu bạn đã mồi out
với một số văn bản lớn hơn, như:
echo 'This is longer than "what I read from stdin": foo' > out
Sau đó, bạn sẽ nhận được vào out
:
what I read from stdin: foo
read from stdin": foo
what I read from stdout: read from stdin": foo
what I read from stderr: err
Xem làm thế nào $(cat <&3)
đã đọc những gì còn lại sau lần đầu tiên printf
và làm như vậy cũng di chuyển vị trí xuất sắc qua nó để các printf
đầu ra tiếp theo những gì được đọc sau đó.
echo x
không giống nhưecho x > /dev/stdout
khi thiết bị xuất chuẩn không đi đến đường ống hoặc một số thiết bị ký tự như thiết bị tty. Chẳng hạn, nếu thiết bị xuất chuẩn đi đến một tệp thông thườngecho x > /dev/stdout
sẽ cắt bớt tệp và thay thế nội dung của nó bằngx\n
thay vì ghix\n
ở vị trí xuất chuẩn hiện tại.