【问题标题】:Shell program with pipes in C在 C 中使用管道的 Shell 程序
【发布时间】:2015-11-25 08:51:24
【问题描述】:

我的管道有问题。我的程序是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;
}

【问题讨论】:

  • 您在 for 循环中执行了两次 fork()(可能是无意的)。你能检查一下吗?
  • @Prasanna 你是对的。我改变了它,但我得到了与我们之前相同的结果。
  • Linux 流水线命令的每个部分(例如cat foo | wc -l)在其自己的 subshel​​l(一个单独的进程)中创建和运行。如果您需要使用fork 来执行此操作,那么您应该查看dupdup2,以便您可以控制将输出从一个进程正确重定向到另一个进程的输入。
  • @Ηλέκτρα Zαραφέτα,您的新代码现在看起来如何?你能用更新的代码编辑问题吗?

标签: c linux shell


【解决方案1】:

通过以下更正更新了您的代码。

  1. 删除了在fork() 调用后迭代两次的for() 循环。
  2. 删除了在 dup2 调用父进程和子进程后不正确关闭管道 FD。
  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) {

【讨论】:

  • 非常感谢!!!你的帮助真的很重要!!我注意到,当我使用管道时,在命令成功执行后(谢谢),shell 停止工作,我回到 linux shell...我认为这是因为您只使用一个 fork,并且在 2 个 execvp(s) 之后没有 shell 进程...我对吗??我会在我的拥有。再次感谢!
  • 没错。如果您希望 while (1) 循环正常工作,您还需要一个 fork。
  • @ΗλέκτραZαραφέτα 如果您喜欢 Prasanna 的回答,您应该接受并给予积分
【解决方案2】:

execv* 过程不解释 shell 脚本字符串。它只是启动一个可执行文件并将一组参数传递给它。因此,它无法组织管道。

如果您需要“正常”的 shell 命令执行,您可能需要使用system(char*) 过程而不是 execvp。

否则,如果您需要自己做管道,您可能需要使用 '|' 解析字符串特殊字符并使用 pipe()、fork() 和 I/O 重定向。喜欢这里How to run a command using pipe?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-25
    相关资源
    最近更新 更多