2011-08-16 7 views
49

Một số người có thể cho tôi biết lý do tại sao điều này không hiệu quả? Tôi đang chơi xung quanh với các bộ mô tả tập tin, nhưng cảm thấy một chút bị mất.Trình mô tả tệp hoạt động như thế nào?

#!/bin/bash 
echo "This" 
echo "is" >&2 
echo "a" >&3 
echo "test." >&4 

Ba dòng đầu tiên chạy tốt, nhưng hai lỗi cuối cùng. Tại sao?

Trả lời

63

Trình mô tả tệp 0, 1 và 2 dành cho stdin, stdout và stderr tương ứng.

Trình mô tả tệp 3, 4, .. 9 dành cho các tệp bổ sung. Để sử dụng chúng, bạn cần mở chúng trước. Ví dụ:

exec 3<> /tmp/foo #open fd 3. 
echo "test" >&3 
exec 3>&- #close fd 3. 

Để biết thêm thông tin, hãy xem Advanced Bash-Scripting Guide: Chapter 20. I/O Redirection.

+1

Đó là những gì tôi đang tìm kiếm! Vì vậy, tôi cần phải xác định một tập tin cho nó để sử dụng như một nơi lưu trữ tạm thời với lệnh exec, và sau đó đóng chúng khi tôi đã làm xong? Xin lỗi, tôi hơi mờ với lệnh exec, tôi không sử dụng nó nhiều. – Trcx

+1

có, nhưng không phải tạm thời. Tệp sẽ tồn tại ngay cả sau khi chương trình của bạn hoàn tất. – dogbane

+0

Điều đó có thể hoạt động độc đáo sau đó, tôi đang cố gắng cổng một số tập lệnh tương thích với crontab task scheduler, nhưng tôi gặp sự cố khi cron không cho phép đường ống của stdout trong tập lệnh. – Trcx

16

Điều này không thành công vì các trình mô tả tệp đó không trỏ đến bất kỳ điều gì! Mô tả tệp mặc định bình thường là đầu vào tiêu chuẩn 0, đầu ra tiêu chuẩn 1 và luồng lỗi chuẩn 2. Vì tập lệnh của bạn không mở bất kỳ tệp nào khác, không có bộ mô tả tệp hợp lệ nào khác. Bạn có thể mở tệp bằng bash bằng cách sử dụng exec. Dưới đây là một sửa đổi của các ví dụ của bạn:

#!/bin/bash 
exec 3> out1  # open file 'out1' for writing, assign to fd 3 
exec 4> out2  # open file 'out2' for writing, assign to fd 4 

echo "This"  # output to fd 1 (stdout) 
echo "is" >&2 # output to fd 2 (stderr) 
echo "a" >&3  # output to fd 3 
echo "test." >&4 # output to fd 4 

Và bây giờ chúng tôi sẽ chạy nó:

$ ls 
script 
$ ./script 
This 
is 
$ ls 
out1 out2 script 
$ cat out* 
a 
test. 
$ 

Như bạn thấy, sản lượng thêm đã được gửi đến các tập tin được yêu cầu.

+0

Có cách nào tôi có thể ghi nó ra cho thiết bị đầu cuối không? Tôi muốn có thể nhìn thấy tất cả trong thiết bị đầu cuối, nhưng muốn có thể gửi đầu ra mà tôi muốn. tức là ./script 2> out.2 3> out.3 4> out.4 – Trcx

+0

@Trcx, nếu bạn muốn ghi vào terminal, sử dụng 'stdout' hoặc' stderr'. Tại sao bạn cần hoặc muốn sử dụng các tệp khác cho điều đó? –

+0

Các kịch bản mà tôi đang cố gắng làm cho tương thích với crontab cần phải viết nhiều tệp ra, nhưng crontab không cho phép tệp được ghi từ tập lệnh (vì thiếu hỗ trợ từ stdout) Tuy nhiên tôi có thể sử dụng crontab để ghi đầu ra của các tập lệnh vào một tệp. Tôi đã nghĩ rằng tôi có thể có các kịch bản ghi vào đầu ra khác nhau, và sau đó có crontab tách tất cả mọi thứ vào các tập tin thích hợp. Tôi đã chỉ tìm kiếm một cách cleaver để ghi vào các tập tin bằng cách sử dụng stdout. Nhưng nhờ các bạn, tôi đã phát hiện ra rằng tôi đã đánh giá cao nó. (một lần nữa: P) Cảm ơn sự giúp đỡ! – Trcx

35

Đó là một câu hỏi cũ nhưng một điều cần làm rõ.

Trong khi câu trả lời bằng Carl Norum và dogbane là chính xác, giả định là thay đổi kịch bản của bạn để làm cho nó hoạt.

Những gì tôi muốn chỉ ra là bạn không cần phải thay đổi kịch bản:

#!/bin/bash 
echo "This" 
echo "is" >&2 
echo "a" >&3 
echo "test." >&4 

Nó hoạt động nếu bạn gọi một cách khác:

./fdtest 3>&1 4>&1 

có nghĩa để chuyển hướng các mô tả tập tin 3 và 4 đến 1 (là đầu ra tiêu chuẩn).

Điểm có ích là kịch bản là hoàn toàn tốt đẹp trong muốn viết thư cho mô tả khác hơn là chỉ 1 và 2 (stdout và stderr) nếu những mô tả được cung cấp bởi các quá trình cha mẹ.

dụ của bạn là thực sự khá thú vị bởi vì kịch bản này có thể viết đến 4 file khác nhau:

./fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt 

Bây giờ bạn có đầu ra trong 4 tập tin riêng biệt:

$ for f in file*; do echo $f:; cat $f; done 
file1.txt: 
This 
file2.txt: 
is 
file3.txt: 
a 
file4.txt: 
test. 

thú vị hơn là gì về nó là chương trình của bạn không cần phải có quyền ghi cho các tệp đó, vì nó không thực sự mở chúng.

Ví dụ, khi tôi chạy sudo -s thay đổi người sử dụng gốc, tạo ra một thư mục như là người chủ, và cố gắng chạy lệnh sau khi sử dụng thường xuyên của tôi (RSP trong trường hợp của tôi) như thế này:

# su rsp -c '../fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt' 

tôi nhận được một lỗi:

bash: file1.txt: Permission denied 

Nhưng nếu tôi làm chuyển hướng bên ngoài của su:

# su rsp -c '../fdtest' >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt 

(lưu ý sự khác biệt trong dấu nháy đơn) nó hoạt động và tôi nhận được:

# ls -alp 
total 56 
drwxr-xr-x 2 root root 4096 Jun 23 15:05 ./ 
drwxrwxr-x 3 rsp rsp 4096 Jun 23 15:01 ../ 
-rw-r--r-- 1 root root 5 Jun 23 15:05 file1.txt 
-rw-r--r-- 1 root root 39 Jun 23 15:05 file2.txt 
-rw-r--r-- 1 root root 2 Jun 23 15:05 file3.txt 
-rw-r--r-- 1 root root 6 Jun 23 15:05 file4.txt 

là 4 tác phẩm thuộc sở hữu của người chủ trong một thư mục sở hữu bởi root - mặc dù kịch bản không có quyền để tạo các tệp đó.

Ví dụ khác sẽ sử dụng chroot jail hoặc container và chạy chương trình bên trong nơi không có quyền truy cập vào các tệp đó ngay cả khi nó được chạy dưới dạng gốc và vẫn chuyển hướng các mô tả đó ra bên ngoài. cho toàn bộ hệ thống tệp hoặc bất kỳ thứ gì khác cho tập lệnh này.

Vấn đề là bạn đã khám phá ra một cơ chế rất thú vị và hữu ích. Bạn không phải mở tất cả các tệp bên trong tập lệnh như đã được đề xuất trong các câu trả lời khác. Đôi khi nó rất hữu ích để chuyển hướng chúng trong khi gọi script.

Nói tóm lại, đây:

echo "This" 

thực sự là tương đương với:

echo "This" >&1 

và chạy chương trình như:

./program >file.txt 

là giống như:

./program 1>file.txt 

Số 1 chỉ là số mặc định và nó là giá trị xuất chuẩn.

Nhưng ngay cả chương trình này:

#!/bin/bash 
echo "This" 

có thể sản xuất một lỗi "Bad mô tả". Làm sao? Khi chạy như:

./fdtest2 >&- 

Kết quả sẽ là:

./fdtest2: line 2: echo: write error: Bad file descriptor 

Thêm >&- (mà cũng giống như 1>&-) có nghĩa là kết thúc đầu ra tiêu chuẩn. Thêm 2>&- sẽ có nghĩa là đóng stderr.

Bạn thậm chí có thể làm một điều phức tạp hơn.kịch bản ban đầu của bạn:

#!/bin/bash 
echo "This" 
echo "is" >&2 
echo "a" >&3 
echo "test." >&4 

khi chạy với chỉ:

./fdtest 

in:

This 
is 
./fdtest: line 4: 3: Bad file descriptor 
./fdtest: line 5: 4: Bad file descriptor 

Nhưng bạn có thể làm cho mô tả 3 và 4 làm việc, nhưng số 1 thất bại bằng cách chạy:

./fdtest 3>&1 4>&1 1>&- 

Kết quả đầu ra:

./fdtest: line 2: echo: write error: Bad file descriptor 
is 
a 
test. 

Nếu bạn muốn mô tả cả 1 và 2 thất bại, chạy nó như thế này:

./fdtest 3>&1 4>&1 1>&- 2>&- 

Bạn nhận:

a 
test. 

Tại sao? Không có gì thất bại? Nó đã làm nhưng không có tiêu chuẩn (tập tin mô tả số 2) bạn không thấy thông báo lỗi!

Tôi nghĩ rất hữu ích khi thử nghiệm theo cách này để có được cảm giác về cách các trình mô tả và chuyển hướng của chúng hoạt động.

Kịch bản của bạn là một ví dụ rất thú vị - và tôi cho rằng nó không bị hỏng chút nào, bạn chỉ sử dụng sai! :)

+0

V phản hồi thú vị .. Cảm ơn –

+0

thực sự, tập tin mô tả 1 là stdout; stdin là bộ mô tả tập tin 0. – programmerjake

+0

@programmerjake Rất tiếc. Typo đã được sửa. Cảm ơn đã chỉ ra điều đó. – rsp