2016-09-14 23 views
1

私は自分のシェルでパイプ処理しようとする次のプログラムを構築しました。 A StringArrayは単純にchar**です。コードは正常に動作しますが、cat txt.txt | grep aを入力すると、何も画面に印刷されません。デバッグ時に、コードが152(印刷出力コマンドがどこにあるか)のように停止しているように見えました。ここではpid==0i==0です。パイプ機能が正しく実行されていません

コンテキストの場合、パイプが検出された後で別の関数でこの関数を呼び出しています。あなたの大きな問題の

void doPipe(StringArray sa) 
{ 
    printf("In 69\n"); 
    int filedes[2]; // pos. 0 output, pos. 1 input of the pipe 
    int filedes2[2]; 

    int num_cmds = 0; 

    char *command[256]; 

    pid_t pid; 

    int err = -1; 
    int end = 0; 

    // Variables used for the different loops 
    int i = 0; 
    int j = 0; 
    int k = 0; 
    int l = 0; 

    // First we calculate the number of commands (they are separated 
    // by '|') 
    while (sa[l] != NULL){ 
     if (strcmp(sa[l],"|") == 0){ 
      num_cmds++; 
     } 
     l++; 
    } 
    num_cmds++; 

    // Main loop of this method. For each command between '|', the 
    // pipes will be configured and standard input and/or output will 
    // be replaced. Then it will be executed 
    while (sa[j] != NULL && end != 1){ 
     k = 0; 
     // We use an auxiliary array of pointers to store the command 
     // that will be executed on each iteration 
     while (strcmp(sa[j],"|") != 0){ 
      command[k] = sa[j]; 
      j++;  
      if (sa[j] == NULL){ 
       // 'end' variable used to keep the program from entering 
       // again in the loop when no more arguments are found 
       end = 1; 
       k++; 
       break; 
      } 
      k++; 
     } 
     // Last position of the command will be NULL to indicate that 
     // it is its end when we pass it to the exec function 
     command[k] = NULL; 
     j++;   
     printf("In 121\n"); 

     // Depending on whether we are in an iteration or another, we 
     // will set different descriptors for the pipes inputs and 
     // output. This way, a pipe will be shared between each two 
     // iterations, enabling us to connect the inputs and outputs of 
     // the two different commands. 
     if (i % 2 != 0){ 
      pipe(filedes); // for odd i 
     }else{ 
      pipe(filedes2); // for even i 
     } 

     pid=fork(); 

     if(pid==-1){    
      if (i != num_cmds - 1){ 
       if (i % 2 != 0){ 
        close(filedes[1]); // for odd i 
       }else{ 
        close(filedes2[1]); // for even i 
       } 
      }   
      printf("Child process could not be created\n"); 
      return; 
     } 
     if(pid==0){ 
      printf("In 148\n"); 

      // If we are in the first command 
      if (i == 0){ 
       printf("In 152\n"); 

       dup2(filedes2[1], STDOUT_FILENO); 
      } 
      // If we are in the last command, depending on whether it 
      // is placed in an odd or even position, we will replace 
      // the standard input for one pipe or another. The standard 
      // output will be untouched because we want to see the 
      // output in the terminal 
      else if (i == num_cmds - 1){ 
       printf("In 162\n"); 

       if (num_cmds % 2 != 0){ // for odd number of commands 
        dup2(filedes[0],STDIN_FILENO); 
        printf("In 166\n"); 

       }else{ // for even number of commands 
        dup2(filedes2[0],STDIN_FILENO); 
        printf("In 166\n"); 

       } 
      // If we are in a command that is in the middle, we will 
      // have to use two pipes, one for input and another for 
      // output. The position is also important in order to choose 
      // which file descriptor corresponds to each input/output 
      }else{ // for odd i 
       if (i % 2 != 0){ 
        dup2(filedes2[0],STDIN_FILENO); 
        dup2(filedes[1],STDOUT_FILENO); 
       }else{ // for even i 
        dup2(filedes[0],STDIN_FILENO); 
        dup2(filedes2[1],STDOUT_FILENO);      
       } 
      } 

      if (execvp(command[0],command)==err){ 
       kill(getpid(),SIGTERM); 
      }  
     } 

     // CLOSING DESCRIPTORS ON PARENT 
     if (i == 0){ 
      close(filedes2[1]); 
     } 
     else if (i == num_cmds - 1){ 
      if (num_cmds % 2 != 0){     
       close(filedes[0]); 
      }else{     
       close(filedes2[0]); 
      } 
     }else{ 
      if (i % 2 != 0){      
       close(filedes2[0]); 
       close(filedes[1]); 
      }else{     
       close(filedes[0]); 
       close(filedes2[1]); 
      } 
     } 

     waitpid(pid,NULL,0); 

     i++;  
    } 


} 
+1

親は、各子が次の子を倒すのを待つように見えます。それはいくつかのレベルで間違っています。子供のすべて*を始めて、それらのすべてを待つ必要があります。 –

+1

'execvp()'が失敗した場合、なぜ子は自分自身に 'SIGTERM'を送りますか? 'exit()'する方がはるかに簡単です。また、 'execvp()'の戻り値をテストすることもできますが、失敗した場合にのみ返されるため、返す必要はありません。 –

+0

'printf()'というデバッグ用の呼び出しがあり、行番号をハードコードしているようです。デバッグのためにprint文に問題はありませんが、行番号をハードコードする代わりに '__LINE__'マクロを使うことを検討してください。 –

答えて

4

一つは、パイプライン建設の各反復にwaitpidを行うことができます。待機は最後に行われます(リスト内のピッドを覚えておいてください)。

私はあなたのコードを理解するのが難しかったので、私は単純化とクリーンアップをやりました。特に、どこでもif (i % 2 ...)を実行すると、作業が難しくなりました。

コードをクリーンアップして修正しました。

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

typedef struct { 
    int pipe_fildes[2]; 
} pipectl_t; 

#define CLOSEME(_fd) \ 
    do { \ 
     close(_fd); \ 
     _fd = -1; \ 
    } while (0) 

void 
doPipe(char **sa) 
{ 
    pipectl_t pipes[2]; 
    pipectl_t *pipein; 
    pipectl_t *pipeout; 
    pipectl_t *pipetmp; 

    int num_cmds = 0; 

    char *command[256]; 
    pid_t pidlist[256]; 

    pid_t pid; 

    int err = -1; 
    int end = 0; 

    // Variables used for the different loops 
    int icmd = 0; 
    int j = 0; 
    int k = 0; 
    int l = 0; 

    // First we calculate the number of commands (they are separated 
    // by '|') 
    for (int l = 0; sa[l] != NULL; ++l) { 
     if (strcmp(sa[l], "|") == 0) 
      num_cmds++; 
    } 
    num_cmds++; 

    for (int ipipe = 0; ipipe <= 1; ++ipipe) { 
     pipes[ipipe].pipe_fildes[0] = -1; 
     pipes[ipipe].pipe_fildes[1] = -1; 
    } 

    pipein = &pipes[0]; 
    pipeout = &pipes[1]; 

    // Main loop of this method. For each command between '|', the 
    // pipes will be configured and standard input and/or output will 
    // be replaced. Then it will be executed 
    while (sa[j] != NULL && end != 1) { 
     // We use an auxiliary array of pointers to store the command 
     // that will be executed on each iteration 
     k = 0; 
     while (strcmp(sa[j], "|") != 0) { 
      command[k] = sa[j]; 
      j++; 
      k++; 
      if (sa[j] == NULL) { 
       // 'end' variable used to keep the program from entering 
       // again in the loop when no more arguments are found 
       end = 1; 
       break; 
      } 
     } 

     // Last position of the command will be NULL to indicate that 
     // it is its end when we pass it to the exec function 
     command[k] = NULL; 

     j++; 

     // swap input and output, so previous child's output becomes the new 
     // child's input 
     // NOTE: by doing this here, in one place, we eliminate all the i % 2 
     // if statements 
     pipetmp = pipein; 
     pipein = pipeout; 
     pipeout = pipetmp; 

     // are we the last command? 
     int lastflg = (icmd == (num_cmds - 1)); 

     // last command does _not_ have an output pipe, so don't create one 
     if (! lastflg) 
      pipe(pipeout->pipe_fildes); 

     pid = fork(); 

     // NOTE: fork failure almost never happens and is fatal 
     if (pid == -1) { 
      printf("Child process could not be created\n"); 
      return; 
     } 

     // process child 
     if (pid == 0) { 
      // NOTE: after we've dup'ed a file descriptor, we close it 

      // first command does _not_ have a pipe for input 
      if (icmd > 0) 
       dup2(pipein->pipe_fildes[0],STDIN_FILENO); 
      CLOSEME(pipein->pipe_fildes[0]); 

      // last command does _not_ have a pipe for output 
      if (! lastflg) 
       dup2(pipeout->pipe_fildes[1],STDOUT_FILENO); 
      CLOSEME(pipeout->pipe_fildes[1]); 

      // close the parent sides of the pipes (in this child) 

      // close previous child's output descriptor (the feed for our input) 
      CLOSEME(pipein->pipe_fildes[1]); 

      // close next child's input descriptor (our feed for its input) 
      CLOSEME(pipeout->pipe_fildes[0]); 

      if (execvp(command[0], command) == err) { 
#if 0 
       kill(getpid(), SIGTERM); 
#else 
       exit(1); 
#endif 
      } 
     } 

     // close all input descriptors for _this_ child 
     CLOSEME(pipein->pipe_fildes[0]); 
     CLOSEME(pipein->pipe_fildes[1]); 

     // close output side of _this_ child's output pipe [which becomes next 
     // child's input pipe] 
     CLOSEME(pipeout->pipe_fildes[1]); 

     pidlist[icmd] = pid; 

     icmd++; 
    } 

    // wait for all pids _after_ the entire pipeline is constructed 
    for (int icmd = 0; icmd < num_cmds; ++icmd) 
     waitpid(pidlist[icmd], NULL, 0); 
} 

// main -- main program 
int 
main(int argc,char **argv) 
{ 
    char *cp; 
    char *bp; 
    char buf[1000]; 
    char **av; 
    char *avlist[256]; 

    --argc; 
    ++argv; 

    for (; argc > 0; --argc, ++argv) { 
     cp = *argv; 
     if (*cp != '-') 
      break; 

     switch (cp[1]) { 
     default: 
      break; 
     } 
    } 

    while (1) { 
     printf("> "); 
     fflush(stdout); 

     cp = fgets(buf,sizeof(buf),stdin); 
     if (cp == NULL) 
      break; 

     av = avlist; 
     bp = buf; 
     while (1) { 
      cp = strtok(bp," \t\r\n"); 
      bp = NULL; 

      if (cp == NULL) 
       break; 

      *av++ = cp; 
     } 
     *av = NULL; 

     doPipe(avlist); 
    } 

    return 0; 
} 

UPDATE:

私はこのコードを実行し、同じコマンドcat txt.txt | grep a唯一の私は、[無償スタイルのクリーンアップをご容赦ください]管理するために、物事を簡単にするために構造体を追加しましたパイプの後の2番目のコマンドではなく、最初のコマンドを実行するように見えます。 (それはtxtファイルをキャッチしますがgrepはありません)

私は投稿する前にプログラム全体をテストしました。私はちょうどcat/grepコマンドを使って再テストしました。それは機能しましたが、それは私のプログラムは変わりませんでした。

これがどうして起こっているのでしょうか?私は自分のdoPipeメソッドを自分のコードで実装して、String **だけでchar *であるStringArrayを渡しました。

私の提案は以下のとおりです。

  1. は私の変わらないバージョンがあなたのために働くことを確認します。
  2. doPipegdbブレークポイントを使用し、引数を見てください。どちらのプログラムでも、どちらも同じである必要があります。
  3. StringArrayが本当にchar **の場合は、バージョンに置き換えて違いがないことを確認してください。それはvoid doPipe(char **sa)で、あなたのコードがまだコンパイルされているかどうかを確認してください。ブレークポイントでgdbでは、両方のプログラム
  4. StringArrayptype saを行うことができるはずexecvpchar **
  5. を望んでいるので、私は特にここでは、それを避けるだろう:-)私にビットの「Java風」を探しますsaが正しくNULLが終了していることを確認します。それがパイプラインの最後のコマンドでない場合、偽/ゴミとなり、失敗したことのエラーチェックはexecvpではありません。
  6. num_cmdsが同じであることを確認してください。
  7. Try cat txt.txt | grep a | sed -e s/a/b/あなたはcatgrepではなくsedを取得する場合、これはnum_cmdsを意味正しくない
  8. はバッファのその呼び出し側の構文解析は別のトークンで"|"を置くことを確認します。 2 cat txt.txt|grep a

UPDATE番号:

ところで、あなたのパイプのコードがまだ動作していない場合(例えばそれは、このコードはcat txt.txt | grep aで動作しますが、それ意志でない作品であり、最後のコマンドがでない場合が実行されます)、最後のトークンに改行があるかどうかを確認してください(改行が正しく削除されていない)。

私はこれをすべて試しましたが、これで動作するリダイレクションコードを取得できません。基本的に、私はパイプ(例えば|)、このコードでは、私はリダイレクト(例えば<または>)をサポートするために、一般的な解析を行う「<」または「>」

をチェックしなければならない場所へと困惑しています、行ごとに複数のコマンド(例えば;)、埋め込まれたサブシェル(例えば(echo the date is ; date)、およびデタッチジョブ(例えば&)ケアのビットを必要とし、あなたはマルチレベルのアプローチを必要とすることができます。

を私は疑うことあなたがパイプを取得した後、および/またはリダイレクト作業をする場合は、より多くのシェル構文を実装する必要があります。これを前に行っています。あなたがする必要があるものはここにあります。

入力バッファchar-by-charをスキャンし、トークン構造体に型を持つトークン構造体を保存する必要があります。これらの構造体のリンクリストが必要です。もっと詳しくはこちら。 - >ab"c"ab\"c:エスケープ引用符を意識され、>abcから​​:あなたが引用符で囲まれた文字列に遭遇したとき

することは、あなたが引用符を取り除く必要があります。

また、[what が呼び出す]「bareword」という文字列に接する引用符付き文字列には注意が必要です。echo abcabc"d ef"ghiがある場合は、これを単一の文字列トークンに連結する必要があります。abcd efghi

リダイレクタのバックスラッシュも考慮する必要があります。 echo abc > defabcをファイルdefに入れるリダイレクションです。しかし、echo abc \> defはそのままstdoutにabc > defを出力します。他の "句読点"のバックスラッシュも同様です。

また、句読点にはが含まれておらず、その周囲に空白を持つようにする必要があります()。すなわち、echo abc>defは、それがecho abc > defであるかのように扱われなければならない。

また、引用符で囲まれた文字列内の句読点は、上にエスケープされているかのように扱う必要があります。すなわち、echo abc ">" defではなく、リダイレクトであり、[再度]は単純なコマンドとして扱われるべきです。

また、現在の行がバックスラッシュ(たとえば、\<newline>)内ので終わる場合、これは次の行が「継続」行であることを意味します。バックスラッシュと改行を削除する必要があります。次に、別の行を読み、トークンリストを構築し続けます。さらに

&はのように、切り離されたジョブのためになることができますが:gcc -o myshell myshell.c 2>&1 >logfile

オーケーで、そのすべてを管理するために、我々は、トークンとトークンの種類を必要とするよう、date &、それはまた、リダイレクトの一部にすることができます構造体:

当初
// token types 
typedef enum { 
    TOKEN_NORMAL,      // simple token/string 
    TOKEN_QUO1,       // quoted string 
    TOKEN_QUO2,       // quoted string 
    TOKEN_SEMI,       // command separater (e.g. ;) 
    TOKEN_OREDIR,      // output redirector (e.g. >) 
    TOKEN_IREDIR,      // input redirector (e.g. <) 
    TOKEN_PIPE,       // pipe separater (e.g. |) 
    TOKEN_AMP       // an & (can be detach or redirect) 
} toktype_t; 

// token control 
typedef struct token token_t; 
struct token { 
    token_t *tok_next;     // forward link 
    token_t *tok_prev;     // backward link 
    toktype_t tok_type;     // token type 
    char tok_str[256];     // token value 
}; 

// token list 
typedef struct tlist tlist_t; 
struct token { 
    tlist_t *tlist_next;    // forward link 
    tlist_t *tlist_prev;    // backward link 

    token_t *tlist_head;    // pointer to list head 
    token_t *tlist_tail;    // pointer to list tail 
}; 

は、[継続を意識さ]入力行を解析した後、我々は、単一tlistを持っています。

リストに;の区切り文字がある場合は、分割してサブリストを作成します。次に、サブリストをループし、順番にコマンドを実行します。

サブコマンドを見るときに、それが&で終わる場合は、コマンドを分離して実行する必要があります。我々はそれをメモし、リストの裏側にそれをポップします。

さて、今私たちは、フォームのかもしれないリストがあります:

cat < /etc/passwd 
grep root 
sed -e s/root/admin/ > /tmp/out 
:今

cat < /etc/passwd | grep root | sed -e s/root/admin/ > /tmp/out 

を、私たちは、私たちは三つの要素を持つリストを持っている|にさらに分割を行います

実際には、これらの「行」のそれぞれがtlistであり、これはリストの2次元のリストである:

list_of_tlists: 
    | 
    | 
tlist[0] --> cat --> < --> /etc/passwd 
    | 
    | 
tlist[1] --> grep --> root 
    | 
    | 
tlist[2] --> sed --> -e --> s/root/admin/ --> > /tmp/out 

パイプラインを作成するときに、リダイレクトに注意し、必要に応じてpipeの代わりにopenをファイルします。

これは要約です。

ここで私の答えをご覧ください:Implementing input/output redirection in a Linux shell using C完全かつ完全な実装です。

リダイレクトを行うためのコードがあります。おそらく私がここに投稿したコードとそのコードをマージすることによってパイプを含めるようにすることができます。

そのOPはリダイレクトを行う際に助けを求めましたパイプ。

サイドノート:そのとき、シェルの実装の質問が多かった。だから、私はほとんどすべてを行う完全なシェルを作り上げることになりました。しかし、そのバージョンはあまりにも大きく、SOに投稿できませんでした。だから、そのページで、私が掲示したペーストビンのリンクを見つけてください。それは完全なソースコードを持っています。それは、ダウンロード、構築、実行することができます。

そのコードを直接使用したくないかもしれませんが、いくつか考えてください。また、フルバージョンは、私が上で説明したものとは少し違ったやり方をするかもしれません。

+0

このコードを実行すると、同じコマンド "cat txt.txt | grep a"だけが最初のコマンドを実行するように見え、パイプの後の2番目のコマンドは表示されません。 (それはtxtファイルをキャッチしますが、grepはしません)なぜこれが起こっているのでしょうか?私は自分のdoPipeメソッドを自分のコードで実装して、String **だけでchar *であるStringArrayを渡しました。 – rsa

+0

私はこれをすべて試しましたが、リダイレクションコードがこれで動作するようにはできません。基本的には、このコードのどこで「<' or '>」をチェックするべきか混乱しています。次に、 を追加してください(if((fd0 = open(input、O_RDONLY、0))<0){ 出口(0); } dup2(fd0、0); 閉じる(fd0); } if(out) { int fd00; if((fd00 = creat(出力、0644))<0){ exit(0); } dup2(fd00、STDOUT_FILENO); 閉じる(fd00); } – rsa

関連する問題