我将我的问题浓缩为以下代码:
#include <stdio.h>
int main(){
char buffer[256];
printf("Enter input: ");
scanf("%s", buffer);
system("/bin/sh");
return 0;
}
如果我使用用户输入运行该程序,我会得到:
user@ubuntu:~/testing/temp$ ./main
Enter input: Test
$
最后一行是程序启动的 shell。
但是如果我使用来自管道的输入运行程序:
user@ubuntu:~/testing/temp$ echo 'test' | ./main
Enter input: user@ubuntu:~/testing/temp$
该程序似乎没有打开外壳。
经过一番修补后,我意识到如果我这样做:
user@ubuntu:~/testing/temp$ (python -c "print 'test'" ; echo 'ls') | ./main
a.out main main.c
Enter input: user@ubuntu:~/testing/temp
我能够ls
在打开的外壳中运行该命令。
那么,两个问题:
- 为什么外壳不像第一种情况那样打开?
- 我该如何处理这个问题?必须决定运行哪些命令非常不方便前运行程序:我更愿意有一个外壳,我可以在其中动态选择要运行的命令。
答案1
- 为什么外壳不像第一种情况那样打开?
第一种情况stdin
是终端和 shell 是交互的。它等待您的命令等。
第二种情况stdin
是管道,并且外壳是非交互式的。您的程序消耗第一行stdin
(即字符串test\n
),然后 shell 尝试读取stdin
并看到EOF
.它退出,因为这就是获取输入的程序EOF
应该做的事情。
在第三种情况下,出于同样的原因,shell 再次是非交互式的。您消耗了(ie )scanf()
上的第一行,然后 shell 读取。 shell 运行,尝试读取更多命令,看到,然后退出。stdin
test\n
ls
ls
EOF
- 我该如何处理这个问题?
如果“处理它”是指在stdin
连接到管道时运行交互式 shell,则解决方案是使用pty(7)
.
答案2
您的 C 程序的行为与(read x; /bin/sh)
.
如果您只是在命令行上输入这个片段,那么它的标准输入将连接到您终端的键盘;将读取一行,然后sh
读取更多行,直到发生文件结束条件(通常可以按 Ctrl-D 来导致文件结束条件)。
在通过管道输入程序的示例中,输入大小是有限的;这就是您放在那里的内容,仅此而已:第一行之后的行(如果有)将由 解释sh
,然后退出。
您可以通过使用 cat 将键盘输入转发到以下方式来模拟无管道情况sh
:
(echo test; cat) | (read x; /bin/sh)
也许:
(echo test; cat) | ./main
然而,这可能不如直接运行 shell 那么好;检测到其输入不是终端,它可能会禁用其行编辑功能。
答案3
要运行连接到管道的交互式 shell,您只需使其具有交互式功能。
input | /bin/sh -i
shell 不需要终端来进行交互 - 它需要交互输入。总的来说,交互式运行时 shell 的行为与终端几乎没有关系 -(至少根据规格)- 通常与非交互式 shell 的行为不同,在非交互式 shell 中,错误处理比其他任何事情都更受关注。交互式 shell 往往不会在错误情况下退出,否则会导致非交互式 shell 退出。一个外壳应该默认不过,如果输入来自终端,则为交互模式。
有些 shell 似乎需要终端输入才能进行交互使用,但这是一种错觉。事实上,这些 shell 的工作方式通常与您的示例案例非常相似 -bash
例如,设置readline
代表其处理终端 i/o 细节,zsh
调用其 ZLE 行编辑器,并且dash
(如果没有使用构建时选项编译小的)BSDlibedit
库中的链接。这些行编辑器读取终端输入并将其处理为类似于逐行 shell 脚本的内容,然后 shell 根据需要执行该脚本。
不过,您对这些编辑器中的任何一个都没有问题。根据您的提示和 execve 调用来判断,您正在调用一个dash
用小的构建时选项(Debian 默认)在这种情况下,它会正常工作 - 但您可能从中获得的唯一行编辑功能将是终端的行规则本机提供的功能(看stty
)。
您的问题是 shell 没有任何输入 - 当它检测到 EOF 时,它就会死亡,正如其他地方已经指出的那样。你可能会做...
input | /bin/sh -i -o ignoreeof
但你可能会不是喜欢结果。dash
不会像某些 shell 那样在连续 10 个空读取时退出 - 它只会打印...
Type 'exit' to exit the shell
...到标准错误永远。稍微好一点的可能是...
cat input - | /bin/sh -i
...将文件中的 shell 命令input
与其stdin 连接到cat
其-
stdout,然后以交互方式执行结果/bin/sh
。这将起作用 - 尽管您可能希望确保将stty
线路规则配置为规范状态之类的东西并使用适当的erase
键,以便您至少可以获得功能正常的退格键。这可能已经正确设置 - 但无论如何都值得检查。
其他方式...
echo ": some command; exec <$(tty)" | /bin/sh -i
上述方法之所以有效,是因为您的管道是控制终端上的当前前台作业 - 您的管道当前拥有终端输入并且cat
实际上正在读取它。与此形成对比的是python -c
和echo
不读取它 - 它们只传递由命令行参数生成的输出 - 因此,当它们的输出结束时,shell 的输入也会结束。这是正确的,除非 shell 被指示在其他地方查找输入,就像我echo
在第二个示例中所做的那样。
终端只是 shell 的多种可能输入源之一 - 并且可以通过多种不同方式进行处理。您的终端的会话领导者 - 看起来像bash
- 等待管道的终端控制结束,以便它可以重新获得控制并执行下一步操作。该信息将作为异步信号传递给它 - 这就是终端的用途。终端在任意数量的读取/打印过程中复用显示/输入对。他们在这方面做得很好。
例如:
$ PS1='bgsh1: ' sh -i +m & PS1='bgsh2: ' sh -i +m &
$ bgsh1: bgsh2:
[2] + Stopped (tty input) sh -i +m
[1] - Stopped (tty input) sh -i +m
$ i=0; while [ "$((i+=1))" -lt 5 ]; do fg "%$(((i%2)+1))"; done
sh -i +m
bgsh2: var=something
bgsh2: kill -TSTP $$
sh -i +m
bgsh1: var=else
bgsh1: kill -TSTP $$
sh -i +m
bgsh2: echo $var; kill -TSTP $$
something
sh -i +m
bgsh1: echo $var; kill -TSTP $$
else
[1] + Stopped sh -i +m
[2] - Stopped sh -i +m
$