文件描述符如何工作?

发布于 2024-11-29 14:46:19 字数 186 浏览 0 评论 0原文

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

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

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

佞臣 2024-12-06 14:46:19

文件描述符 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 重定向< /a>.

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

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

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.

↘紸啶 2024-12-06 14:46:19

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

虽然 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 拥有的目录中有 4 个 root 拥有的文件 - 即使该脚本没有创建这些文件的权限

另一个例子是使用 chroot 监狱或容器并在其中运行一个程序,即使它以 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

But you can make描

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

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

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

a
test.

prints ?难道没有什么失败的事情吗? 确实如此,但没有 stderr(文件描述符编号 2)你没有看到错误消息!

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

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

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.

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).

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

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

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

Now you have the output in 4 separate files:

$ 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 interesting about 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 -s to 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:

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

I get an error:

bash: file1.txt: Permission denied

But if I do the redirection outside of su:

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

(note the difference in single quotes) it works and 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 permissions to create those files.

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.

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.

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.

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:

./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:

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

You get:

a
test.

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

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! :)

深海不蓝 2024-12-06 14:46:19

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

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

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:

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

忆梦 2024-12-06 14:46:19

添加到 来自 rsp 的答案回答来自 @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 detector 响应,并且 || 为用于在前一个命令以非零状态退出时处理以下命令 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。 >temp.txt 方法

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

To add on to the answer from rsp and respond the question in the comments of that answer from @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 #.

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/null to discard the possible bash: #: Bad file descriptor response and the || is used to process the following command exec #>/dev/null when 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.

Calling the script without any redirections yields:

# ./script.sh
This
is

In this case, the redirections for a and test are shipped off to /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.txt overwrites the file temp.txt while 4>>temp.txt appends to the file.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文