1

私は大学の課題のためにミニシェルに取り組んでいます。コマンドを読み込み、パス var から実行するバイナリを見つけ、パイプを使用する場合と使用しない場合の両方でコマンドを実行する必要があります。パイプを除いて、すべてが機能しています(と思います)。Web 検索を通じて、ハード コードされた 2 つのコマンドを使用し、1 つのコマンドを別のコマンドにパイプするテスト プログラムを作成して、期待どおりの結果を得ることができました。そのコードをコピーして実際のプログラムに貼り付けると、最初のコマンドは正常に出力されますが (実際にはパイプがないかのようにコマンドを出力します)、2 番目のコマンドは実際には何もしないと思います (最初のコマンドの出力は2番目にパイプスルーされます)。

コード全体は次のとおりです。

#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;   
    }

入力エコー 1 | プロンプトでwc -lを実行すると、それぞれの wait print ステートメントが続く1つのみが出力されます。C を使用してから数年が経ちましたが、正しい軌道に乗っていますか?

ありがとう。

編集: これが現在の runpipe 関数です。しかし、表示されるのは wait ステートメントだけです。

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);
    }
}
4

2 に答える 2

1

予期しない動作の原因となっていることがいくつかあります。

1 つ目は、分岐しすぎていることです。runpipe()関数呼び出しを の switch ステートメントに展開するとmain()、ひ孫レベルに到達することがわかります。

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()

この問題に関連するのは、パイプを作成する場所です。フォークすると、新しく作成された子プロセスは、親プロセスの開いているすべてのファイルを継承します (他の多くのものの中でも)。これには、デフォルトの記述子 0、1、および 2 (stdin、stdout、および stderr) と、作成したパイプを含む他の開いているファイルが含まれますfd。これは、親、子、孫、およびひ孫がすべて、パイプの両端のコピーを継承していることを意味します。runpipe()関数内の未使用の端 (孫とひ孫のコピー) を正しく閉じますが、関数内の親と子にmain()もコピーがあります!

パイプを使用するプロセスのペアは で作成されたものだけなので、の宣言と呼び出しをその関数runpipe()に移動できます。fdpipe(2)

これら 2 つの変更により、問題が解決されます。

シェルのフローに関連するまったく無関係の問題は、関数の「親」プロセスでそれを実行することになりmain()ます。その親は を実行しているため、シェルは、パイプラインの最後のコマンド (この場合) が終了したときではなく、終了するとすぐにプロンプ​​トを返します。シェルと実際のシェルで何かを実行すると、動作の違いを確認できます。wait(2)runpipe()cmd1cmd1cmd2echo | sleep 10

于 2013-03-04T18:39:32.050 に答える
0

このdup関数はファイル記述子を複製し、新しい複製を返します。ただし、stdin子がまだ存在するため、これは機能しません。新しいファイル記述子は標準入力の代わりに配置されません。

を実行する前に、まず標準入力ファイル記述子を閉じる必要がありますdup。またはdup2、複製を行う前に、宛先ファイル記述子を最初に自動的に閉じる which を使用します。

dup2(pfd[0], STDIN_FILENO);
于 2013-03-03T21:10:54.020 に答える