【C#】外部コマンドを実行する方法をわかりやすく解説

※ この記事にはアフィリエイトリンクが含まれます

外部コマンドをC#から実行したくて悩んでいませんか?

  • Process.Startの使い方がわからない
  • 標準出力や標準エラーの取得方法がわからない
  • 非同期に実行してUIが固まらないようにしたい

結論から言うと、C#ではProcessProcessStartInfoを使い、

リダイレクトや非同期読み取り、エンコーディングに注意すれば安全に実行できます。

  • ProcessStartInfoで設定してからProcessを起動する
  • 標準出力/標準エラーはリダイレクトして取得する
  • 非同期読み取りやキャンセルでUIを保護する

この記事では、初心者向けに基本から実践的な注意点までを順を追って解説します。

目次

なぜC#から外部コマンドを実行するのか

外部コマンド実行は、既存のツールやスクリプトを再利用するために有効です。

ファイル操作や画像処理、システム情報の取得などで便利に使えます。

基本:ProcessとProcessStartInfoの使い方

最も基本的なのはSystem.Diagnostics.Processを使う方法です。

ProcessStartInfoで実行ファイルや引数を設定し、Process.Startで起動します。

簡単な同期実行の例

次はコマンドを同期的に実行する最小例です。出力はコンソールに表示されます。

using System.Diagnostics;

var psi = new ProcessStartInfo{
    FileName = "ipconfig",
    Arguments = "/all",
    UseShellExecute = false,
};

Process.Start(psi).WaitForExit();

注意点としてUseShellExecutefalseにすることで、

標準出力のリダイレクトなどの細かい制御が可能になります。

標準出力を取得する例

外部コマンドの結果をプログラム内で扱うには、標準出力をリダイレクトします。

using System.Diagnostics;

var psi = new ProcessStartInfo{
    FileName = "dotnet",
    Arguments = "--info",
    RedirectStandardOutput = true,
    RedirectStandardError = true,
    UseShellExecute = false,
    CreateNoWindow = true
};

using var proc = Process.Start(psi);
string output = proc.StandardOutput.ReadToEnd();
string error = proc.StandardError.ReadToEnd();
proc.WaitForExit();

Console.WriteLine(output);

この方法は簡単ですが、出力が大きい場合はデッドロックに注意が必要です。

標準出力・標準エラーを同時に同期読みすると停止することがあります。

非同期で安全に実行する方法

UIアプリや長時間実行するコマンドでは非同期実行が推奨です。

async/awaitBeginOutputReadLineなどを使うことで安全に読み取れます。

非同期で出力を逐次読み取る例

以下は出力をイベントで受け取り、UIをブロックしない例です。

using System;
using System.Diagnostics;
using System.Threading.Tasks;

async Task RunCommandAsync(){
    var psi = new ProcessStartInfo{
        FileName = "ping",
        Arguments = "-n 5 127.0.0.1",
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        UseShellExecute = false,
        CreateNoWindow = true
    };

    using var proc = new Process{ StartInfo = psi, EnableRaisingEvents = true };

    proc.OutputDataReceived += (s,e) => { if(e.Data!=null) Console.WriteLine(e.Data); };
    proc.ErrorDataReceived += (s,e) => { if(e.Data!=null) Console.Error.WriteLine(e.Data); };

    proc.Start();
    proc.BeginOutputReadLine();
    proc.BeginErrorReadLine();

    await Task.Run(()=> proc.WaitForExit());
}

BeginOutputReadLineを使うことで、大きな出力でも安全に処理できます。

またTask.Runで待機することでUIスレッドをブロックしません。

引数の扱い方とエスケープ

コマンドライン引数をそのまま連結すると脆弱性や誤動作の原因になります。

引数はArgumentsプロパティに文字列として渡しますが、空白や特殊文字を考慮します。

  • スペースを含む引数は引用符で囲む
  • ユーザー入力は検証する
  • Shellを介在させる実行は注意が必要

例えば引数を組み立てるときは自前で引用するか、配列を扱うラッパーを使いましょう。

// 簡易な引用ヘルパー
string Quote(string arg) => arg.Contains(' ') ? '"'+arg+'"' : arg;

var args = string.Join(' ', Quote(path), Quote(option));
psi.Arguments = args;

プラットフォーム差とシェルの使い分け

WindowsとLinuxで使うシェルやコマンドは異なります。

bashやsh、PowerShellを使う場合はFileNameにシェルを指定します。

// PowerShellを使う例(Windows)
psi.FileName = "powershell";
psi.Arguments = "-NoProfile -Command \"Get-Process | Select-Object -First 5\"";

// bashを使う例(Linux/WSL)
psi.FileName = "/bin/bash";
psi.Arguments = "-c \"ls -la /home\"";

UseShellExecutetrueにするとシェル経由で起動できますが、出力制御は難しくなります。

出力を取得したい場合はUseShellExecute=falseが基本です。

キャンセル処理とタイムアウト

長時間実行されるコマンドにはタイムアウトやキャンセルを用意しましょう。

CancellationTokenでプロセスを強制終了するラッパーを用意すると便利です。

using System.Threading;

async Task<int> RunWithCancellationAsync(ProcessStartInfo psi, CancellationToken ct){
    using var proc = new Process{ StartInfo = psi };
    proc.Start();

    using(ct.Register(()=> { try{ if(!proc.HasExited) proc.Kill(); } catch{} })){
        await Task.Run(()=> proc.WaitForExit());
    }

    return proc.ExitCode;
}
</int>

この方法でユーザー操作に応じてプロセスを終了できます。

ただしKillは強制終了なので、最終手段として使ってください。

セキュリティと注意点

外部コマンド実行にはセキュリティリスクが伴います。

特にユーザー入力をそのまま引数に渡すとコマンドインジェクションに繋がります。

  • 必ず入力検証を行う
  • 可能なら固定コマンドと固定引数を使う
  • 必要な権限の範囲に限定する

またエンコーディングの扱いにも注意が必要です。特に日本語出力がある場合です。

ProcessStartInfo.StandardOutputEncodingを設定して正しい文字列に変換しましょう。

実践的なユーティリティ関数の例

よく使う処理はユーティリティ関数にまとめると再利用できます。

using System.Text;

async Task<(int ExitCode, string StdOut, string StdErr)> RunCommandCaptureAsync(
    string fileName, string args, CancellationToken ct = default){

    var psi = new ProcessStartInfo{
        FileName = fileName,
        Arguments = args,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        UseShellExecute = false,
        CreateNoWindow = true,
        StandardOutputEncoding = Encoding.UTF8,
        StandardErrorEncoding = Encoding.UTF8
    };

    using var proc = new Process{ StartInfo = psi };
    var sbOut = new StringBuilder();
    var sbErr = new StringBuilder();

    proc.OutputDataReceived += (s,e)=> { if(e.Data!=null) sbOut.AppendLine(e.Data); };
    proc.ErrorDataReceived += (s,e)=> { if(e.Data!=null) sbErr.AppendLine(e.Data); };

    proc.Start();
    proc.BeginOutputReadLine();
    proc.BeginErrorReadLine();

    using(ct.Register(()=> { try{ if(!proc.HasExited) proc.Kill(); } catch{} })){
        await Task.Run(()=> proc.WaitForExit());
    }

    return (proc.ExitCode, sbOut.ToString(), sbErr.ToString());
}

この関数は標準出力と標準エラーをUTF-8で取得し、

キャンセルにも対応しています。初心者でも扱いやすい形です。

まとめと次のステップ

ここまででC#から外部コマンドを実行する基本と注意点がわかりました。

ポイントはProcessStartInfoで設定し、出力はリダイレクトして安全に扱うことです。

次のステップは実際のユースケースで試してみることです。

たとえば簡単なツールを呼び出して結果を画面に出すところから始めてください。

この記事があれば、初めてのコマンド実行でも迷わず実装できるはずです。頑張ってください。

#エンジニアとして、もっと自分の力を活かしたいあなたへ

「このまま今の職場にいて、成長できるんだろうか?」

C#を使っている方なら、一度はそう感じたことがあるかもしれません。

実は今、C#/.NETエンジニアの市場価値は高まっており
年収アップ・フルリモート・自社開発企業など、選べる選択肢は確実に増えています。

もし今後、C#を活かして働きたい、あるいは開発現場で経験を積みたいとお考えなら…
自分に合った転職サービスを早めに知っておくことが大きな武器になります。

現役エンジニアからのサポート付き・無料で学べるスクール・社内SE特化の求人など、
転職支援サービスを厳選したまとめ記事をこちらで紹介しています。

ここまで読んでいただきありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

沖縄出身のエンジニアです。IT業界で5年以上の経験があり、主にC#やPHPを使って開発を行ってきました。新しい技術にも興味があり、日々学びながらスキルアップを目指しています。

コメント

コメントする

CAPTCHA


目次