POSIX中的'tee'命令是如何工作的?

4

tee newOutputFile < existingInputFile > newOutputFile2

这个命令的参数是如何传入的呢?是像这样吗?

  1. tee 首先处理 newOutputFile < existingInputFile,因此将现有输入文件 existingInputFile 的内容写入 newOutputFile。
  2. newOutputFile > newOutputFile2,因此将 newOutputFile 的内容写入 newOutputFile 2。

我正在尝试编写一个处理此特定命令的shell,但我不确定传递参数给 tee 的顺序。根据我编写程序的方式,它会执行以下操作:

tee newOutputFile2 < existingInputFIle


请查看http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/tee.c中的函数tee_files() - Henk Langeveld
1个回答

6
tee命令是一个常规的Unix程序,就像shsort或者cat一样。
在调用tee命令之前(创建将执行tee命令的进程的fork之后),处理处理< existingInputFile> newOutputFile2的所有I/O重定向工作都是由shell完成的。该命令的标准输入来源于existingInputFile,标准输出连接到newOutputFile2。传递给tee的唯一参数是argv[0](字符串tee)和argv[1](字符串newOutputFile),以及一个空指针表示参数列表的结束。
特别注意,shell并不参与实际读取existingInputFile;它只是打开文件进行读取并将其连接到tee的标准输入,但并不知道tee是否实际上读取了它。同样,shell也不参与实际写入newOutputFile2;它只是打开文件并将其截断(或创建),将其连接到tee的标准输出,但并不知道tee是否实际上向其写入了任何内容。在此情况下,当tee命令正在运行时,父shell是完全被动的,并不进行I/O操作。
按设计,tee读取它的标准输入并将每个给定参数列表中的文件都写入一次以及写入标准输出的副本。

我以为shell参与了实际读写文件。所以当我调用execvp时,它只接受命令(在这种情况下是tee)和写入内容的最终文件(在这种情况下是newOutputFile2)。我正在尝试创建自己的shell程序,应该如何进行I/O重定向?dup2是否发挥作用?

Shell仅涉及文件的打开和关闭,而不涉及读写。在您的命令行tee newOutputFile < existingInputFile > newOutputFile2中,命令是tee,唯一的其他参数是newOutputFile。通常情况下,命令(在本例中为tee)并不知道其标准输入提供者文件的名称,也不知道其标准输出写入的文件名称。实际上,特别是对于tee,输入通常是管道,而不是文件,输出也很常见是管道而不是文件:

some_command arg1 arg2 | tee some_command.log | another_command its_arg1 its_arg2 > output.file

在你自己的shell程序中,你可以使用dup2()函数来复制已经单独打开的文件描述符,使其成为标准输入:

// Redirect standard input from existingInputFile using dup2()
char *i_filename = "existingInputFile";
int fd = open(i_filename, O_RDONLY);
if (fd < 0)
{
    fprintf(stderr, "unable to open file %s for reading (%d: %s)\n",
            i_filename, errno, strerror(errno));
    exit(1);
}
dup2(fd, STDIN_FILENO);
close(fd);  // Crucial!

请注意,在这种情况下关闭fd非常重要。否则,命令将使用至少一个未在命令行中指定的额外文件描述符打开。对于标准输出重定向,您需要类似的代码块。
或者您可以使用:
// Redirect standard input from existingInputFile
close(0);
char *i_filename = "existingInputFile";
int fd = open(i_filename, O_RDONLY);
if (fd < 0)
{
    fprintf(stderr, "unable to open file %s for reading (%d: %s)\n",
            i_filename, errno, strerror(errno));
    exit(1);
}
assert(fd == 0);

// Redirect standard output to NewOutputFile2
close(1);
char * o_filename = "newOutputFile2";
fd = open(o_filename, O_WRONLY|O_CREAT|O_TRUNC, 0644); // Classically 0666
if (fd < 0)
{
    fprintf(stderr, "unable to open file %s for writing (%d: %s)\n",
            o_filename, errno, strerror(errno));
    exit(1);
}
assert(fd == 1);

这是因为open()返回先前未打开的最低可用文件描述符,因此通过关闭0,您知道open()将在成功时返回0,并在失败时返回-1(即使0以前已关闭)。然后,通过归纳法,您知道在关闭1之后,open()将在成功时返回1,并在失败时返回-1(即使1以前已关闭)。除非命令行包括I/O重定向(如2>/dev/null2>&1或类似内容),否则通常不会对标准错误进行调整。
如果您愿意,也可以将0644写成:
O_IRUSR|O_IWUSR|O_IRGRP|O_IROTH

如果您想使用组和其他写入权限(0666),请添加|O_IWGRP|O_IWOTH。无论如何,权限都将被umask修改。个人而言,我发现八进制更容易阅读,但在O_Ixyyy名称被发明之前,我已经使用八进制权限了许多年。


我明白了。我之前认为shell实际上涉及文件的读写。所以当我调用execvp时,它只接受命令(在这种情况下是tee)和要将内容写入的最终文件(在这种情况下是newOutputFile2)。我正在尝试创建自己的shell程序,那我应该如何进行I/O重定向呢?这时候是不是就需要用到dup2了? - Ria
1
实际上,特别是使用tee时,输入通常是管道而不是文件,输出也很常见是管道而不是文件。因此,tee的名称是一个文字游戏,即“t-pipe”(该命令通过管道传输数据,并将其分流以将数据写入文件;STDIN和STDOUT是t-pipe的两端,目标文件是第三个)。 - Sigi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接