文件描述符是如何工作的



谁能告诉我为什么这不起作用?我正在摆弄文件描述符,但感觉有点失落。

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

前三行运行正常,但最后两行出错。为什么?

文件描述符0、1和2分别用于stdin、stdout和stderr。

文件描述符3,4,…9为附加文件。为了使用它们,你需要先打开它们。例如:

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

有关更多信息,请参阅高级bash脚本指南:第20章。I/O重定向。

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

虽然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

是根目录下的4个根所有的文件- ,即使脚本没有权限创建这些文件

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

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

总的来说就是:

echo "This"

实际上相当于:

echo "This" >&1

和运行程序:

./program >file.txt

等于:

./program 1>file.txt

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

但是即使是这个程序:

#!/bin/bash
echo "This"

会产生"Bad descriptor"错误。如何?

./fdtest2 >&-

输出将是:

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

添加>&-(与1>&-相同)意味着关闭标准输出。添加2>&-将意味着关闭标准错误。

你甚至可以做一个更复杂的事情。您的原始脚本:

#!/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、标准输出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.
$

可以看到,额外的输出被发送到请求的文件。

添加rsp的答案,并在@ mattclimb的答案的评论中回答问题。

您可以通过尝试早期重定向到文件描述符来测试文件描述符是否打开,如果失败,将所需的编号文件描述符打开到/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以外的内容,您可以在脚本中定义要重定向到的默认文件,或者您可以更改脚本的执行方法并将这些额外的文件描述符重定向到您想要的任何地方。

相关内容

  • 没有找到相关文章

最新更新