2011-08-16 274 views
49

有人能告訴我爲什麼這不起作用嗎?我正在玩文件描述符,但感覺有點失落。文件描述符如何工作?

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

前三行運行正常,但最後兩個出錯。爲什麼?

回答

63

文件描述符0,1和2分別用於stdin,stdout和stderr。

文件描述符3,4,... 9用於附加文件。爲了使用它們,你需要先打開它們。例如:

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

欲瞭解更多信息,請看看Advanced Bash-Scripting Guide: Chapter 20. I/O Redirection

+1

這就是我要找的!所以我需要指定一個文件作爲exec命令的臨時存儲位置,然後在完成後關閉它們?對不起,我對exec命令有些模糊,我沒有太多用處。 – Trcx

+1

是的,但這不是暫時的。該文件即使在程序完成後也會存在。 – dogbane

+0

這可以很好地工作,然後,我試圖移植一些腳本來與crontab任務調度程序兼容,但我遇到了麻煩,因爲cron不允許在腳本中輸入stdout。 – Trcx

16

這是失敗的,因爲這些文件描述符不指向任何東西!正常的默認文件描述符是標準輸入0,標準輸出1和標準錯誤流2。由於您的腳本沒有打開任何其他文件,因此沒有其他有效的文件描述符。您可以使用exec以bash打開文件。這是你的示例的變化:

#!/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 

而現在,我們將運行:

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

正如你所看到的,額外的輸出被送到請求的文件。

+0

有沒有辦法讓我把它寫出來給終端?我希望能夠在終端中看到它,但希望能夠將輸出發送到我想要的地方。即./script 2> out.2 3> out.3 4> out.4 – Trcx

+0

@Trcx,如果你想寫入終端,使用'stdout'或'stderr'。爲什麼你需要或想要使用其他文件? –

+0

我試圖與crontab兼容的腳本需要寫出多個文件,但是crontab不允許從腳本寫入文件(因爲它缺少stdout的支持)但是我可以使用crontab編寫輸出的腳本文件。我在想,我可以將腳本寫入各種輸出,然後讓crontab將所有內容分隔到相應的文件中。我只是在尋找一種切肉刀的方式來使用標準輸出寫入文件。但是,多虧了你們,我發現我正在反思它。 (再次:P)感謝您的幫助! – Trcx

35

這是一個老問題,但有一件事需要澄清

雖然Carl Norum和dogbane的答案是正確的,但假設是更改腳本以使其工作

我想什麼指出的是,你不需要改劇本

./fdtest 3>&1 4>&1 

這意味着:

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

,如果你調用不同它的工作原理將文件描述符3和4重定向到1(這是標準輸出)。

的要點是,腳本在想如果由父進程提供的那些描述符寫入不僅僅是1和2(stdout和stderr)其它描述符完全正常

你舉的例子其實是很有趣的,因爲該腳本可以寫入4個不同的文件:

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

現在你有輸出4個獨立的文件:

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

什麼是更有趣關於這是你的程序不必具有這些文件的寫入權限,因爲它實際上並沒有打開它們。

例如,當我運行sudo -s改變用戶根目錄,創建一個目錄作爲根,並嘗試爲我的普通用戶(在我的情況RSP)這樣運行下面的命令:

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

我得到一個錯誤:

bash: file1.txt: Permission denied 

,如果我做的su外重定向:

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

(注意單引號的區別)它的工作原理,我得到:

# 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 

這4個文件由root擁有的目錄屬於root - 即使腳本沒有權限來創建這些文件。

另一個例子是使用chroot jail或者一個容器,然後在內部運行一個程序,即使它以root身份運行,仍然無法訪問這些文件,並且仍然在需要時從外部重定向這些描述符,而無需實際訪問整個文件系統或這個腳本的其他內容。

問題是你已經發現了一個非常有趣和有用的機制。您不必像其他答案中所建議的那樣打開腳本內的所有文件。有時在腳本調用過程中重定向它們很有用。

總而言之,這一點:

echo "This" 

實際上相當於:

echo "This" >&1 

和運行程序爲:

./program >file.txt 

是一樣的:

./program 1>file.txt 

數字1只是一個默認數字,它是stdout。

但即使是這樣的程序:

#!/bin/bash 
echo "This" 

可以產生 「壞描述符」 的錯誤。怎麼樣?當作爲運行:

./fdtest2 >&- 

的輸出將是:

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

添加>&-(這是一樣的1>&-)指關閉的標準輸出。添加2>&-意味着關閉stderr。你可以做更復雜的東西。您的原始腳本:

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

時只需運行:

./fdtest 

打印:

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

但是你可以描述符3和4的工作,但1號運行失敗:

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

它輸出:

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

如果你想描述或1和2失敗,運行像這樣:

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

你得到:

a 
test. 

爲什麼?沒有任何失敗? 它沒有但沒有stderr(文件描述符編號2)您沒有看到錯誤消息!

我認爲通過試驗這種方式來了解描述符及其重定向如何工作是非常有用的。

你的腳本確實是一個非常有趣的例子 - 我認爲它沒有被打破,你只是使用它錯了! :)

+0

V有趣的迴應..謝謝 –

+0

其實,文件描述符1是stdout; stdin是文件描述符0. – programmerjake

+0

@programmerjake Oops。錯字固定。感謝您指出。 – rsp