Avatar ·

Как выполнить команду и получить её вывод в C++ (POSIX)?

Привет, коллеги! Я только начал осваивать C++ на Linux и столкнулся с задачей: нужно выполнить внешнюю команду (например, `ls -la` или `ping google.com`) и получить её вывод прямо в программе. Пробовал `system()` — она работает, но возвращает только код завершения, а сам вывод выводится в консоль. Мне нужно захватить этот вывод в строку, чтобы потом обработать. Например, я пишу утилиту, которая проверяет, какие процессы запущены. Хочу выполнить `ps aux` и проанализировать результат. Но как это сделать на C++ в стиле POSIX? Слышал про `popen()`, но не уверен, что это лучший способ. Ещё есть `fork()`, `exec()`, `pipe()` — это выглядит сложно для новичка. Может, есть какой-то простой и надёжный метод? Вот мой текущий код, который не работает как надо: ```cpp #include #include int main() { int result = system("ls -la"); std::cout << "Exit code: " << result << std::endl; // А где сам вывод? Он только на экране, а не в переменной. return 0; } ``` Подскажите, как правильно получить вывод команды в C++ с использованием POSIX-функций? Буду благодарен за примеры с пояснениями. <<>>

Использование popen() для захвата вывода команды

Самый простой и эффективный способ получить вывод внешней команды в C++ в среде POSIX — это использовать функцию popen(). Она создаёт канал (pipe), запускает указанную команду и позволяет читать её стандартный вывод (stdout) как обычный файловый поток. В отличие от system(), вы получаете полный контроль над выводом.

Функция popen() принимает два аргумента: команду (строку) и режим ("r" для чтения, "w" для записи). Она возвращает указатель на FILE*, который можно читать с помощью fgets(), fread() или C++-совместимых обёрток. После завершения работы нужно закрыть поток функцией pclose().

Пример: получение вывода команды в строку

Вот полный пример на C++17:

#include <iostream>
#include <memory>
#include <cstdio>
#include <string>
#include <array>

std::string exec(const char* cmd) {
    std::array<char, 128> buffer;
    std::string result;
    // Запускаем команду и открываем поток для чтения
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
    if (!pipe) {
        throw std::runtime_error("popen() failed!");
    }
    // Читаем вывод построчно
    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
        result += buffer.data();
    }
    return result;
}

int main() {
    try {
        std::string output = exec("ls -la");
        std::cout << "Вывод команды:\n" << output << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Ошибка: " << e.what() << std::endl;
        return 1;
    }
    return 0;
}

Объяснение кода

  • popen(cmd, "r") — запускает команду в подоболочке (/bin/sh -c cmd) и подключает её stdout к нашему потоку.
  • std::unique_ptr — RAII-обёртка для автоматического закрытия потока через pclose() при выходе из области видимости. Это защищает от утечек ресурсов.
  • fgets() — читает строку из потока. Мы используем буфер на 128 байт; если строка длиннее, она будет прочитана за несколько итераций.
  • Обработка ошибок: если popen() возвращает nullptr (например, не хватило памяти или команда не найдена), выбрасывается исключение.

Важные замечания

  • Безопасность: popen() использует оболочку /bin/sh. Если команда содержит пользовательский ввод, экранируйте её (например, через execvp() с аргументами, но это сложнее).
  • Код возврата: pclose() возвращает статус завершения команды (аналогично system()). Если нужно проверить успешность, сохраните результат pclose().
  • Производительность: Для больших объёмов данных используйте чтение блоками (fread()) вместо построчного.
  • Альтернативы: В C++17 можно использовать std::filesystem::read_symlink() для простых случаев, но для произвольных команд popen() — стандарт.

Этот подход работает на всех POSIX-системах (Linux, macOS, BSD) и является наиболее простым для 90% задач по запуску команд из C++.

<<>>

Более низкоуровневый подход: fork(), exec() и pipe()

Если вам нужен полный контроль над процессом (например, захват stderr или передача данных в stdin), используйте комбинацию pipe(), fork() и exec(). Это сложнее, но даёт гибкость.

Вот минимальный пример для захвата stdout:

#include <unistd.h>
#include <sys/wait.h>
#include <cstring>
#include <iostream>

std::string exec_fork(const char* cmd) {
    int pipefd[2];
    if (pipe(pipefd) == -1) return "";

    pid_t pid = fork();
    if (pid == 0) { // Дочерний процесс
        close(pipefd[0]); // Закрываем чтение
        dup2(pipefd[1], STDOUT_FILENO); // Перенаправляем stdout в pipe
        close(pipefd[1]);
        execl("/bin/sh", "sh", "-c", cmd, nullptr);
        _exit(1); // Если exec() не удался
    } else if (pid > 0) { // Родительский процесс
        close(pipefd[1]); // Закрываем запись
        char buffer[128];
        std::string result;
        ssize_t count;
        while ((count = read(pipefd[0], buffer, sizeof(buffer)-1)) > 0) {
            buffer[count] = '\0';
            result += buffer;
        }
        close(pipefd[0]);
        waitpid(pid, nullptr, 0); // Ждём завершения
        return result;
    }
    return "";
}

Плюсы: можно захватывать stderr (через второй pipe), передавать данные в stdin, управлять сигналами. Минусы: больше кода, нужно обрабатывать ошибки системных вызовов, риск зомби-процессов при неправильной обработке. Для большинства задач popen() из первого ответа — лучший выбор.