如果我想将所有标准输出重定向到一个文件,我会运行
my_prog 1> out
如果我想对 stderr 做同样的事情,我会运行
my_prog 2> err
但是,我知道在 shell 中还有其他文件描述符。例如,最热门的答案这个问题使用第三个文件描述符,可以通过命令行向其发送数据
echo "Some console message" 1>&3
C 程序有办法写入这个文件描述符吗?在没有其他程序读取它的情况下写入它是否也会将其输出发送到终端?
答案1
澄清一些事情。在:
echo foo >&3
echo
不写入"foo\n"
其文件描述符 3.echo
始终写入 stdout、其文件描述符 1. 它write()
使用整数作为其第一个参数、指向内存中以第二个参数1
开头的区域的指针来调用系统调用,并且( )的长度作为第三个。foo\n
4
foo\n
在 C 语言中,你可以这样写write(1, "foo\n", 4)
。在上面的代码中,shell 将 fd 1 重定向到相同的打开文件描述在调用之前在 fd 3 上打开echo
(通过dup2()
系统调用)。因此,尽管它在功能上是等效的,但它与 do 不同write(3, "foo\n", 4)
。实际上,它类似于(简化的):
if (pid = fork())
waitpid(pid, ...);
else {
dup2(3, 1);
execlp("echo", "foo", 0);
}
并echo
做了一个write(1, "foo\n", 4)
除了在几乎所有 shell 中,echo
是内置的,因此没有fork
或exec
。相反,shell 会:
saved_stdout = dup(1);
dup2(3, 1);
builtin_echo("foo");
dup2(saved_stdout, 1); close(saved_stdout); /* restore stdout */
(其中是在同一进程中builtin_echo()
执行此操作的函数)。write(1, "foo\n", 4)
对于执行此操作的命令write(3, "foo\n", 4)
,您可以查看ksh
/zsh
的print -u3 foo
内置命令。
现在,每个进程都可以随意使用文件描述符。除了 0、1 和 2 按照惯例保留给 stdin、stdout 和 stderr 之外。其他 fd 通常并不特殊,但在 shell 中(这只是应用程序的一种类型),fd 0 到<some-value>
某些值至少为 9 的位置被保留供(shell 的)用户使用。壳不会与它们混合以获取其内部的汤。例如我的saved_stdout = dup(1)
上面是一个近似值。实际上,shell 将确保saved_stdout
该值大于<some-value>
。
现在,由于 0、1、2 之外的 fd 没有附加任何约定,因此您不能期望在 fd 3 上打开任何内容。很可能它会被关闭。或者如果不是,则脚本的调用者可能只是忘记关闭它(或向其添加标志O_CLOEXC
),因为它没有理由为您打开它,因为没有人期望在 fd 3 上打开任何内容。
如果您知道 fd 3 已打开某些内容(通常由您事先在同一脚本中打开),则可以使用 fd 3,例如:
{
var=$(cmd 2>&1 >&3)
} 3>&1
其中 fd 3 被定义为dup()
fd 1 的a前我们用它cmd
返回dup()
到 fd 1。
答案2
假设描述符是打开的,C 确实可以write
(或更可能使用一些最终归结为 a 的更高级别调用write(2)
)对该描述符。这里是这个write2three.c
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
if ((fd = dup(3)) == -1) err(1, "dup failed");
dprintf(fd, "hello\n");
exit(EXIT_SUCCESS);
}
例如
$ make write2three
cc write2three.c -o write2three
$ ./write2three
write2three: dup failed: Bad file descriptor
$ rm out
$ ./write2three 3>out
$ cat out
hello
$
之后您可能需要在外壳中进行清理。
$ exec 3>&-
$
答案3
我对 C 不太熟悉,所以可能有更好的方法,但您可以简单地使用系统调用 ( man 2 write
):
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
写入数据的发送位置取决于文件描述符打开的内容。仅当文件描述符属于终端时,数据才会到达终端。
如果您写入管道或套接字,而另一端不读取数据,则write()
当缓冲区已满时,写入应用程序(即调用)将阻塞。
您可以在中看到文件描述符/proc/$PID/fd
答案4
C 中是否可以写入其他文件描述符?
是的。
大多数进程从执行它们的父进程继承 3 个打开的文件描述符。
stdin
,stdout
和stderr
。这些分别是文件描述符 0 - 2。
我相信 fd 0 ( stdin
) 以只读模式打开,因此您的 c 程序可以读取此文件描述符,但不能写入它。
当您的程序打开另一个文件时,它会按增量顺序获取下一个文件描述符。