文件描述符如何工作?


71

有人可以告诉我为什么这行不通吗?我在玩文件描述符,但是有点迷路。

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

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

Answers:


109

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


1
那就是我要的!因此,我需要通过exec命令指定一个文件用作临时存储位置,然后在完成后关闭它们?抱歉,我对exec命令有点模糊,我用的不多。
2011年

1
是的,但这不是暂时的。即使您的程序完成,该文件也将存在。
dogbane

那可以很好地解决问题,我试图移植一些脚本以与crontab任务计划程序兼容,但是由于cron不允许在脚本中使用stdout进行管理,因此遇到了麻烦。
Trcx

2
您不必指定文件。您也可以让FD 3点到的fd 1点到同一个文件exec 3>&1的原因echo hi >&3打印“嗨”到标准输出。
菲利普·阿尔瓦雷斯

为什么3<>不只是3>在第一行?
clapas

75

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

尽管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),您没有看到错误消息!

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

您的脚本确实是一个非常有趣的示例-我认为它根本没有损坏,您只是在错误地使用它!:)


V有趣的回复。。谢谢
Gunith D

实际上,文件描述符1是stdout;标准输入是文件描述符0
programmerjake

@programmerjake糟糕!错字固定。感谢您指出。
rsp

为什么回显“是”使stdout到stderr>&2?
solfish

这是一个了不起的回应。谢谢!有没有办法检查文件是否存在?即。如果您运行./fdtest没有3>&1 4>&1,有没有一种方法fdtest来确定文件描述符3和4不存在?
MattCochrane '18 -10-30

19

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

现在我们将运行它:

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

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


有什么办法可以将输出写到终端上吗?我希望能够在终端中看到所有内容,但希望能够将输出发送到我想要的位置。即./script 2> out.2 3> out.3 4> out.4
Trcx

@Trcx,如果要写入终端,请使用stdoutstderr。您为什么需要或想要使用其他文件?
卡尔·诺鲁姆

我试图与crontab兼容的脚本需要写出多个文件,但是crontab不允许从脚本中写入文件(因为它缺少stdout的支持)。但是我可以使用crontab来写输出脚本到文件。我当时以为可以将脚本写入各种输出,然后让crontab将所有内容分离到适当的文件中。我只是在寻找一种无需使用stdout就可以写入文件的方法。但是,感谢你们,我发现自己想得太多了。(再次:P)感谢您的帮助!
2011年

2

要在添加到从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 descriptor响应,并且当前一个以非零状态退出时,||使用stderr处理以下命令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或者可以更改脚本的执行方法,并将这些多余的文件描述符重定向到所需的任何位置。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.