在 bash 中读取 C 代码:标准输入和标准输出

4

我有一个简单的C语言程序,其中包含读取函数,但是我不理解输出结果。

//code1.c
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main()
{

    int r;
    char c; // In C, char values are stored in 1 byte

    r = read ( 0, &c, 1);

    // DOC:
    //ssize_t read (int filedes, void *buffer, size_t size)
    //The read function reads up to size bytes from the file with descriptor filedes, storing the results in the buffer.
    //The return value is the number of bytes actually read.

    // Here:
    // filedes is 0, which is stdin from <stdio.h>
    // *buffer is &c : address in memory of char c
    // size is 1 meaning it will read only 1 byte

    printf ("r = %d\n", r);

    return 0;
}

这里是程序运行结果的屏幕截图:

Screenshot of program executio

我按照上面的示例运行了这个程序两次,第一次输入"a",第二次输入"aecho hi"。
以下是结果的解释:
  1. 当调用read函数时,它检测到标准输入已关闭,于是重新打开。这一步骤看起来似乎不必要,应该只需读取即可,但为什么会重新打开呢?本人不是很清楚。
  2. 在bash中输入"aecho hi"并按下回车键。
  3. read函数优先处理标准输入,并读取了"aecho hi"的第一个字节:"a"。
  4. 通过printf函数确认read已经处理了1个字节。
  5. a.out程序完成并被终止。
  6. 剩余的数据在bash中被处理,流向标准输出并执行它,但由于read函数已经删除了第一个字节,因此导致了问题。
这些解释都是假设性的,非常模糊。如有任何帮助理解发生了什么的信息,将不胜感激。

程序启动时就打开了标准输入(stdin),而不仅仅是在调用read()函数时才打开。你的其他解释是正确的。 - alk
stdin不是一个“进程”,而是一个流,就像stdout一样。 - alk
goes to stdout” 不是真的,shell(这里是bash)会将它从stdin读取到的内容回显到stdout,然后执行读取到的内容(echo hi),并将结果(hi)打印/写入到stdout - alk
2
read()指定为阻塞并等待所需的所有输入(在您的情况下为 1 字节)或直到输入流明确告知:“没有更多输入”,您可以通过控制台(在 UNIX 下)按 Ctrl-D 来执行此操作。闪烁的光标是由 shell 控制的。 - alk
@JonathanLeffler 我有一个非常相似的问题。据我所知,当我们在终端仿真器中键入一些 echo hi 时,行规则立即将其回显,以便我们可以在终端窗口中看到此行。Shell本身不会执行回显。在OP的示例中也是如此:我们正在输入符号时看到了 aecho hi。但是为什么在 ./a.out 完成后会出现 echo hi 行呢?我的猜测是,如果shell读取带有尾随 <enter> 的命令(shell获取 echo hi<enter> 进行读取),则会执行回显,否则不会。 - Ilya Loskutov
显示剩余2条评论
2个回答

2
当您在终端模拟器上键入时,它会将您的按键写入一个“文件”中,这种情况下是一个内存缓冲区,由于文件系统的存在,它看起来就像可能在磁盘上的任何其他文件一样。
每个进程都从其父进程继承3个打开的文件句柄。我们在这里感兴趣的是其中之一,标准输入。终端仿真器执行的程序(这里是bash)被赋予内存缓冲区作为其标准输入,如第一段所述。
当通过bash运行a.out时,将相同的文件作为标准输入接收。请记住: basha.out正在从相同的已打开文件中读取。
运行a.out后,它的read会阻塞,因为它的标准输入为空。当您键入aecho hi<enter>时,终端会将这些字符写入缓冲区(<enter>变成一个单独的换行符)。a.out仅请求一个字符,因此它获取a并将其余字符留在文件中。(更准确地说,当读取a后,文件指针仍指向e。) a.out完成后,bash尝试从相同的文件中读取。通常,该文件为空(即,文件指针在文件末尾),因此bash阻塞等待另一个命令。但在这种情况下,已经有可用的输入:echo hi\n。现在,bash读取它,就好像您在a.out完成之后键入了它一样。

据我所知,当我们在终端模拟器中键入“echo hi”时,行规则会将此行回显,以便我们可以在终端窗口中看到它。当我们在OP的示例中键入“aecho hi”后立即看到它时,同样的情况也发生了。但是为什么会有“echo hi”行,在“./a.out”完成后出现?是谁做出了这个回声? - Ilya Loskutov
这是标准输入中a.out未消耗的部分;shell(或更具体地说,Readline库)会导致输入缓冲区被显示出来。(shell从终端继承其标准输入,而a.out从shell继承;无论a.out未读取什么,shell都会读取,就像OP在a.out退出后键入它一样。) - chepner
但是,当我们在终端仿真器中键入一些 echo hi<enter> 时,我们只看到这一行一次,即由行规则回显的那一行(据我所知)。在这种情况下,shell 以同样的方式从标准输入读取。在 OP 的示例中,这一行出现了两次(aecho hiecho hi)。那么区别在哪里呢? - Ilya Loskutov
终端在用户输入时会回显 aecho hia.out 退出后,shell 可以检查其标准输入,如果读取成功,则可以在显示提示符之后回显它,并等待进一步的输入(如果需要)。 - chepner

1

请查看this。正如Alk所建议的那样,程序已经打开了标准输入和标准输出。现在你需要明白,一旦你键入:

aecho hi

当你按下回车键时,stdin缓冲区会填充所有这些字母(和空格)-只要你不刷新它,它将继续存在。当程序退出时,stdin缓冲区仍然是满的,并且您的终端会自动处理对stdin的写入,通过将其回显到stdout - 这就是你在结尾看到的内容-你的shell读取stdin

现在,正如您所指出的那样,您的代码为您“按下回车键”,第一次执行添加一个空的shell行,在第二次执行echo hi。但是你必须记住,你按下了回车键,所以"\n"在缓冲区中!具体来说,实际上输入了:

aecho hi\n

一旦您的程序退出,shell会读取缓冲区中剩余的字符,包括回车符,这就是您看到的原因!

但是为什么读取操作会将 "aecho hi" 中的 "a" 清除掉呢? 在第一次执行时,缓冲区为空(假设读取操作清除了 "a")。然而它仍然执行了一个空命令。 - shrimpdrake
我不明白 - 在第一次执行时,你输入了 'a'。这填充了缓冲区为 'a',然后读取并清空(是的,读取会删除它所读取的内容)。这就是读取的工作方式(像大多数读取操作一样,你希望下一次调用读取下一个字符,而不是相同的字符 - scanf、get等等)。然后还有 "\n" - 换行符!我会编辑我的回答,我忘记了它。 - kabanus
关于 "\n":我被“迫”按回车键来表示我已经完成了输入,如果我能够绕过这一点,程序就会将剩余的文本放入stdout中,然后我就可以在bash中进行编辑(因为它还没有执行,有一个\n)。我该怎么做? - shrimpdrake
你是正确的。唯一的方法是在你的代码中写入(参见write)。如果你想从终端读取,你可以从stdin读取所有内容,然后将其写回而不包括\n - kabanus
是的,正如alk所评论的那样,Ctrl-D就可以避免按回车键。 - shrimpdrake

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