bash 文件描述符是如何工作的?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7082001/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 20:54:07  来源:igfitidea点击:

How do file descriptors work?

bashshellstdoutstdinfile-descriptor

提问by Trcx

Can someone tell me why this does not work? I'm playing around with file descriptors, but feel a little lost.

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

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

The first three lines run fine, but the last two error out. Why?

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

回答by dogbane

File descriptors 0, 1 and 2 are for stdin, stdout and stderr respectively.

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

File descriptors 3, 4, .. 9 are for additional files. In order to use them, you need to open them first. For example:

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

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

For more information take a look at Advanced Bash-Scripting Guide: Chapter 20. I/O Redirection.

有关更多信息,请查看高级 Bash 脚本指南:第 20 章 I/O 重定向

回答by rsp

It's an old question but one thing needs clarification.

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

While the answers by Carl Norum and dogbane are correct, the assumption is to change your script to make it work.

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

What I'd like to point out is that you don't need to change the script:

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

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

It works if you invoke it differently:

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

./fdtest 3>&1 4>&1

which means to redirect file descriptors 3 and 4 to 1 (which is standard output).

这意味着将文件描述符 3 和 4 重定向到 1(这是标准输出)。

The point is that the script is perfectly finein wanting to write to descriptors other than just 1 and 2 (stdout and stderr) if those descriptors are provided by the parent process.

关键是,如果这些描述符是由父进程提供的那么脚本非常适合写入除 1 和 2(stdout 和 stderr)以外的描述符

Your example is actually quite interesting because this script can write to 4 different files:

您的示例实际上非常有趣,因为此脚本可以写入 4 个不同的文件:

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

Now you have the output in 4 separate files:

现在您在 4 个单独的文件中有输出:

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

What is more interestingabout it is that your program doesn't have to have write permissions for those files, because it doesn't actually open them.

什么是更有趣的关于它是你的程序不必有这些文件的写权限,因为它实际上并没有打开。

For example, when I run sudo -sto change user to root, create a directory as root, and try to run the following command as my regular user (rsp in my case) like this:

例如,当我运行sudo -s将用户更改为 root 时,以 root 身份创建一个目录,并尝试以我的普通用户(在我的情况下为 rsp)运行以下命令,如下所示:

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

I get an error:

我收到一个错误:

bash: file1.txt: Permission denied

Butif I do the redirection outside of su:

但是,如果我在 之外进行重定向su

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

(note the difference in single quotes) it worksand I get:

(注意单引号的区别)它有效,我得到:

# 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

which are 4 files owned by root in a directory owned by root - even though the script didn't have permissionsto create those files.

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

Another example would be using chroot jail or a container and run a program inside where it wouldn't have access to those files even if it was run as root and still redirect those descriptors externally where you need, without actually giving access to the entire file system or anything else to this script.

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

The point is that you have discovered a very interesting and useful mechanism. You don't have to open all the files inside of your script as was suggested in other answers. Sometimes it is useful to redirect them during the script invocation.

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

To sum it up, this:

总结一下,这个:

echo "This"

is actually equivalent to:

实际上相当于:

echo "This" >&1

and running the program as:

并将程序运行为:

./program >file.txt

is the same as:

是相同的:

./program 1>file.txt

The number 1 is just a default number and it is stdout.

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

But even this program:

但即使是这个程序:

#!/bin/bash
echo "This"

can produce a "Bad descriptor" error. How? When run as:

可能会产生“错误描述符”错误。如何?当运行为:

./fdtest2 >&-

The output will be:

输出将是:

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

Adding >&-(which is the same as 1>&-) means closing the standard output. Adding 2>&-would mean closing the stderr.

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

You can even do a more complicated thing. Your original script:

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

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

when run with just:

当运行时:

./fdtest

prints:

印刷:

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

But you can make descriptors 3 and 4 work, but number 1 fail by running:

但是您可以使描述符 3 和 4 工作,但通过运行第 1 个描述符失败:

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

It outputs:

它输出:

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

If you want descriptors both 1 and 2 fail, run it like this:

如果您希望描述符 1 和 2 都失败,请像这样运行它:

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

You get:

你得到:

a
test.

Why? Didn't anything fail? It didbut with no stderr (file descriptor number 2) you didn't see the error messages!

为什么?没有失败吗?它确实存在,但没有 stderr(文件描述符编号 2),您没有看到错误消息!

I think it's very useful to experiment this way to get a feeling of how the descriptors and their redirection work.

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

Your script is a very interesting example indeed - and I argue that it is not broken at all, you were just using it wrong!:)

你的脚本确实是一个非常有趣的例子——我认为它根本没有坏,你只是用错了!:)

回答by Carl Norum

It's failing because those file descriptors don't point to anything! The normal default file descriptors are the standard input 0, the standard output 1, and the standard error stream 2. Since your script isn't opening any other files, there are no other valid file descriptors. You can open a file in bash using exec. Here's a modification of your example:

它失败了,因为这些文件描述符没有指向任何东西!正常的默认文件描述符是标准输入0、标准输出1和标准错误流2。由于您的脚本没有打开任何其他文件,因此没有其他有效的文件描述符。您可以使用 bash 在 bash 中打开文件exec。这是对您的示例的修改:

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

And now we'll run it:

现在我们将运行它:

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

As you can see, the extra output was sent to the requested files.

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

回答by AGipson

To add on to the answer from rspand respond the question in the commentsof that answer from @MattClimbs.

要在添加到从RSP回答回应的评论的问题,从这个问题的答案的@MattClimbs

You can test if the file descriptor is open or not by attempting to redirect to it early and if it fails, open the desired numbered file descriptor to something like /dev/null. I do this regularly within scripts and leverage the additional file descriptors to pass back additional details or responses beyond return #.

您可以通过尝试尽早重定向到文件描述符来测试文件描述符是否打开,如果失败,则将所需编号的文件描述符打开为类似/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

The stderr is redirected to /dev/nullto discard the possible bash: #: Bad file descriptorresponse and the ||is used to process the following command exec #>/dev/nullwhen the previous one exits with a non zero status. In the event that the file descriptor is already opened, the two tests would return a zero status and the exec ...command would not be executed.

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

Calling the script without any redirections yields:

在没有任何重定向的情况下调用脚本会产生:

# ./script.sh
This
is

In this case, the redirections for aand testare shipped off to /dev/null

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

Calling the script with a redirection defined yields:

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

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

The first redirection 3>temp.txtoverwrites the file temp.txtwhile 4>>temp.txtappends to the file.

第一次重定向会在追加到3>temp.txt文件temp.txt时覆盖4>>temp.txt文件。

In the end, you can define default files to redirect to within the script if you want something other than /dev/nullor you can change the execution method of the script and redirect those extra file descriptors anywhere you want.

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