C 系统(“bash”)忽略标准输入

Hed*_*iri 7 linux bash c

我有一个文件输入:

$ cat input
1echo 12345
Run Code Online (Sandbox Code Playgroud)

我有以下程序

第一个版本

#include <stdio.h>
#include <stdlib.h>

int main() {
  system("/bin/bash -i");
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

现在如果我运行它,

$ gcc -o program program.c
$ ./program < input
bash: line 1: 1echo: command not found
$ exit
Run Code Online (Sandbox Code Playgroud)

一切都按预期工作。

现在我想忽略文件输入的第一个字符,所以我getchar()在调用system().

第二版:

#include <stdio.h>
#include <stdlib.h>

int main() {
  getchar();
  system("/bin/bash -i");
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

令人惊讶的是,bash 立即退出,就像没有输入一样。

$ gcc -o program program.c
$ ./program < input
$ exit
Run Code Online (Sandbox Code Playgroud)

问题为什么 bash 没有收到输入?

注意 我尝试了一些东西,我发现为主进程分叉一个新孩子可以解决问题:

第三版

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
  getchar();
  if (fork() > 0) {
    system("/bin/bash -i");
    wait(NULL);
  }
  return 0;
}

$ gcc -o program program.c
$ ./program < input
$ 12345
$ exit
Run Code Online (Sandbox Code Playgroud)

操作系统Ubuntu 16.04 64 位,gcc 5.4

Mic*_*mer 15

文件流定义为

完全缓冲当且仅当可以确定不涉及交互式设备

由于您正在重定向到标准输入,因此 stdin 是非交互式的,因此它被缓冲。

getchar是一个流函数,它将导致从流中填充缓冲区,消耗这些字节,然后将单个字节返回给您。system只运行 fork-exec,因此子进程按原样继承所有打开的文件描述符。当bash试图从它的标准输入中读取时,它会发现它已经在文件的末尾,因为所有的内容都已经被你的父进程读取了。


在您的情况下,您只想在移交给子进程之前消耗该单个字节,因此:

setvbuf()函数可以在 stream 指向的流与打开的文件相关联之后但在对流执行任何其他操作 [...] 之前使用。

因此在 之前添加一个合适的调用getchar()

#include <stdio.h>
#include <stdlib.h>

int main() {
  setvbuf(stdin, NULL, _IONBF, 0 );
  getchar();
  system("/bin/bash -i");
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

将通过设置stdin为无缓冲 ( _IONBF)来执行您想要的操作。getchar将导致只读取一个字节,其余的输入将可供子进程使用。这可能是更好地使用read替代,避免了整体流接口,在这种情况下。


当在 fork 之后可以从两个进程访问句柄时,POSIX 强制要求某些行为,但明确指出

如果进程之一执行的唯一操作是exec函数之一 [...],则在该进程中永远不会访问句柄。

这意味着system()它没有(必须)做任何特别的事情,因为它只是 fork-exec

这可能是您的fork解决方法所采用的方法。如果在两侧都可以访问手柄,那么对于第一个

如果流以允许读取的模式打开,并且底层打开的文件描述指的是能够查找的设备,则应用程序应执行fflush(),或应关闭流。

调用fflush()读取流将意味着:

底层打开文件描述的文件偏移量应设置为流的文件位置

所以描述符位置应该重置回 1 字节,与流的相同,并且后续子进程将从该点开始获取其标准输入。

此外,对于第二个(孩子的)句柄

如果任何先前的活动句柄已被显式更改文件偏移量的函数使用,除了上面对第一个句柄的要求外,应用程序应在适当的位置执行lseek()fseek()(根据句柄类型)。

我认为“适当的位置”可能是相同的(尽管没有进一步说明)。该getchar()呼叫“明确地改变了文件偏移”,所以这种情况下应适用。该通道的目的是,在叉的任一分支工作应该有同样的效果,所以无论fork() > 0fork() == 0应该工作一样。但是,由于在这个分支中实际上没有发生任何事情,因此有争议的是,这些规则中的任何一条都不应该用于父级或子级。

确切的结果可能取决于平台 - 至少,没有直接指定“可以访问”的确切内容,也没有直接指定哪个句柄是第一个和第二个。父进程还有一个更早的覆盖案例:

如果对该打开的文件描述符的任何句柄执行的唯一进一步操作是关闭它,则无需采取任何操作。

这可以说适用于您的程序,因为它只是在之后终止。如果是这样fflush(),则应跳过所有剩余情况,包括,并且您看到的行为将偏离规范。有争议的是,调用fork()构成对句柄执行操作,但不明确或不明显,所以我不相信这一点。要求中也有足够多的“要么”和“或”,似乎允许很多变化。

出于多种原因,我认为您看到的行为可能是一个错误,或者至少是对规范的慷慨解释。我的总体解读是,因为在每种情况下,the 的一个分支fork都不做任何事情,所以不应该应用这些规则中的任何一个,并且应该忽略描述符位置。我不能确定这一点,但它似乎是最直接的阅读。


我不会依赖fork技术工作。你的第三个版本在这里对我不起作用。使用setbuf/setvbuf代替。如果可能,我什popen至会使用或类似的方法明确设置具有必要过滤的过程,而不是依赖于流和文件描述符交互的变幻莫测。


mur*_*uru 1

鼻恶魔

man 3 getchar说:

The getchar() function shall be equivalent to getc(stdin).
Run Code Online (Sandbox Code Playgroud)

man 3 getc说:

It is not advisable to mix calls to  input  functions  from  the  stdio
library  with  low-level  calls  to  read(2)  for  the  file descriptor
associated with the input stream; the results  will  be  undefined  and
very probably not what you want.
Run Code Online (Sandbox Code Playgroud)

交互模式下的 Bash 可能使用read等(通过 readline)来直接访问输入。所以我们有鼻恶魔

  • 这不太正确 - 即使“bash”使用“stdio”库,它也会访问与父进程不同的缓冲区,因此结果仍然是未定义的。 (2认同)