我正在为一个大学作业开发迷你外壳。我们必须读入命令,从路径var中找到要执行的二进制文件,并在有和没有管道的情况下执行命令。我有一切工作(我想)除了管道。 通过网络搜索,我已经能够构建一个测试程序,该程序使用两个硬编码命令并相互管道,具有预期的结果。现在,当我将该代码复制并粘贴到我的实际程序中时,第一个命令输出正常(实际输出命令就像没有管道一样),而第二个我认为实际上没有做任何事情(第一个输出不是传递到第二个。)
以下是整个代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define BUFFSIZE 1024
#define MAXWORDS 17
#define MAXCHAR 64
static char *path;
extern char **environ;
//split cmd "string" on pipe (|) symbol
void split(char **pipe, char **left, char **right, int n)
{
int i, x;
for(i = 0; i < n; i++)
{
if (strchr(&pipe[i][0], '|') != 0)
{
for(x = 0; x < i; x++)
strcpy(left[x], pipe[x]);
left[x++] = 0;
break;
}
}
i++;
for(x = 0; i < n; x++)
strcpy(right[x], pipe[i++]);
right[x++] = 0;
}
//Find directory where cmd can be executed from (PATH or direct access)
char *finddir(char *s)
{
char *pp;
char *pf;
int ok;
strcpy(path, getenv("PATH"));
pp = strtok(path, ":");
while (pp != NULL)
{
pf = (char *)malloc(strlen(pp) + strlen(s) + 2);
if (pf == NULL)
{
fprintf(stderr, "Out of memory in finddir\n");
return NULL;
}
strcpy(pf,pp);
strcat(pf,"/");
strcat(pf,s);
ok = !access(pf, X_OK);
free(pf);
if (ok)
return pp;
pp = strtok(NULL, ":");
}
return NULL;
}
int cmdcheck(char *cmd, char *p)
{
char *dir;
if (strchr(p, '/') != NULL)
sprintf(cmd, "%s\0", p);
else
{
dir = finddir(p);
if (dir == NULL)
return 1;
else
sprintf(cmd, "%s/%s\0", dir, p);
}
return 0;
}
void runpipe(int pfd[], char *cmd1, char *p1[], char *cmd2, char *p2[])
{
int pid;
int status;
switch (pid = fork())
{
case 0: //Child
dup(pfd[0]);
close(pfd[1]); //the child does not need this end of the pipe
execve(cmd2, p2, environ);
perror(cmd2);
default: //Parent
dup(pfd[1]);
close(pfd[0]); //the parent does not need this end of the pipe
execve(cmd1, p1, environ);
perror(cmd1);
case -1: //ERROR
perror("fork-RP");
exit(1);
}
}
int main(void)
{
int status; //read status when reading cmd in
char ch; //character currently reading
int n, i, x; //(n) count of chars read; (i) cmd args iter; (x) cmd arg iter in cmd array
char buffer[BUFFSIZE]; //read buffer
char *token; //token var when splitting buffer
int pid0, pid1, pid2; //return ID from fork call
int which; //return value from wait (child pID that just ended)
char msg[100]; //messages to print out
char *cmd1, *cmd2; //cmds when piping
char *params[MAXWORDS]; //cmd parameters to send to execve
int fd[2]; //pipe file descriptors
char *pparam1[MAXWORDS]; //cmd "string" on left side of pipe
char *pparam2[MAXWORDS]; //cmd on right side of pipe
for(;;)
{
for (i = 0; i < MAXWORDS; i++)
params[i] = malloc(MAXCHAR);
n = 0;
write(1, "# ", 2);
for(;;)
{
status = read(0, &ch, 1);
if (status == 0)
return 0; //End of file
if (status == -1)
return 1; //Error
if(n == BUFFSIZE)
{
write(1, "Line too long\n", 14);
return 1;
}
buffer[n++] = ch;
if(ch == '\n')
break;
}
buffer[n] = '\0';
x = 0;
token = strtok(buffer, " \t\n\0");
while(token != NULL)
{
strcpy(params[x++], token);
token = strtok(NULL, " \t\n\0");
}
params[x] = 0;
path = getenv("PATH");
if (path == NULL)
{
fprintf(stderr, "PATH environment variable not found.\n");
return 1;
}
n = strlen(path);
path = (char *)malloc(n+1);
if (path == NULL)
{
fprintf(stderr, "Unable to allocate space for copy of PATH.\n");
return 1;
}
cmd1 = malloc(MAXCHAR);
cmd2 = malloc(MAXCHAR);
for (i = 0; i < MAXWORDS; i++)
pparam1[i] = malloc(MAXCHAR);
for (i = 0; i < MAXWORDS; i++)
pparam2[i] = malloc(MAXCHAR);
split(params, pparam1, pparam2, x);
//Check first cmd
if(cmdcheck(cmd1, pparam1[0]))
{
sprintf(msg, "cmd '%s' is not executable\n", pparam1[0]);
write(1, msg, strlen(msg));
break;
}
//Check second cmd
if(cmdcheck(cmd2, pparam2[0]))
{
sprintf(msg, "cmd '%s' is not executable\n", pparam2[0]);
write(1, msg, strlen(msg));
break;
}
pipe(fd);
switch (pid0 = fork())
{
case 0: //Child
switch (pid1 = fork())
{
case 0: //Child
runpipe(fd, cmd1, pparam1, cmd2, pparam2);
exit(0);
default:
exit(0);
//break;
case -1: //ERROR
perror("fork-2");
exit(1);
}
default: //Parent
which = wait(&status);
if (which == -1)
{
write(1, "wait failed\n", 12);
exit(1);
}
if (status & 0xff)
sprintf(msg, "process %d terminated abnormally for reason %d\n", which, status & 0xff);
else
sprintf(msg, "process %d terminated normally with status %d\n", which, (status >> 8) & 0xff);
write(1, msg, strlen(msg));
break;
case -1: //ERROR
perror("fork-1");
exit(1);
}
free(cmd1);
free(cmd2);
for (i = 0; i < MAXWORDS; i++)
free(pparam1[i]);
for (i = 0; i < MAXWORDS; i++)
free(pparam2[i]);
free(path);
for (i = 0; i < MAXWORDS; i++)
free(params[i]);
}
return 0;
}
键入 echo one | wc -l </ em>在提示符下只会输出一个以及相应的wait print语句。我使用C已经有几年了,所以我走在正确的轨道上了吗?
感谢。
编辑: 这是现在的runpipe功能。但唯一印刷的是等待声明。
void runpipe(int pfd[], char *cmd1, char *p1[], char *cmd2, char *p2[])
{
const int READ = 0;
const int WRITE = 1;
int pid;
int status;
switch (pid = fork())
{
case 0: //Child
close(pfd[WRITE]);
dup2(pfd[READ], STDIN_FILENO);
close(pfd[READ]);
execve(cmd2, p2, environ);
perror(cmd2);
default: //Parent
close(pfd[READ]);
dup2(pfd[WRITE], STDOUT_FILENO);
close(pfd[WRITE]);
execve(cmd1, p1, environ);
perror(cmd1);
case -1: //ERROR
perror("fork-RP");
exit(1);
}
}
答案 0 :(得分:1)
有一些事情正在导致意外行为。
首先是你分得太多了。如果您将runpipe()
函数调用展开到main()
中的switch语句,您将看到您达到曾孙级别:
switch (pid0 = fork())
{
case 0: // Child
switch (pid1 = fork())
{
case 0: // GRAND-Child
// function call to runpipe()
switch (pid = fork())
{
case 0: // GREAT-GRAND-Child
close(pfd[WRITE]);
dup2(pfd[READ], STDIN_FILENO);
close(pfd[READ]);
execve(cmd2, p2, environ);
perror(cmd2);
default: // GRAND-Child
close(pfd[READ]);
dup2(pfd[WRITE], STDOUT_FILENO);
close(pfd[WRITE]);
execve(cmd1, p1, environ);
perror(cmd1);
没有必要。在main()
中分叉一次,然后拨打runpipe()
功能。
与此问题相关的是您正在创建管道的位置。 fork时,新创建的子进程继承所有父进程的打开文件(以及许多其他内容)。这包括默认描述符0,1和2(stdin,stdout和stderr),以及任何其他打开的文件,包括您创建的名为fd
的管道。这意味着父,子,孙和曾孙都继承了管道两端的副本。您正确关闭runpipe()
函数(孙子孙女的副本)中未使用的末尾,但main()
函数中的父和子也有副本!
由于使用管道的唯一一组进程是在runpipe()
中创建的进程,因此您可以将fd
的声明和pipe(2)
的调用移动到该函数中。
这两项修改将解决您的问题。
与shell流程相关的一个完全不相关的问题是,main()
最终会在wait(2)
函数的“父”进程中执行runpipe()
。由于该父级是运行cmd1
的父级,因此一旦cmd1
完成,您的shell将返回其提示,而不是在管道中的最后一个命令(在这种情况下为cmd2
)饰面。您可以通过在shell和真正的shell中运行类似echo | sleep 10
的内容来查看行为差异。
答案 1 :(得分:0)
dup
函数复制文件描述符,返回新副本。但是,这不起作用,因为子节点中的stdin
仍然存在,并且新的文件描述符将不代替标准输入。
在执行dup
之前,必须首先关闭标准输入文件描述符。或者使用dup2
,它将在执行复制之前自动关闭目标文件描述符:
dup2(pfd[0], STDIN_FILENO);