使用STDOUT_FILENO,STDIN_FILENO和\ n

时间:2017-12-19 03:31:59

标签: c shell unix terminal io


我知道这里有很多问题在C中讨论写,读系统调用和文件描述符。我尝试了一下,但我真的找不到我想要的东西......

从STDIN_FILENO读取

新行时的EOF?

我们说我在test.c中有这段代码:

int main(void){
    char buff[8];
    int res;
    res=read(STDIN_FILENO, buff, 8);
    assert(res!=-1);
    res=write(STDOUT_FILENO, buff, res);
    assert(res!=-1);
    return 0
}

包含头文件和所有。为什么在执行文件test时会出现此行为?

$ ./test
1
1
$ ./test
11$

在第一次执行中,我使用了一个新行( enter ),而在第二次执行中,我使用 ctrl + d 来放置一个eof 。 为什么在这种情况下换行符会像eof一样?

我的意思是读应该尝试读取8个字符。它不应该在第一次执行中等待吗?如果我想在缓冲区中键入'1\n2\n3'怎么办?如何使我的程序工作?

我有两个想法,但我不知道如何验证它们:

  1. 这种行为是否与进程从管道读取的方式有关?即使有更多的东西可以到来,只想阅读那里的内容的想法?如果是这样:如何使读取调用成为阻塞系统调用? (我希望我不要滥用这个词。)
  2. 当键入新的换行符时,终端是否可以放置EOF字符?我不认为这是真的,但如果我输入123456789 + 输入而执行test停止在输入键后输入,则使用1中的字符到8并且终端执行9作为命令(这意味着它采用9和换行符)。我只是不明白。
  3. 如果错误地切换STDIN_FILENO和STDOUT_FILENO,为什么程序仍然有效?

    现在我有另一个代码,test2.c

    int main(void){
        char buff[8];
        int res;
        res=read(STDOUT_FILENO, buff, 8);
        assert(res!=-1);
        res=write(STDIN_FILENO, buff, res);
        assert(res!=-1);
        return 0;
    }
    

    在这两种情况下我都有相同的行为。 为什么不能使用EBADF读写失败?这可能是我不知道的一些微妙的tty-shell互动吗?

    谢谢!

    N.B:如果您对如何改进此问题或标题有任何建议,请告诉我。

1 个答案:

答案 0 :(得分:0)

将iPhone上输入的一堆评论转化为答案的外观。

计划1

换行符就像行尾,并将数据发送到从终端读取的程序。当read()返回0时,您会获得EOF。如果出现错误,它只会返回-1

  

那么为什么read系统调用会返回?为什么不等待EOF?如果我使用read来读取文件,如果遇到行尾,它就不会返回吗?为什么stdin的处理方式不同?

不是标准输入被区别对待;它是被区别对待的终端。 (另请参阅Canonical vs non-canonical terminal input。)通常(在规范模式下),当您键入换行符或键入 Control-D 时,终端驱动程序会使输入可用。在任何情况下你都没有得到EOF。在第一个中,有两个字符要读;在第二个,一个。读取返回可用的限制。它不等待缓冲区被填充。如果键入换行符(并且程序读取它)然后 Control-D ,则没有等待读取的字符,因此read()返回0,表示EOF。程序可以忽略它并再次尝试阅读;根据文件类型,它可能会或可能不会立即获得另一个EOF指示。当您键入 1 Control-D 时,有一个字符可用,因此read()报告有一个字符可用。要获得EOF(在循环程序中),您必须再次键入 Control-D ,这将报告可用的0字节或EOF。请注意,如果文件中没有剩余的字节,即使磁盘文件也不等待填充缓冲区;短读取是完全正常的,但终端甚至比使用磁盘文件更常见。管道,FIFO,插座都有略微不同的规则;特殊设备(如/dev/null/dev/zero/dev/random等)也有不同的规则。

而且,从根本上说,终端的处理方式与磁盘文件和管道不同,因为合理的行为需要这样做。想一想。终端的一部分是键盘;另一部分是屏幕。它与磁盘文件非常不同。此外,必须猜测要键入多少(和哪些)字符会很麻烦,因为在填充缓冲区之前终端输入不可用。

计划2

对于交换通道,打开终端的经典机制是打开它进行读写,并使每个标准I / O描述符引用它。如果没有打开文件描述符(并且在获得getty / login进程时没有),则open()或{{1}将返回最低的未打开文件描述符}}。标准代码沿着以下行开始:

dup()

所以现在文件描述符0,1,2都引用char *tty_name = …; int fd = open(tty_name, O_RDWR); dup(fd); dup(fd); 中命名的终端。这意味着您通常可以读取标准输出或标准错误,并写入标准输入。它不能保证(如果标准输入通过管道从一个程序传输并且标准输出通过管道连接到另一个程序,它将无法工作),但它通常是以终端作为标准输入和标准输出运行的程序的选项(和标准错误) )。