- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我创建了一个 question about this a few days .我的解决方案与已接受答案中建议的内容一致。然而,我的一个 friend 提出了以下解决方案:
请注意,代码已经更新了几次(检查编辑修订)以反射(reflect)下面答案中的建议。如果您打算给出一个新的答案,请记住这个新代码而不是有很多问题的旧代码。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]){
int fd[2], i, aux, std0, std1;
do {
std0 = dup(0); // backup stdin
std1 = dup(1); // backup stdout
// let's pretend I'm reading commands here in a shell prompt
READ_COMMAND_FROM_PROMPT();
for(i=1; i<argc; i++) {
// do we have a previous command?
if(i > 1) {
dup2(aux, 0);
close(aux);
}
// do we have a next command?
if(i < argc-1) {
pipe(fd);
aux = fd[0];
dup2(fd[1], 1);
close(fd[1]);
}
// last command? restore stdout...
if(i == argc-1) {
dup2(std1, 1);
close(std1);
}
if(!fork()) {
// if not last command, close all pipe ends
// (the child doesn't use them)
if(i < argc-1) {
close(std0);
close(std1);
close(fd[0]);
}
execlp(argv[i], argv[i], NULL);
exit(0);
}
}
// restore stdin to be able to keep using the shell
dup2(std0, 0);
close(std0);
}
return 0;
}
这模拟了一系列通过管道的命令,就像在 bash 中一样,例如:cmd1 |命令2 | ... |命令_n。我说“模拟”,因为如您所见,命令实际上是从参数中读取的。只是为了业余时间编写一个简单的 shell 提示...
当然还有一些问题需要修复和添加错误处理,但这不是这里的重点。我想我有点明白代码了,但它仍然让我很困惑整个事情是如何工作的。
我是不是遗漏了什么,或者这真的有效,而且它是解决问题的一个很好而干净的解决方案?如果没有,谁能指出这段代码存在的关键问题?
最佳答案
看起来很合理,虽然它确实需要修复泄漏的 std
和 aux
到子级和循环之后,以及父级的原始 stdin
永远失去了。
如果有颜色可能会更好......
./a.out foo bar baz <stdin >stdoutstd = dup(stdout) || |+==========================std || || ||pipe(fd) || || pipe1[0] -- pipe0[1] || || || || || ||aux = fd[0] || || aux || || || XX || || || || /-------++----------+| ||dup2(fd[1], 1) || // || || || || || || || ||close(fd[1]) || || || XX || || || || ||fork+exec(foo) || || || || XX || || || /-----++-------+| ||dup2(aux, 0) // || || || || || || ||close(aux) || || XX || || || ||pipe(fd) || || pipe2[0] -- pipe2[1] || || || || || ||aux = fd[0] || || aux || || || XX || || || || /-------++----------+| ||dup2(fd[1], 1) || // || || || || || || || ||close(fd[1]) || || || XX || || || || ||fork+exec(bar) || || || || XX || || || /-----++-------+| ||dup2(aux, 0) // || || || || || || ||close(aux) || || XX || || || ||pipe(fd) || || pipe3[0] -- pipe3[1] || || || || || ||aux = fd[0] || || aux || || || XX || || || || /-------++----------+| ||dup2(fd[1], 1) || // || || || || || || || ||close(fd[1]) || || || XX || || XX || || || /-------++-----------------+|dup2(std, 1) || // || || || || || ||fork+exec(baz) || || || ||
foo
gets stdin=stdin
, stdout=pipe1[1]
bar
gets stdin=pipe1[0]
, stdout=pipe2[1]
baz
gets stdin=pipe2[0]
, stdout=stdout
My suggestion is different in that it avoids mangling the parent's stdin
and stdout
, only manipulating them within the child, and never leaks any FDs. It's a bit harder to diagram, though.
for cmd in cmds
if there is a next cmd
pipe(new_fds)
fork
if child
if there is a previous cmd
dup2(old_fds[0], 0)
close(old_fds[0])
close(old_fds[1])
if there is a next cmd
close(new_fds[0])
dup2(new_fds[1], 1)
close(new_fds[1])
exec cmd || die
else
if there is a previous cmd
close(old_fds[0])
close(old_fds[1])
if there is a next cmd
old_fds = new_fds
parent cmds = [foo, bar, baz] fds = {0: stdin, 1: stdout}cmd = cmds[0] { there is a next cmd { pipe(new_fds) new_fds = {3, 4} fds = {0: stdin, 1: stdout, 3: pipe1[0], 4: pipe1[1]} } fork => child there is a next cmd { close(new_fds[0]) fds = {0: stdin, 1: stdout, 4: pipe1[1]} dup2(new_fds[1], 1) fds = {0: stdin, 1: pipe1[1], 4: pipe1[1]} close(new_fds[1]) fds = {0: stdin, 1: pipe1[1]} } exec(cmd) there is a next cmd { old_fds = new_fds old_fds = {3, 4} }}cmd = cmds[1] { there is a next cmd { pipe(new_fds) new_fds = {5, 6} fds = {0: stdin, 1: stdout, 3: pipe1[0], 4: pipe1[1], 5: pipe2[0], 6: pipe2[1]} } fork => child there is a previous cmd { dup2(old_fds[0], 0) fds = {0: pipe1[0], 1: stdout, 3: pipe1[0], 4: pipe1[1], 5: pipe2[0], 6: pipe2[1]} close(old_fds[0]) fds = {0: pipe1[0], 1: stdout, 4: pipe1[1], 5: pipe2[0] 6: pipe2[1]} close(old_fds[1]) fds = {0: pipe1[0], 1: stdout, 5: pipe2[0], 6: pipe2[1]} } there is a next cmd { close(new_fds[0]) fds = {0: pipe1[0], 1: stdout, 6: pipe2[1]} dup2(new_fds[1], 1) fds = {0: pipe1[0], 1: pipe2[1], 6: pipe2[1]} close(new_fds[1]) fds = {0: pipe1[0], 1: pipe1[1]} } exec(cmd) there is a previous cmd { close(old_fds[0]) fds = {0: stdin, 1: stdout, 4: pipe1[1], 5: pipe2[0], 6: pipe2[1]} close(old_fds[1]) fds = {0: stdin, 1: stdout, 5: pipe2[0], 6: pipe2[1]} } there is a next cmd { old_fds = new_fds old_fds = {3, 4} }}cmd = cmds[2] { fork => child there is a previous cmd { dup2(old_fds[0], 0) fds = {0: pipe2[0], 1: stdout, 5: pipe2[0], 6: pipe2[1]} close(old_fds[0]) fds = {0: pipe2[0], 1: stdout, 6: pipe2[1]} close(old_fds[1]) fds = {0: pipe2[0], 1: stdout} } exec(cmd) there is a previous cmd { close(old_fds[0]) fds = {0: stdin, 1: stdout, 6: pipe2[1]} close(old_fds[1]) fds = {0: stdin, 1: stdout} }}
Your updated code does fix the previous FD leaks… but adds one: you're now leaking std0
to the children. As Jon says, this is probably not dangerous to most programs... but you still should write a better behaved shell than this.
Even if it's temporary, I would strongly recommend against mangling your own shell's standard in/out/err (0/1/2), only doing so within the child right before exec. Why? Suppose you add some printf
debugging in the middle, or you need to bail out due to an error condition. You'll be in trouble if you don't clean up your messed-up standard file descriptors first. Please, for the sake of having things operate as expected even in unexpected scenarios, don't muck with them until you need to.
As I mentioned in other comments, splitting it up into smaller parts makes it much easier to understand. This small helper should be easily understandable and bug-free:
/* cmd, argv: passed to exec
* fd_in, fd_out: when not -1, replaces stdin and stdout
* return: pid of fork+exec child
*/
int fork_and_exec_with_fds(char *cmd, char **argv, int fd_in, int fd_out) {
pid_t child = fork();
if (fork)
return child;
if (fd_in != -1 && fd_in != 0) {
dup2(fd_in, 0);
close(fd_in);
}
if (fd_out != -1 && fd_in != 1) {
dup2(fd_out, 1);
close(fd_out);
}
execvp(cmd, argv);
exit(-1);
}
应该这样:
void run_pipeline(int num, char *cmds[], char **argvs[], int pids[]) {
/* initially, don't change stdin */
int fd_in = -1, fd_out;
int i;
for (i = 0; i < num; i++) {
int fd_pipe[2];
/* if there is a next command, set up a pipe for stdout */
if (i + 1 < num) {
pipe(fd_pipe);
fd_out = fd_pipe[1];
}
/* otherwise, don't change stdout */
else
fd_out = -1;
/* run child with given stdin/stdout */
pids[i] = fork_and_exec_with_fds(cmds[i], argvs[i], fd_in, fd_out);
/* nobody else needs to use these fds anymore
* safe because close(-1) does nothing */
close(fd_in);
close(fd_out);
/* set up stdin for next command */
fd_in = fd_pipe[0];
}
}
可以看到Bash从 execute_cmd.c#execute_pipeline
调用的 execute_cmd.c#execute_disk_command
,xsh从 jobs.c#job_run
调用的 process.c#process_run
,甚至是 BusyBox 中的每一个的 various small and minimal shells将它们分开。
关于c - C 中的这种多管道代码有意义吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/948221/
我有一张 table People (First_Name, Last_Name)。此表包含与示例中一样重复的记录(并非所有行都重复): First_Name Last_Name John
我用 Java 编写过很多程序,之前也涉足过 C++。我在各种 C++ 书籍中阅读了有关指针的内容,并完成了书籍中的各种示例。 我了解指针的基础知识,但有一件事我一直不清楚。指针在现实世界中的应用是什
线 .Mappings(m => m.FluentMappings.AddFromAssemblyOf() 它有什么作用?它会在派生自 ClassMap 的 Product 类的程序集中查找任
我有用于打印数字的自定义打印功能。我制作了一个 ASCII 版本和一个 UTF-16LE 版本。 UTF-16LE 版本对 0-9 使用全角代码/字符,对十六进制使用 A-F。在调试我的函数时,我注意
这是我的代码片段: float ab(float); 以后 if(ab(temp)
我在一个项目文件中包含以下代码: //begin of the file ((window) => { 'use strict'; class View extends GSM.Event
我一直在到处寻找关于 ? 用法的正确解释。和 *。我注意到我可以使用以下方法拒绝所有用户的访问: 如果我想允许某个组,我应该在其上方添加下一行: 但是当我看到人们使用 ? 时,我开始忘记什么意思,
我正在关注 melon js tutorial .这是在我的 HUD.js 文件的顶部。 game.HUD = game.HUD || {} 我以前在其他例子中见过这个。 namespace.some
我正在处理一个包含数千行代码的文件。我正在第 700 行实现一个算法。我经常不得不离开这些行来检查文件中的其他方法。 导航回到我实际编码的地方通常很痛苦。如果我可以在第 700 行设置一个航路点并为其
我遇到了这段代码 do { if (higherQuality && w > targetWidth) { w /= 2; if (w &
uint8_t * const LCDMem = (uint8_t *) &LCDM3; 此代码在 msp430fg4618 培训套件中用于 lcd 配置。谁能解释一下上述代码的含义? 它允许使用 a
上下文 阅读一些内核代码。 问题 我不明白这行是什么意思 *(void **) &(int[2]){0,PAGE_SIZE}; 还有更多,这是什么意思 {0,PAGE_SIZE} 对我来说,它看起来不
我正在查看 Underscore.js 的源代码库,专门用于 map方法(该页面第 85 行左右,并复制到此处): _.map = function(obj, iterator, context)
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 9
我是一名优秀的程序员,十分优秀!