文件描述符如何工作?

时间:2011-08-16 17:01:34

标签: bash shell stdout stdin file-descriptor

有人可以告诉我为什么这不起作用?我正在玩文件描述符,但感觉有点迷失。

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

前三行运行良好,但最后两行错误。为什么呢?

4 个答案:

答案 0 :(得分:96)

文件描述符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 :(得分:56)

这是一个老问题,但有一件事需要澄清

虽然Carl Norum和dogbane的答案是正确的,但我们的假设是更改您的脚本以使其正常工作

我想指出的是您不需要更改脚本

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

如果您以不同方式调用它,它会起作用:

./fdtest 3>&1 4>&1

表示将文件描述符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将用户更改为root时,以root身份创建一个目录,并尝试以我的常规用户(在我的情况下为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

这是root拥有的目录中root的4个文件 - ,即使脚本没有权限来创建这些文件。

另一个例子是使用chroot jail或容器并在其内部运行程序,即使它以root身份运行也无法访问这些文件,并且仍然将这些描述符重定向到您需要的外部,而无需实际访问整个文件系统或此脚本的任何其他内容。

关键是你发现了一个非常有趣和有用的机制。您不必像其他答案中所建议的那样打开脚本内的所有文件。有时在脚本调用期间重定向它们很有用。

总结一下,这个:

echo "This"

实际上相当于:

echo "This" >&1

并将程序运行为:

./program >file.txt

与:

相同
./program 1>file.txt

数字1只是一个默认数字,它是标准输出。

但即使是这个程序:

#!/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)您没有看到错误消息!

我认为以这种方式进行实验以了解描述符及其重定向的工作方式非常有用。

你的剧本确实是一个非常有趣的例子 - 我认为它根本没有被破坏,你只是错误地使用它!:)

答案 2 :(得分:18)

它失败了,因为那些文件描述符没有指向任何东西!普通的默认文件描述符是标准输入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.
$

如您所见,额外输出已发送到请求的文件。

答案 3 :(得分:0)

要添加到answer from rsp中的答案的respond the question in the comments@MattClimbs中。

您可以通过尝试尽早重定向到文件描述符来测试文件描述符是否打开,如果失败,则将所需编号的文件描述符打开为/dev/null之类的东西。我会定期在脚本中执行此操作,并利用其他文件描述符将其他详细信息或响应传回return #之外。

script.sh

#!/bin/bash
2>/dev/null >&3 || exec 3>/dev/null
2>/dev/null >&4 || exec 4>/dev/null

echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4

将stderr重定向到/dev/null,以丢弃可能的bash: #: Bad file descriptor响应,并且当前一个命令退出时,||用于处理以下命令exec #>/dev/null零状态。如果文件描述符已经打开,则这两个测试将返回零状态,并且将不执行exec ...命令。

在不进行任何重定向的情况下调用脚本会产生:

# ./script.sh
This
is

在这种情况下,atest的重定向被发送到/dev/null

使用已定义的重定向调用脚本会产生:

# ./script.sh 3>temp.txt 4>>temp.txt
This
is
# cat temp.txt
a
test.

第一个重定向3>temp.txt覆盖文件temp.txt,而4>>temp.txt附加到文件。

最后,如果需要除/dev/null以外的其他内容,则可以定义默认文件以重定向到脚本内,或者可以更改脚本的执行方法,并在所需的任何位置重定向这些额外的文件描述符。 / p>