tsurutanのつぶやき

備忘録としてつぶやきます

c言語で簡単なシェルプログラムを作ってみた

f:id:tsurutan:20150524132353j:plain

c言語で簡単なシェルプログラムを作ってみたので順を追って説明していきたいと思います。

今回はsystem関数を使わずにforkとexec,pipeを使い実装してみました。

1. forkを使い子プロセスを作成する

pid_t pid = fork();

 //子1プロセス時に実行
execvp (comm1[0], comm1);
exit(-1);

//親プロセス
wait(&status);

まずfork()を使い親プロセスと子プロセスを作ります。

入力されたコマンドを子プロセスでexecvp()を使い実行します。

その後exit(-1)を使い子プロセスを削除します。

これだけで"ls"コマンドを入力した時に正常な値を返してくれます。

f:id:tsurutan:20150611110801p:plain

しかし、このままでは"ls | wc -l"といった複数のコマンドを受け付けることができません。

そこでpipeを使い子プロセス間で値の共有をしてみましょう。

pipeを使い子プロセス間で値を共有する

int fd[2];
pipe(fd);

pid_t pid = fork();

まずは親プロセスでパイプを作成し、forkします。

//子プロセス1
dup2(fd[1], 1);
close(fd[1]);
execvp (comm1[0], comm1);
exit(-1);

次に子プロセス1で書き込み口fd[1]をdup2で置き換え、不要になった初期のfd[1]をcloseします。

その後、先ほどと同じようにexecvp (comm1[0], comm1)を使いコマンドを実行し子プロセス1を終了させます。

dup2によってこの子プロセス1の出力結果がpipeに流れるので、これを新しく作る子プロセス2で受け取りコマンドを実行させてみましょう。

 //子1プロセスの終了を待つ
 wait(&status);
 close(fd[1]);
 pid_t pid2 = fork();

 //子2プロセス時に実行
 dup2(fd[0], 0);
 execvp (comm2[0], comm2);
 close(fd[0]);
 exit(-1);

 close(fd[0]);

まず、親プロセスでpipeの書き込み口をcloseし、forkします。

その後、子プロセス2でdup2を行い読み込み口fd[0]を置き換えます。これで先ほど書いた子プロセス1の出力結果を受け取ることができるので、ここでexecvp (comm2[0], comm2);を使いコマンドを実行させます。

以上です。これでls | wc -lという複数のコマンドも実行できるようになりました。(複数と書いてありますが、この段階では"|"で区切られた二つのコマンドのみ実行可能です。)

f:id:tsurutan:20150611113203p:plain

全体のコードを載せておきます。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAX_ARGS 50
#define MAX_LEN  500

int main(void)
{
    int argc, n = 0;
    char buf[1024];
    char input[MAX_LEN], *argv[MAX_ARGS], *cp;
    int status;
    const char *delim = " \t\n"; /* コマンドのデリミタ(区切り文字) */
    
    while (1) {
        /* プロンプトの表示 */
        ++n;
        printf("command[%d] ", n);
        
        /* 1行読み込む */
        if (fgets(input, sizeof(input), stdin) == NULL) {
            /* EOF(Ctrl-D)が入力された */
            printf("Goodbye!\n");
            exit(0);
        }
        
        /* コマンド行を空白/タブで分割し,配列 argv[] に格納する */
        cp = input;
        for (argc = 0; argc < MAX_ARGS; argc++) {
            if ((argv[argc] = strtok(cp,delim)) == NULL)
                break;
            cp = NULL;
        }
        
        char *comm1[MAX_ARGS], *comm2[MAX_ARGS];
        int hasComm2 = 0, i = 0, j = 0;
        
        //入力されたコマンドをcomm1,comm2に分割する
        while (argv[i]) {
            if (hasComm2 == 0) {
                if (*argv[i] == '|') {
                    hasComm2 = 1;
                } else {
                    comm1[i] = argv[i];
                    comm1[i + 1] = NULL;
                }
            } else {
                comm2[j] = argv[i];
                comm2[j + 1] = NULL;
                j++;
            }
            i++;
        }
        
        //exitが入力された時に終了する
        if (strcmp(argv[0], "exit") == 0) {
            exit(0);
        }
        
        //"|"が入力された時にパイプを作成
        int fd[2];
        if (hasComm2 == 1) {
            pipe(fd);
        }
        
        pid_t pid = fork();
        
        if (pid < 0) {
            perror("fork");
            exit(-1);
        } else if (pid == 0 && hasComm2 == 0) {
            //子1プロセス時に実行
            execvp (comm1[0], comm1);
            exit(-1);
        } else if (pid == 0 && hasComm2 == 1) {
            //"|"を含んでおり子1プロセス時に実行
            dup2(fd[1], 1);
            close(fd[1]);
            execvp (comm1[0], comm1);
            exit(-1);
        } else if (hasComm2 == 1){
            //子1プロセスの終了を待つ
            wait(&status);
            close(fd[1]);
            pid_t pid2 = fork();
            if (pid2 == 0) {
                //子2プロセス時に実行
                dup2(fd[0], 0);
                execvp (comm2[0], comm2);
                close(fd[0]);
                exit(-1);
            }
            close(fd[0]);
        }
        //子2プロセスの終了を待つ
        wait(&status);
    }
}

新・明解C言語 入門編 (明解シリーズ)

新・明解C言語 入門編 (明解シリーズ)

苦しんで覚えるC言語

苦しんで覚えるC言語