C++の基礎 - 外部コマンド

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
ナビゲーションに移動 検索に移動

概要

標準ライブラリのsystem関数、POSIXシステムのpopen関数、Windowsでは_popen関数を使用して外部コマンドを実行することができる。

モダンなアプローチとしては、Boost.Processライブラリの使用が推奨される。
これは、プラットフォームに依存しない統一的なインターフェースを提供しており、同期・非同期の実行、標準入出力のリダイレクト、環境変数の制御、プロセス間通信等、高度な機能をサポートしている。

パフォーマンスにおいては、system関数は新しいシェルプロセスを起動するため、オーバーヘッドが大きくなる傾向がある。
popen関数やBoost.Processライブラリは直接プロセスを制御できるため、より効率的な実行が可能である。

クロスプラットフォーム開発においては、条件コンパイルを使用して適切な実装を選択、あるいは、Boost.Processライブラリのような抽象化層を利用することで、
プラットフォーム間の差異を吸収することができる。

また、C++ 17以降では、std::filesystemライブラリを使用することにより、ファイルシステム操作に関連する多くのタスクを外部コマンドを使用せずに実行することが可能である。
これにより、よりセキュアで効率的なコードを記述することができる。

※注意
セキュリティにおいて、外部コマンドを実行する場合はユーザ入力を直接使用することは避けるべきである。
もし使用する場合は、必ず適切な入力検証とサニタイズを行う必要がある。
実行するコマンドのパスを完全修飾パスで指定することにより、PATHインジェクション攻撃のリスクを軽減できる。

また、コマンドインジェクション攻撃のリスクにも注意が必要である。


system関数

最も一般的なアプローチは、system関数を使用することである。
これは、cstdlibヘッダに含まれており、シンプルな実装が可能である。

 #include <cstdlib>
 
 int main()
 {
 #ifdef _WIN32
    system("dir");    // Windows
 #else
    system("ls -l");  // Linux
 #endif
 
    return 0;
 }


しかし、system関数には重要な制限がある。
それは、コマンドの出力を取得できないことやセキュリティ上のリスクが存在することである。


popen関数 / _popen関数

より高度な制御が必要な場合は、popen関数 (POSIXシステム)、または、_popen関数 (Windows) を使用できる。

 #include <cstdio>
 #include <cstring>
 
 int main()
 {
    FILE* pipe = popen("ls -l", "r");
    if (!pipe) return 1;
 
    char buffer[128];
    std::string result = "";
 
    while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
       result += buffer;
    }
 
    pclose(pipe);
 }



std::filesystemクラス

最新のC++では、より安全で柔軟な方法として、std::filesystem (C++17以降) を使用することもできる。
これは直接的な外部コマンド実行ではないが、ファイルシステム操作に関しては優れた選択肢である。


Boost.Process

より高度な機能が必要な場合は、Boost.Process等のサードパーティ製ライブラリを使用することができる。
これは、非同期実行やプロセス間通信等の機能を提供している。

Boost.Processを使用するには、BoostのSystemおよびFileSystemをインストールする必要がある。

# RHEL
sudo dnf install boost1.78-system boost1.78-filesystem

# SUSE
sudo zypper install libboost_system1_75_0-devel libboost_filesystem1_75_0-devel


同期実行

 #include <iostream>
 #include <string>
 #include <vector>
 #include <boost/process.hpp>
 
 // 同期実行
 void run_sync_command()
 {
    // 出力を格納する変数
    std::string output;
 
    // パイプを使用してコマンドを実行して、出力を取得
    boost::process::ipstream pipe_stream;
    boost::process::child c("/usr/bin/ls", boost::process::args({"-l"}), boost::process::std_out > pipe_stream);
 
    // 出力を1行ずつ読み取り
    std::string line;
    while (pipe_stream && std::getline(pipe_stream, line)) {
       output += line + "\n";
    }
 
    // プロセスの終了を待機
    c.wait();
 
    std::cout << "Exit Code: " << c.exit_code() << std::endl;
    std::cout << "Output: " << output << std::endl;
 }
 
 int main()
 {
    try {
       run_sync_command();
    }
    catch (const std::exception &e) {
       std::cerr << "Error: " << e.what() << std::endl;
       return -1;
    }
 
    return 0;
 }


非同期実行

 #include <iostream>
 #include <string>
 #include <vector>
 #include <boost/process.hpp>
 #include <boost/process/async.hpp>
 #include <boost/asio.hpp>

 // 非同期実行
 void run_async_command()
 {
    // boost::asio::io_contextの作成
    boost::asio::io_context io_context;
 
    // 出力を格納する変数
    std::string output;
 
    // パイプを使用してコマンドを実行
    boost::process::ipstream pipe_stream;
    boost::process::child c(
       boost::process::search_path("ls"),
       boost::process::args({"-l"}),
       boost::process::std_out > pipe_stream,
       io_context
    );
 
    // 非同期読み取り用のバッファ
    std::vector<char> buf(4096);
 
    // 非同期読み取りの開始
    boost::asio::async_read(
       pipe_stream,
       boost::asio::buffer(buf),
       [&](const boost::system::error_code& ec, std::size_t size) {
          if (!ec) {
             output.append(buf.begin(), buf.begin() + size);
          }
       }
    );
 
    // 非同期処理の実行
    io_context.run();
 
    // プロセスの終了を待機
    c.wait();
 
    std::cout << "Exit Code: " << c.exit_code() << std::endl;
    std::cout << "Output: " << output << std::endl;
 }
 
 int main()
 {
    try {
       // 非同期実行
       run_async_command();
    }
    catch (const std::exception &e) {
       std::cerr << "Error: " << e.what() << std::endl;
       return -1;
    }
 }


プロセス間通信

Boost.Processを使用してプロセス間通信を行うことが可能である。
子プロセスの標準入力、標準出力、標準エラー出力を通じて通信を行うことができる。

以下の例では、双方向通信 (親プロセスと子プロセス間で双方向のメッセージ交換)、および、非同期I/O (子プロセスからの出力を非同期で読み取る) を行っている。

 #include <iostream>
 #include <string>
 #include <thread>
 #include <chrono>
 #include <boost/process.hpp>
 #include <boost/asio.hpp>
 
 // 親プロセス
 class ProcessCommunicator
 {
 private:
    boost::asio::io_context io_context;
    boost::process::opstream child_stdin;
    boost::process::ipstream child_stdout;
    boost::process::child child_process;
    bool running;
 
 public:
    ProcessCommunicator() : running(false)
    {}
 
    // プロセスの起動
    bool start(const std::string& command)
    {
       try {
          child_process = boost::process::child(
             command,
             boost::process::std_out > child_stdout,
             boost::process::std_in < child_stdin,
             io_context
          );
          running = true;
          return true;
       }
       catch (const std::exception &e) {
          std::cerr << "Error starting process: " << e.what() << std::endl;
          return false;
       }
    }
 
    // 子プロセスにメッセージを送信
    void send_message(const std::string &message)
    {
       if (!running) return;
 
       child_stdin << message << std::endl;
       child_stdin.flush();
    }
 
    // 子プロセスからの出力を読み取る
    void read_output()
    {
       std::string line;
       while (running && std::getline(child_stdout, line)) {
          std::cout << "Parent received: " << line << std::endl;
       }
    }
 
    // プロセスの終了
    void stop()
    {
       if (!running) return;
 
       send_message("exit");
       child_process.wait();
       running = false;
    }
 
    // プロセスが実行中かどうかを確認
    bool is_running() const
    {
       return running && child_process.running();
    }
 
    // デストラクタ
    ~ProcessCommunicator()
    {
       if (running) stop();
    }
 };
 
 int main()
 {
    ProcessCommunicator communicator;
 
    // 子プロセスを起動
    if (!communicator.start("./Child")) {
       std::cerr << "Failed to start child process" << std::endl;
       return -1;
    }
 
    // 出力読み取り用のスレッドを起動
    std::thread output_thread([&communicator]() {
       communicator.read_output();
    });
 
    // メインループ : ユーザ入力を子プロセスに送信
    std::string input;
    std::cout << "Enter messages to send to child process (type 'exit' to quit):" << std::endl;
 
    while (std::getline(std::cin, input)) {
       if (input == "exit") break;
       communicator.send_message(input);
    }
 
    // プロセスを終了して、スレッドを待機
    communicator.stop();
    output_thread.join();
 
    return 0;
 }


 // Child.cppファイル (子プロセスとして実行するプログラム)
 #include <iostream>
 #include <string>
 
 int main()
 {
    std::string line;
    while (std::getline(std::cin, line)) {
       if (line == "exit") {
          std::cout << "Child process exiting..." << std::endl;
          break;
       }
       std::cout << "Child received: " << line << std::endl;
       std::cout << "Child processed: " << line << " (processed)" << std::endl;
    }
 
    return 0;
 }