文件描述符如何工作?

Trc*_*rcx 67 bash shell stdin stdout file-descriptor

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

#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4
Run Code Online (Sandbox Code Playgroud)

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

dog*_*ane 98

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

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

exec 3<> /tmp/foo  #open fd 3.
echo "test" >&3
exec 3>&- #close fd 3.
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请参阅Advanced Bash-Scripting Guide:第20章I/O重定向.

  • 这就是我要找的!所以我需要用exec命令指定一个文件作为临时存储位置,然后在完成后关闭它们?抱歉,我对 exec 命令有点模糊,我不经常使用它。 (2认同)
  • 您不必指定文件.你也可以将fd 3指向同一个文件,fd 1指向`exec 3>&1`导致`echo hi>&3`将"hi"打印到stdout. (2认同)

rsp*_*rsp 59

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

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

我想指出的是你不需要改变脚本:

#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4
Run Code Online (Sandbox Code Playgroud)

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

./fdtest 3>&1 4>&1
Run Code Online (Sandbox Code Playgroud)

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

关键是,如果父进程提供了那些描述符,那么脚本完全可以写入除1和2(stdout和stderr)之外的描述符.

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

./fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt
Run Code Online (Sandbox Code Playgroud)

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

$ for f in file*; do echo $f:; cat $f; done
file1.txt:
This
file2.txt:
is
file3.txt:
a
file4.txt:
test.
Run Code Online (Sandbox Code Playgroud)

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

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

# su rsp -c '../fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt'
Run Code Online (Sandbox Code Playgroud)

我收到一个错误:

bash: file1.txt: Permission denied
Run Code Online (Sandbox Code Playgroud)

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

# su rsp -c '../fdtest' >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt
Run Code Online (Sandbox Code Playgroud)

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

# 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
Run Code Online (Sandbox Code Playgroud)

这是root拥有的4个归档文件所在的目录 - 即使该脚本没有创建这些文件的权限.

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

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

总结一下,这个:

echo "This"
Run Code Online (Sandbox Code Playgroud)

实际上相当于:

echo "This" >&1
Run Code Online (Sandbox Code Playgroud)

并运行该程序:

./program >file.txt
Run Code Online (Sandbox Code Playgroud)

是相同的:

./program 1>file.txt
Run Code Online (Sandbox Code Playgroud)

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

但即便是这个程序:

#!/bin/bash
echo "This"
Run Code Online (Sandbox Code Playgroud)

可以产生"错误的描述符"错误.怎么样?运行时:

./fdtest2 >&-
Run Code Online (Sandbox Code Playgroud)

输出将是:

./fdtest2: line 2: echo: write error: Bad file descriptor
Run Code Online (Sandbox Code Playgroud)

添加>&-(与之相同1>&-)意味着关闭标准输出.添加2>&-意味着关闭stderr.

你甚至可以做一件更复杂的事情.你的原始剧本:

#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4
Run Code Online (Sandbox Code Playgroud)

当只运行:

./fdtest
Run Code Online (Sandbox Code Playgroud)

打印:

This
is
./fdtest: line 4: 3: Bad file descriptor
./fdtest: line 5: 4: Bad file descriptor
Run Code Online (Sandbox Code Playgroud)

但是你可以使描述符3和4工作,但是1号运行失败:

./fdtest 3>&1 4>&1 1>&-
Run Code Online (Sandbox Code Playgroud)

它输出:

./fdtest: line 2: echo: write error: Bad file descriptor
is
a
test.
Run Code Online (Sandbox Code Playgroud)

如果你想要描述符1和2都失败,运行如下:

./fdtest 3>&1 4>&1 1>&- 2>&-
Run Code Online (Sandbox Code Playgroud)

你得到:

a
test.
Run Code Online (Sandbox Code Playgroud)

为什么?什么都没有失败?它确实没有stderr(文件描述符编号2)你没有看到错误消息!

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

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


Car*_*rum 18

它失败了,因为那些文件描述符并没有指向任何东西!普通的默认文件描述符是标准输入0,标准输出1和标准错误流2.由于您的脚本没有打开任何其他文件,因此没有其他有效的文件描述符.您可以使用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
Run Code Online (Sandbox Code Playgroud)

现在我们将运行它:

$ ls
script
$ ./script 
This
is
$ ls
out1    out2    script
$ cat out*
a
test.
$
Run Code Online (Sandbox Code Playgroud)

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


小智 5

添加rsp 的答案并在@MattClimbs的答案的评论中回答问题

您可以通过尝试提前重定向到文件描述符来测试文件描述符是否打开,如果失败,则将所需的编号文件描述符打开为类似/dev/null. 我定期在脚本中执行此操作,并利用附加文件描述符来传回return #.

脚本文件

#!/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
Run Code Online (Sandbox Code Playgroud)

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

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

# ./script.sh
This
is
Run Code Online (Sandbox Code Playgroud)

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

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

# ./script.sh 3>temp.txt 4>>temp.txt
This
is
# cat temp.txt
a
test.
Run Code Online (Sandbox Code Playgroud)

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

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