C言語で外部コマンドを実行したいときに便利なのがsystem関数です。
シェルコマンドをそのまま呼び出せるため、お手軽に使える一方で、誤った使い方をするとセキュリティリスクも高い関数です。
本記事では、system関数の基本から、変数を使ったコマンド生成方法、安全に使うための注意点まで、コンパイルして試せるサンプルコード付きで解説します。
system関数とは
system関数の概要
system関数は、C言語の標準ライブラリに含まれている外部コマンド実行のための関数です。
C言語のプログラムからシェルを起動し、そのシェル経由でコマンドを実行します。
#include <stdlib.h>
int system(const char *string);
主な特徴
- 引数の文字列を、シェル(多くのUnix系OSでは
/bin/sh)に渡して実行します。 - 戻り値は、実行したコマンドの終了ステータスやエラー情報を表します。
- シェルに依存するため、環境によって挙動が変わる場合があります。

上記イメージのように、プログラムからそのままOSコマンドを直接叩くのではなく、一度シェルを経由して実行するのが重要なポイントです。
system関数を使うためのヘッダファイル
system関数を使うには、#include <stdlib.h>が必要です。
このヘッダファイルには、メモリ確保関数や乱数生成関数などと一緒に、system関数の宣言が含まれています。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// コマンドを実行してみる
int status = system("echo Hello");
printf("status = %d\n", status);
return 0;
}
※ここでは詳しい意味は後の節で解説しますが、戻り値のstatusを確認することが大事だと覚えておいてください。
基本的な使い方
最もシンプルな例
Helloを表示する例
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// WindowsでもUnix系でも動きやすいように "echo" を使用
int ret = system("echo Hello from system");
printf("systemの戻り値: %d\n", ret);
return 0;
}
Hello from system
systemの戻り値: 0 // ただし環境により異なる場合があります
ここでは、シェルのechoコマンドを実行し、その結果が標準出力に表示されます。
system関数自体は、標準出力の内容を返すのではなく、終了ステータスを整数として返すだけです。
OSごとのコマンド違いに注意
system関数に渡す文字列は、OS依存です。
例えば:
- Unix系(Linux、macOSなど):
lsでファイル一覧表示 - Windows:
dirでファイル一覧表示
同じソースコードで動かしたい場合、条件コンパイルを使う方法があります。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int ret;
#ifdef _WIN32
// Windows環境の場合
ret = system("dir");
#else
// Unix系(Linux, macOSなど)の場合
ret = system("ls");
#endif
printf("終了ステータス: %d\n", ret);
return 0;
}
出力例(Unix系で実行した場合の一例):
(カレントディレクトリのファイル一覧)
終了ステータス: 0
このように、コマンド文字列は環境ごとに作り分ける必要があることを意識してください。
変数を使ったコマンド生成
文字列を組み立てて実行する
system関数はconst char *を受け取るため、可変な引数を使いたいときは、事前に文字列を組み立てる必要があります。
もっともよく使われるのがsprintfやsnprintfです。

ファイル名を指定して表示する例
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const char *filename = "test.txt"; // 実際に存在するファイル名を指定
char cmd[256]; // コマンドを組み立てるバッファ
#ifdef _WIN32
// Windows: type コマンドでファイル内容を表示
// snprintfを使うことでバッファオーバーフローを防ぐ
snprintf(cmd, sizeof(cmd), "type %s", filename);
#else
// Unix系: cat コマンドでファイル内容を表示
snprintf(cmd, sizeof(cmd), "cat %s", filename);
#endif
printf("実行するコマンド: %s\n", cmd);
int ret = system(cmd);
printf("systemの戻り値: %d\n", ret);
return 0;
}
出力例(Unix系でtest.txtに「Hello」が入っている場合):
実行するコマンド: cat test.txt
Hello
systemの戻り値: 0
このように、snprintfを使うとバッファサイズを明示でき、オーバーフローを防ぎやすくなるためおすすめです。
数値変数を混ぜたコマンド
変数が整数のときも、フォーマット指定子を使えば簡単にコマンドに埋め込めます。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int count = 5;
char cmd[128];
#ifdef _WIN32
// 単純な例として、cmd.exeに /c echo を渡して回数付きメッセージを表示
snprintf(cmd, sizeof(cmd), "cmd /c echo repeat=%d", count);
#else
// Unix系: /bin/sh -c echo を使って同様の出力
snprintf(cmd, sizeof(cmd), "sh -c 'echo repeat=%d'", count);
#endif
printf("コマンド: %s\n", cmd);
int ret = system(cmd);
printf("終了コード: %d\n", ret);
return 0;
}
コマンド: sh -c 'echo repeat=5'
repeat=5
終了コード: 0
整数・文字列・その他の値を組み合わせて、柔軟にコマンドを生成できる点がsystem関数の利点です。
ただし、後述するようにユーザ入力をそのまま渡すのは危険です。
戻り値とエラー処理
system関数の戻り値の意味
system関数の戻り値は、単なる成功・失敗ではなく、複数の意味を含んだ値です。
環境やコンパイラによって扱いが多少異なりますが、典型的には次のように解釈します。
- -1: system自体の呼び出しに失敗(シェル起動に失敗したなど)
- シェルやコマンドの終了コードをエンコードした値(Unix系では
WIFEXITEDなどのマクロで解析)
POSIX環境ではWEXITSTATUS(status)で実際の終了コードを取り出しますが、ここでは最低限のチェック方法に絞って紹介します。
成功・失敗をチェックする例
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int status;
// 存在するであろうコマンドを実行
status = system("echo OK");
if (status == -1) {
// system自体の失敗
fprintf(stderr, "systemの呼び出しに失敗しました\n");
return 1;
}
// 単純化のため、0なら成功とみなす例
if (status == 0) {
printf("コマンドは成功したとみなします (status=%d)\n", status);
} else {
printf("コマンドがエラーを返した可能性があります (status=%d)\n", status);
}
return 0;
}
OK
コマンドは成功したとみなします (status=0)
実際にはOSごとにステータス値の意味が異なるため、重要な処理ではOS固有のマクロやAPIを使って詳細に解析する必要がある点を覚えておいてください。
system関数の注意点とセキュリティ
なぜ危険と言われるのか
system関数は便利な反面、多くの場面でセキュリティ上のリスクがあります。
理由は以下のような点にあります。
- シェルを経由するため、特殊文字やメタキャラクタが解釈される
- ユーザ入力から作った文字列をそのまま渡すと、コマンドインジェクションの危険がある
- 環境変数(PATH など)の影響を受け、意図しないコマンドが実行される可能性がある

危険な例
以下は典型的に危険な書き方です。
実際に実行しないでください。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char filename[128];
printf("表示したいファイル名を入力してください: ");
if (scanf("%127s", filename) != 1) {
fprintf(stderr, "入力エラー\n");
return 1;
}
char cmd[256];
// 危険: ユーザ入力をそのまま連結
snprintf(cmd, sizeof(cmd), "cat %s", filename);
printf("実行コマンド: %s\n", cmd);
system(cmd); // 入力によりは非常に危険
return 0;
}
ユーザがtest.txt; rm -rf /のような文字列を入力すると、シェルはcat test.txt; rm -rf /と解釈してしまう可能性があります。
ユーザ入力を含むコマンドをsystemで実行するのは、基本的に避けるべきです。
より安全に扱うための考え方
完全に安全にするのは難しいですが、被害を減らす考え方として:
- 可能ならsystemを使わずに、C言語のAPIで直接処理する
(例: ファイルのコピーはfopen/fread/fwriteやCopyFileなどを使う) - どうしてもsystemを使うなら:
- コマンド文字列にユーザ入力を含めない
- 含める場合は、十分なバリデーション(英数字だけ許可するなど)を行う
- 実行するコマンドを限定し、複雑な文字列を受け取らない
systemは「簡単に動く」代わりに「簡単に危険にもなる」関数だと理解して設計することが重要です。
標準ライブラリ他APIとの比較
systemを使うべき場面・避けるべき場面
systemを使うべき典型的な場面は、次のようなケースです。
- デバッグや開発用の簡易ツールで、手早く外部コマンドを試したい
- ビルドスクリプト代わりに、コンパイラやツールチェーンを呼び出す小さなCプログラムを書く
- プロトタイプ段階で、とりあえず動かすことを優先したい
一方で、以下のような場面ではsystemの使用は避けるべきです。
- 長期運用されるサーバプログラムやサービス
- ユーザ入力を多く扱うアプリケーション
- セキュリティ要件が厳しいシステム(社内システム、金融、医療など)
代替手段の例
表形式で、よくある処理と代替手段を比較します。
| やりたいこと | よくある外部コマンド例 | 代替になりうるCの機能・API |
|---|---|---|
| ファイルを読み書きしたい | cat、type | fopen、fread、fwrite、fclose |
| ファイルをコピーしたい | cp、copy | fread/fwriteでコピー、OS固有API |
| ディレクトリ内容を列挙したい | ls、dir | opendir、readdir、FindFirstFileなど |
| 環境変数を参照したい | echo $HOME、echo %PATH% | getenv |
| 別プロセスを起動したい | 任意のプログラム実行 | fork/exec、CreateProcessなど |
「systemで実行していることは、本来CのAPIで書けないか」を一度考えてみると、より安全で移植性の高いコードになりやすくなります。
まとめ
system関数は、C言語からシェルコマンドを直接実行できる便利な関数です。
コマンド文字列を変数で組み立てれば、状況に応じて柔軟な処理も行えます。
一方で、ユーザ入力を含めた文字列を安易に渡すとコマンドインジェクションなどの深刻な問題を招きます。
開発やプロトタイプでは有用ですが、本番コードでは「本当にsystemが必要か」「標準ライブラリやOS固有APIで代替できないか」を検討しつつ、使う場合は戻り値の確認と文字列のバリデーションを徹底することが重要です。
