POSIX'tee'命令如何工作? [英] How does the POSIX 'tee' command work?

查看:90
本文介绍了POSIX'tee'命令如何工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

tee newOutputFile < existingInputFile > newOutputFile2

tee到底将如何接受参数?会是这样吗?

  1. Tee将首先处理newOutputFile < existingInputFile 因此,existentInputFile的内容将被写入newOutputFile
  2. newOutputFile > newOutputFile2 因此,newOutputFile的内容将被写入newOutputFile 2

我正在尝试编写一个处理该特定命令的外壳.但是,对于将参数传递给tee的顺序,我感到困惑.我对程序进行编码的方式将会实现

tee newOutputFile2 < existingInputFIle

tee命令是常规的Unix程序,就像shsortcat一样.

处理< existingInputFile> newOutputFile2涉及的所有I/O重定向工作都是在调用tee命令之前(由fork创建创建将执行tee的进程的)之后,由外壳程序完成的.命令).调用该命令的标准输入来自existingInputFile,其标准输出进入newOutputFile2.赋予tee的唯一参数是argv[0](字符串tee)和argv[1](字符串newOutputFile),以及用于标记参数列表结尾的空指针.

请特别注意,existingInputFile的实际读取中不涉及该外壳程序;它只是打开它进行读取并将其连接到tee的标准输入,但是不知道tee命令是否实际读取了它.类似地,shell并不参与实际写入newOutputFile2的过程.它只是打开并截断它(或创建它)并将其连接到tee的标准输出,但是不知道tee命令是否实际上对其写入了任何内容.在这种情况下,运行tee命令时,父外壳完全是被动的,不执行任何I/O.

根据设计,tee读取其标准输入,并将所有内容的一个副本写入其参数列表中给定的每个文件,再将一个副本写入标准输出.


给我的印象是外壳参与了文件的实际读写.因此,当我调用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).除非命令行中包含2>/dev/null2>&1之类的I/O重定向,否则通常不会修改标准错误.

如果愿意,可以将0644写为:

O_IRUSR|O_IWUSR|O_IRGRP|O_IROTH

(如果要使用组和其他写许可权,则添加|O_IWGRP|O_IWOTH(0666);无论如何,该许可权都将由umask修改).就个人而言,我发现八进制很容易阅读,但是在发明O_Ixyyy名称的几年之前,我开始使用八进制权限.

tee newOutputFile < existingInputFile > newOutputFile2

How exactly will tee take in the arguments? Would it be like this?

  1. Tee will first process newOutputFile < existingInputFile So the contents of existingInputFile will be written into newOutputFile
  2. newOutputFile > newOutputFile2 So the contents of newOutputFile will be written into newOutputFile 2

I am trying to write a shell that process this particular command. However, I am confused as to which order to pass in the arguments to tee. The way I have coded my program up it will do

tee newOutputFile2 < existingInputFIle

解决方案

The tee command is a regular Unix program, just like sh or sort or cat.

All the I/O redirection work involved in handling < existingInputFile and > newOutputFile2 is done by the shell before the tee command is invoked (after the fork that creates the process that will execute the tee command). The command is invoked with its standard input coming from existingInputFile and with its standard output going to newOutputFile2. The only arguments given to tee are argv[0] (the string tee) and argv[1] (the string newOutputFile), plus a null pointer to mark the end of the argument list.

Note specifically that the shell is not involved in the actual reading of existingInputFile; it just opens it for reading and connects it to the standard input of tee, but has no knowledge of whether the tee command actually reads it or not. Similarly, the shell is not involved in the actual writing to newOutputFile2; it just opens and truncates it (or creates it) and connects it to the standard output of tee, but has no knowledge of whether the tee command actually writes anything to it. In this context, while the tee command is running, the parent shell is completely passive, doing no I/O.

By design, tee reads its standard input and write one copy of everything to each of the files given in its argument list and one more copy to standard output.


I was under the impression that the shell was involved in the actual reading and writing of the files. So when I call execvp, it only takes in the command (in this case tee) and the final file to write the contents to (in this case newOutputFile2). I am trying to create my own shell program, how would I do the I/O redirection. Is this where dup2 comes into play?

The shell is only involved in opening and closing, but not in the reading and writing, of the files. In your command line tee newOutputFile < existingInputFile > newOutputFile2, the command is tee and the only other argument is newOutputFile. In general, the command (tee in this case) has no knowledge of the name of the file that is providing it with standard input, nor of the name of the file that it is writing to on its standard output. Indeed, especially with tee, the input is most often a pipe rather than a file, and very often the output is also a pipe rather than a file:

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

In your own shell program, you could use dup2() to duplicate the file descriptor you'd opened separately so that it becomes standard input:

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

Note that it is important to close fd in this scenario. Otherwise, the command is run with at least one extra file descriptor open that was not specified in the command line. You'd have a similar block of code for the standard output redirection.

Or you could use:

// 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);

This is because open() returns the lowest available previously not open file descriptor, so by closing 0, you know that open() will return 0 on success and -1 on failure (even if 0 was previously closed). Then, by induction, you know that after closing 1, open() will return 1 on success and -1 on failure (even if 1 was previously closed). You don't normally tinker with standard error unless the command line includes I/O redirection such as 2>/dev/null or 2>&1 or something similar.

If you prefer, you can write 0644 as:

O_IRUSR|O_IWUSR|O_IRGRP|O_IROTH

(and add |O_IWGRP|O_IWOTH if you want to go with group and other write permission (0666); the permissions will be modified by the umask anyway). Personally, I find the octal a lot easier to read, but I started using octal permissions a number of years before the O_Ixyyy names were invented.

这篇关于POSIX'tee'命令如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆