2011-08-16 275 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