概要

異なるプロセスを生成して外部コマンドプログラムを実行する場合、system関数 (シェルの機能を使用してコマンドを実行) およびexec関数ファミリーを使用する。
他にも、popen関数 (パイプで通信可能) 等がある。

ただし、これらの関数を実行する時、制御はそのプログラムへ移行するため自身のプログラムには戻らない。 (実行に失敗した場合だけ戻る)
そのため、処理を続行する場合は、fork関数と組み合わせて使用する必要がある。


system関数

system関数はセキュリティ上の懸念があり、外部からの入力を直接実行することにより、悪意のあるコマンドインジェクション攻撃に対する脆弱性を持つ。 特に、外部からの入力を含む場合、その入力が信頼できるかどうかを確認する。

 #include <stdio.h>
 #include <stdlib.h>
 
 int main()
 {
    // lsコマンドを実行する
    int result = system("ls non_existent_directory");
 
    if (result != 0) {
       fprintf(stderr, "Command execution failed with error code: %d", result);
    }
    else {
       fprintf(stdout, "Command executed successfully");
    }
 
    return 0;
 }



exec関数ファミリー

exec関数ファミリーとは

system関数の代わりに、外部コマンドを実行するためのより安全な方法がexecl関数およびexecv関数である。

exec関数ファミリーは、現在のプロセスを指定したプログラムに置き換えて実行する。
そのため呼び出し元へ制御が戻ることはない。

関数に渡される第1引数は実行されるプログラムのパスである。
第2引数以降は、その実行されるプログラムのパスへ引数を指定する。
第2引数において、execl関数は可変長の引数、execv関数は引数として配列が渡す。

また、exec関数ファミリーの最後の引数はNULLまたはnullptrにする必要があり、(char*)NULLまたは(char*)nullptrとすべきである。

戻り値

エラーが起きた場合のみ呼び出し元に-1を返す。
それ以外の場合は復帰せず、戻り値も存在しない。

execl関数の使用例

 #include <stdio.h>
 #include <unistd.h>
 #include <errno.h>
 
 int main()
 {
    // lsコマンドを実行する
    execl("/bin/ls", "ls", "-l", (char*)nullptr);
 
    if(errno != 0) {
       // エラーが発生した場合
       perror("exec");
    }
 
    return 0;
 }


execv関数の使用例

 #include <stdio.h>
 #include <unistd.h>
 #include <string.h>
 #include <errno.h>
 
 int main()
 {
    const char *str[] = {"/bin/echo", "hoge", "piyo", NULL};
 
    // echoコマンドを実行する
    execv("/bin/echo", str);
 
    if(errno != 0) {
       // エラーが発生した場合
       perror(strerror(errno));
    }
 
    return 0;
 }


fork関数の使用例

exec関数ファミリーは制御が戻らないため、メインプロセスの処理を続行させる場合、fork関数により子プロセスを生成して、exec関数ファミリーを実行する。

 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <sys/wait.h>
 
 int main()
 {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(-1);
    }
    else if (pid == 0) {
        // 子プロセスで外部コマンドを実行する
        execlp("/usr/bin/echo", "echo", "abc", "def", NULL);
        perror("echo");
        exit(-1);
    }
 
    // 親プロセス
    int status = 0;
    pid_t p = waitpid(pid, &status, 0);  // 子プロセスの終了を待つ
    if (p < 0) {
        perror("waitpid");
        exit(-1);
    }
 
    if (WIFEXITED(status)) {
        // 子プロセスが正常終了した場合
        printf("child exit-code=%d\n", WEXITSTATUS(status));
    }
    else {
        printf("child status=%04x\n", status);
    }
 
    return 0;
 }



popen関数

popen関数の引数に外部コマンドのパスを渡して実行することができる。
popen関数が返すファイルポインタを読み込むことにより、外部コマンドの出力を取得することができる。

popen関数の実装は、fork関数、pipe関数、dup2関数、execv関数等のシステムコールを利用して実装されている。

 #include <stdio.h>
 #include <stdlib.h>
 #include <err.h>
 
 int main()
 {
    FILE *fp      = nullptr;
	char buf[256] = {};
	char *pcmd    = "/bin/ls /bin";
 
	if ((fp = popen(pcmd, "r")) == nullptr) {
       err(EXIT_FAILURE, "%s", pcmd);
    }
 
    while(fgets(buf, BUF, fp) != nullptr) {
       (void)fputs(buf, stdout);
    }
 
    (void)pclose(fp);
 
    exit (EXIT_SUCCESS);
 }