「C言語の基礎 - 外部コマンド」の版間の差分
ページの作成:「== 概要 == 異なるプロセスを生成して外部コマンドプログラムを実行する場合、<code>system</code>関数 (シェルの機能を使用してコマンドを実行) および<code>exec</code>関数ファミリーを使用する。<br> 他にも、<code>popen</code>関数 (パイプで通信可能) 等がある。<br> <br> ただし、これらの関数を実行する時、制御はそのプログラムへ移行するため自身のプログ…」 |
|||
| (同じ利用者による、間の1版が非表示) | |||
| 86行目: | 86行目: | ||
// エラーが発生した場合 | // エラーが発生した場合 | ||
perror(strerror(errno)); | perror(strerror(errno)); | ||
} | |||
return 0; | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== fork関数の使用例 ==== | |||
<code>exec</code>関数ファミリーは制御が戻らないため、メインプロセスの処理を続行させる場合、<code>fork</code>関数により子プロセスを生成して、<code>exec</code>関数ファミリーを実行する。<br> | |||
<syntaxhighlight lang="c"> | |||
#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; | return 0; | ||
} | |||
</syntaxhighlight> | |||
<br><br> | |||
== popen関数 == | |||
<code>popen</code>関数の引数に外部コマンドのパスを渡して実行することができる。<br> | |||
<code>popen</code>関数が返すファイルポインタを読み込むことにより、外部コマンドの出力を取得することができる。<br> | |||
<br> | |||
<code>popen</code>関数の実装は、<code>fork</code>関数、<code>pipe</code>関数、<code>dup2</code>関数、<code>execv</code>関数等のシステムコールを利用して実装されている。<br> | |||
<br> | |||
<syntaxhighlight lang="c"> | |||
#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); | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| 95行目: | 169行目: | ||
__FORCETOC__ | __FORCETOC__ | ||
[[カテゴリ:C | [[カテゴリ:C]] | ||
2024年2月9日 (金) 20:21時点における最新版
概要
異なるプロセスを生成して外部コマンドプログラムを実行する場合、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);
}