C语言中使用管道的Shell程序

4

我在管道方面遇到了问题。我的程序是用C语言编写的Shell程序。我想执行例如ls | wc,但运行后得到的是:

ls: 无法访问 |: 没有那个文件或目录 ls: 无法访问 wc: 没有那个文件或目录。

我做错了什么?

#include <stdlib.h>   
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>

#define MAX_CMD_LENGTH 100

#define MAX_NUM_PARAMS 10

int parsecmd(char* cmd, char** params) { //split cmd into array of params
    int i,n=-1;
    for(i=0; i<MAX_NUM_PARAMS; i++) {
        params[i] = strsep(&cmd, " ");
        n++;
        if(params[i] == NULL) break;
    }
    return(n);
};

int executecmd(char** params) {
    pid_t pid = fork(); //fork process

    if (pid == -1) { //error
        char *error = strerror(errno);
        printf("error fork!!\n");
        return 1;
    } else if (pid == 0) { // child process
        execvp(params[0], params); //exec cmd
        char *error = strerror(errno);
        printf("unknown command\n");
        return 0;
    } else { // parent process
        int childstatus;
        waitpid(pid, &childstatus, 0);    
        return 1;
    }
};

int execpipe (char ** argv1, char ** argv2) {
    int fds[2];
    pipe(fds);
    int i;
    pid_t pid = fork();
    for (i=0; i<2; i++) {
        if (pid == -1) { //error
            char *error = strerror(errno);
            printf("error fork!!\n");
            return 1;
         } else
             if (pid == 0) {
                 if(i ==0){
                     close(fds[1]);
                     dup2(fds[0], 0);
                     close(fds[0]);
                     execvp(argv1[0], argv1);
                     char *error = strerror(errno);
                     printf("unknown command\n");
                     return 0;
                 } else if(i == 1) { 
                     close(fds[0]);
                     dup2(fds[1], 1);
                     close(fds[1]);
                     execvp(argv2[0], argv2);
                     char *error = strerror(errno);
                     printf("unknown command\n");
                     return 0;
                 }
            } else { // parent process
                int childstatus;
                waitpid(pid, &childstatus, 0);
                return 1;
            }
    } // end for
};


int main() {    
    char cmd[MAX_CMD_LENGTH+1];    
    char * params[MAX_NUM_PARAMS+1];    
    char * argv1[MAX_NUM_PARAMS+1];    
    char * argv2[MAX_NUM_PARAMS+1];    
    int k, y, x;    
    int f = 1;    
    while(1) {
        printf("$"); //prompt    
        if(fgets(cmd, sizeof(cmd), stdin) == NULL) break; //read command, ctrl+D exit       
        if(cmd[strlen(cmd)-1] == '\n') { //remove newline char    
            cmd[strlen(cmd)-1] = '\0';    
        }    
        int j=parsecmd(cmd, params); //split cmd into array of params           
        if (strcmp(params[0], "exit") == 0) break; //exit   
        for (k=0; k <j; k++) { //elegxos gia uparksi pipes    
            if (strcmp(params[k], "|") == 0) {    
                f = 0; y = k;      
               printf("pipe found\n");
            }               
        }
        if (f==0) {
            for (x=0; x<k; x++) {    
               argv1[x]=params[x];
            }     
            int z = 0;     
            for (x=k+1; x< j; x++) {     
                argv2[z]=params[x];
                z++;
            }     
            if (execpipe(argv1, argv2) == 0) break;    
         } else if (f==1) {     
             if (executecmd(params) == 0) break;
         }
    } // end while
    return 0;
}

1
你在for循环内部执行了两次fork()(可能是无意中)。你能检查一下吗? - Prasanna
@Prasanna 你是对的。我已经改了,但是结果和之前一样。 - Ηλέκτρα Ζαραφέτα
5
Linux管道命令的每个部分(例如cat foo | wc -l)都会在自己的子shell(单独的进程)中创建和运行。如果需要使用fork来完成这项任务,则应该查看dupdup2,以便您可以控制正确的重定向输出到另一个进程的输入。 - David C. Rankin
@Ηλέκτρα Ζαραφέτα,你的新代码现在看起来怎么样?你能否编辑问题并更新代码? - Prasanna
2个回答

6

根据以下更正更新了您的代码。

  1. 删除了在 fork() 调用之后迭代两次的 for() 循环。
  2. 删除了父进程和子进程中 dup2 调用后错误关闭管道 FDs 的操作。
  3. 根据在 dup2() 调用中复制的文件描述符对齐需要运行的命令。基本上我需要交换 execvp(argv2[0], argv2)execvp(argv1[0], argv1) 调用。
  4. 在搜索管道字符的 for 循环中添加了一个 break; 语句。

更新后的代码如下。

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

#define MAX_CMD_LENGTH 100

#define MAX_NUM_PARAMS 10

int parsecmd(char* cmd, char** params) { //split cmd into array of params
    int i,n=-1;
    for(i=0; i<MAX_NUM_PARAMS; i++) {
        params[i] = strsep(&cmd, " ");
        n++;
        if(params[i] == NULL) break;
    }
    return(n);
};

int executecmd(char** params) {
    pid_t pid = fork(); //fork process

    if (pid == -1) { //error
        char *error = strerror(errno);
        printf("error fork!!\n");
        return 1;
    } else if (pid == 0) { // child process
        execvp(params[0], params); //exec cmd
        char *error = strerror(errno);
        printf("unknown command\n");
        return 0;
    } else { // parent process
        int childstatus;
        waitpid(pid, &childstatus, 0);    
        return 1;
    }
};

int execpipe (char ** argv1, char ** argv2) {
    int fds[2];
    pipe(fds);
    int i;
    pid_t pid = fork();
    if (pid == -1) { //error
        char *error = strerror(errno);
        printf("error fork!!\n");
        return 1;
    } 
    if (pid == 0) { // child process
        close(fds[1]);
        dup2(fds[0], 0);
        //close(fds[0]);
        execvp(argv2[0], argv2); // run command AFTER pipe character in userinput
        char *error = strerror(errno);
        printf("unknown command\n");
        return 0;
    } else { // parent process
        close(fds[0]);
        dup2(fds[1], 1);
        //close(fds[1]);
        execvp(argv1[0], argv1); // run command BEFORE pipe character in userinput
        char *error = strerror(errno);
        printf("unknown command\n");
        return 0;
    }
};


int main() {    
    char cmd[MAX_CMD_LENGTH+1];    
    char * params[MAX_NUM_PARAMS+1];    
    char * argv1[MAX_NUM_PARAMS+1] = {0};    
    char * argv2[MAX_NUM_PARAMS+1] = {0};    
    int k, y, x;    
    int f = 1;    
    while(1) {
        printf("$"); //prompt    
        if(fgets(cmd, sizeof(cmd), stdin) == NULL) break; //read command, ctrl+D exit       
        if(cmd[strlen(cmd)-1] == '\n') { //remove newline char    
            cmd[strlen(cmd)-1] = '\0';    
        }    
        int j=parsecmd(cmd, params); //split cmd into array of params           
        if (strcmp(params[0], "exit") == 0) break; //exit   
        for (k=0; k <j; k++) { //elegxos gia uparksi pipes    
            if (strcmp(params[k], "|") == 0) {    
                f = 0; y = k;      
               printf("pipe found\n");
               break;
            }               
        }
        if (f==0) {
            for (x=0; x<k; x++) {    
               argv1[x]=params[x];
            }     
            int z = 0;     
            for (x=k+1; x< j; x++) {     
                argv2[z]=params[x];
                z++;
            }     
            if (execpipe(argv1, argv2) == 0) break;    
         } else if (f==1) {     
             if (executecmd(params) == 0) break;
         }
    } // end while
    return 0;
}

如果您只对我所做的更改感兴趣,以下是您的代码和上述更新代码之间的差异:

--- original.c
+++ updated.c
@@ -4,6 +4,7 @@
 #include <unistd.h>
 #include <errno.h>
 #include <sys/types.h>
+#include <sys/wait.h>

 #define MAX_CMD_LENGTH 100

@@ -43,44 +44,36 @@
     pipe(fds);
     int i;
     pid_t pid = fork();
-    for (i=0; i<2; i++) {
         if (pid == -1) { //error
             char *error = strerror(errno);
             printf("error fork!!\n");
             return 1;
-         } else
-             if (pid == 0) {
-                 if(i ==0){
+    } 
+    if (pid == 0) { // child process
                      close(fds[1]);
                      dup2(fds[0], 0);
-                     close(fds[0]);
-                     execvp(argv1[0], argv1);
+        //close(fds[0]);
+        execvp(argv2[0], argv2); // run command AFTER pipe character in userinput
                      char *error = strerror(errno);
                      printf("unknown command\n");
                      return 0;
-                 } else if(i == 1) { 
+    } else { // parent process
                      close(fds[0]);
                      dup2(fds[1], 1);
-                     close(fds[1]);
-                     execvp(argv2[0], argv2);
+        //close(fds[1]);
+        execvp(argv1[0], argv1); // run command BEFORE pipe character in userinput
                      char *error = strerror(errno);
                      printf("unknown command\n");
                      return 0;
                  }
-            } else { // parent process
-                int childstatus;
-                waitpid(pid, &childstatus, 0);
-                return 1;
-            }
-    } // end for
 };


 int main() {    
     char cmd[MAX_CMD_LENGTH+1];    
     char * params[MAX_NUM_PARAMS+1];    
-    char * argv1[MAX_NUM_PARAMS+1];    
-    char * argv2[MAX_NUM_PARAMS+1];    
+    char * argv1[MAX_NUM_PARAMS+1] = {0};    
+    char * argv2[MAX_NUM_PARAMS+1] = {0};    
     int k, y, x;    
     int f = 1;    
     while(1) {
@@ -95,6 +88,7 @@
             if (strcmp(params[k], "|") == 0) {    
                 f = 0; y = k;      
                printf("pipe found\n");
+               break;
             }               
         }
         if (f==0) {

1
非常感谢!!!你的帮助真的很重要!!我注意到当我使用管道时,在命令成功执行后(感谢您),shell 停止工作,我回到了 Linux shell... 我认为这是因为你只使用了一个 fork,在 2 个 execvp(s) 之后没有 shell 进程了... 我是对的吗?我会自己解决这个问题。再次感谢你!! - Ηλέκτρα Ζαραφέτα
1
没错。如果你想让 while (1) 循环正常工作,你需要再多一个 fork。 - Prasanna
@ΗλέκτραΖαραφέτα 如果你喜欢Prasanna的回答,你应该接受它并给予积分。 - Ahmed Masud

2
execv*过程不解释shell脚本字符串,它只是启动一个可执行文件并将参数数组传递给它。因此,它无法组织管道。
如果您需要“正常”的shell命令执行,可能希望使用system(char*)过程而不是execvp。
否则,如果您需要自己处理管道,则可能希望使用'|'特殊字符解析字符串,并使用pipe(),fork()和I/O重定向。像这里如何使用管道运行命令?

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