Учитывая следующий ответ (первый ответ С ++ 11):
Как выполнить команду и получить вывод команды в C++ с помощью POSIX?
Вот реализация для вашего удобства:
#include <cstdio>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <array>
std::string exec(const char* cmd) {
std::array<char, 128> buffer;
std::string result;
std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose);
if (!pipe) throw std::runtime_error("popen() failed!");
while (!feof(pipe.get())) {
if (fgets(buffer.data(), 128, pipe.get()) != nullptr)
result += buffer.data();
}
return result;
}
Это действительно хорошо работает для выполнения команды (например, std::string res = exec("ls");
) и преобразования стандартного вывода в строку.
Но чего он не делает, так это получения кода возврата команды (целое число успешно / неуспешно) или stderr. В идеале я хотел бы получить все три (код возврата, stdout, stderr).
Я бы согласился на stdout и stderr. Я думаю, что мне нужно добавить еще один канал, но я действительно не вижу, как первый канал настроен для получения стандартного вывода, поэтому я не могу подумать, как бы его изменить, чтобы получить оба.
У кого-нибудь есть идеи, как это сделать, или альтернативные подходы, которые могут сработать?
Обновить
См. Мой полный пример здесь с выводом:
Start
1 res: /home
2 res: stdout
stderr
3 res:
End
Вы можете видеть, что 3 res:
не печатает stderr так же, как 2 res: stdout
, но stderr просто выгружается на экран в отдельной строке процессом (а не моей программой).
Внешние библиотеки
Я действительно не хочу использовать внешние библиотеки, такие как Qt и boost - в основном потому, что мне нужна их переносимость, а также многие проекты, над которыми я работаю, не используют boost. Однако я отмечу решения, которые содержат эти параметры, поскольку они действительны для других пользователей :)
Полное решение с использованием комментариев / ответа
Спасибо всем за ваши ответы / комментарии, вот модифицированное решение (и работоспособное):
@ user1810087 хм ... нет, я бы предпочел оставить его в чистом виде на C++ / C++ 11. В моем конкретном проекте мы решили не использовать библиотеки boost.
Есть какое-то обходное решение. Вы можете перенаправить stderr на stdout, добавив «2> & 1» к вашему cmd. Подойдет ли это вашим потребностям?
@Alex, черт возьми ... это умно, я все время использую это в сценариях bash, но не думал использовать его здесь! ... если это сработает, то да:) .. Я бы все равно предложил сделать версию на C++ , но это хорошая идея, сэр ... тестирование сейчас ...
В идеале ... posix в порядке, так как я использую именно его. В моем реальном коде у меня есть незначительное отклонение от кода Windows (msvs использует _popen
вместо popen
), и у меня есть #define для этого ... так что у меня уже есть некоторые специфические вещи для ОС ... которых трудно полностью избежать :(
Вы должны прочитать книгу Продвинутое программирование Linux или что-нибудь новее. Для ответа может потребоваться целая книга. Рассмотрим также библиотеки POCO. Вам может понадобиться некоторый цикл событий (или библиотека) вокруг опрос (2). См. Также системные вызовы (2)
@Alex, который отлично сработал - пожалуйста, добавьте свой комментарий в качестве ответа, и я проголосую за него
Рад знать, спасибо! Добавлен
На странице руководства popen
:
The pclose() function waits for the associated process to terminate and returns the exit status of the command as returned by wait4(2).
Таким образом, вызов pclose()
самостоятельно (вместо использования магии деструктора std::shared_ptr<>
) даст вам код возврата вашего процесса (или блокировку, если процесс еще не завершен).
std::string exec(const char* cmd) {
std::array<char, 128> buffer;
std::string result;
auto pipe = popen(cmd, "r"); // get rid of shared_ptr
if (!pipe) throw std::runtime_error("popen() failed!");
while (!feof(pipe)) {
if (fgets(buffer.data(), 128, pipe) != nullptr)
result += buffer.data();
}
auto rc = pclose(pipe);
if (rc == EXIT_SUCCESS) { // == 0
} else if (rc == EXIT_FAILURE) { // EXIT_FAILURE is not used by all programs, maybe needs some adaptation.
}
return result;
}
Получая stderr и stdout с помощью popen()
, я боюсь, что вам нужно будет перенаправить вывод stderr на stdout из командной строки, которую вы передаете popen (), добавив 2>&1
. Это вызывает неудобство, поскольку оба потока непредсказуемо смешиваются.
Если вы действительно хотите иметь два выделенных файловых дескриптора для stderr и stdout, один из способов сделать это - выполнить разветвление самостоятельно и скопировать новые процессы stdout / stderr в два канала, которые доступны из родительского процесса. (см. dup2()
и pipe()
). Я мог бы остановиться здесь более подробно, но это довольно утомительный способ делать что-то, и к нему нужно проявлять большую осторожность. И в Интернете полно примеров.
Патрик, не могли бы вы опубликовать отрывок из примера кода, как я могу это изменить? Я полагаю, мне просто нужно добавить int res = pclose()
в конце?
Я взял ваш код и отказался от использования std::shared_ptr
.
Спасибо - все работает !, выложил полное решение в конце поста :)
Как я могу передать файл .bat вместо аргумента cmd?
Есть какое-то обходное решение. Вы можете перенаправить stderr на stdout, добавив «2> & 1» к вашему cmd. Подойдет ли это вашим потребностям?
Спасибо :) ... примечание: работает и для windows / dos!
Вы можете получить код возврата из канала, используя собственное средство удаления:
#include <cstdio>
#include <iostream>
#include <memory>
#include <string>
#include <array>
#include <utility>
using namespace std;
pair<string, int> exec(const char* cmd) {
array<char, 128> buffer;
string result;
int return_code = -1;
auto pclose_wrapper = [&return_code](FILE* cmd){ return_code = pclose(cmd); };
{ // scope is important, have to make sure the ptr goes out of scope first
const unique_ptr<FILE, decltype(pclose_wrapper)> pipe(popen(cmd, "r"), pclose_wrapper);
if (pipe) {
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
result += buffer.data();
}
}
}
return make_pair(result, return_code);
}
int main(int argc, char* argv[]) {
if (argc <= 1) return 0;
cout << "calling with " << argv[1] << '\n';
const auto process_ret = exec(argv[1]);
cout << "captured stdout : " << '\n' << process_ret.first << endl;
cout << "program exited with status code " << process_ret.second << endl;
return 0;
}
Ниже приведена немного другая версия ответа Патрик. B, в которой используется fread()
вместо fgets()
.
Использование fread()
было предложено Антти Хаапала в этой ветке:
Как выполнить команду и получить вывод команды в C++ с помощью POSIX?
Эта версия считывает стандартный вывод в std::string
, который должен быть передан вторым аргументом.
Функция возвращает код выхода выполнения cmd
, возвращенный из pclose()
.
Чтобы получить также stderr
, добавьте в конец cmd
: "2>&1"
, как предлагает Алекс в этом же потоке.
Функция main()
ниже показывает, как использовать:
int execute(std::string cmd, std::string& output)
чтобы вывести текущий каталог и распечатать его на стандартный вывод:
#include <iostream>
#include <array>
#include <unistd.h>
int execute(std::string cmd, std::string& output) {
const int bufsize=128;
std::array<char, bufsize> buffer;
auto pipe = popen(cmd.c_str(), "r");
if (!pipe) throw std::runtime_error("popen() failed!");
size_t count;
do {
if ((count = fread(buffer.data(), 1, bufsize, pipe)) > 0) {
output.insert(output.end(), std::begin(buffer), std::next(std::begin(buffer), count));
}
} while(count > 0);
return pclose(pipe);
}
int main(int argc, char** argv) {
std::string output;
execute("ls", output);
std::cout << output;
}
Спасибо за это обновление. Я просто блуждаю (и это искреннее любопытство, а не критика), в чем будет преимущество такого подхода? Для меня вывод std :: string - это хорошо, какую пользу нам дает вектор? - Я думаю, что со строками было бы легче выполнять строковые операции (например, поиск и т. д.). Разве это не для обработки команд с двоичным выводом? - Благодарность! (+1 кстати)
@code_fodder добро пожаловать. Ну да, идея использовать вектор для хранения необработанных данных. Если нужно преобразовать его в строку, можно просто std::string(aVector.data())
, но я думаю, вы правы. Вот std::string
, вероятно, то, что ожидается. Я изменил код, чтобы сохранить стандартный вывод в std::string
. Также была исправлена ошибка, позволяющая хранить мусор в конце вектора (или строки).
Как вы говорите, строка, вероятно, является наиболее распространенным требованием для вывода, но я думаю, что это хороший вариант, если вы получаете какой-то необработанный / двоичный вывод - я могу придумать несколько мест, которые могут быть полезны, спасибо: )
Я отредактировал этот ответ, но, по-видимому, он не проходит (StackOverflow становится очень неприятным). Ошибка в "std :: next (std :: begin (buffer), count-1)". Это должно быть «count», а не «count-1». Итератор должен быть за пределами конца. Текущий ответ даст вам результат с пропущенными символами.
@ 4nt, ну да, он предотвращает добавление последнего символа, полученного из канала и сохраненного в buffer.data (), к строковому выводу. Идея заключалась в том, чтобы предотвратить добавление завершающего символа '\ n' из вывода команды Linux в строку. Таким образом, пользователь может выбрать добавление '\ n' или нет, в зависимости от варианта использования. Но ты прав. Последний символ, прочитанный из канала, не сохраняется в выходной строке, будь то '\ n' или что-то еще. Код был отредактирован с учетом вашего предложения. благодаря. Я также исправил некоторые ошибки в #includes и вызове execute () в main ().
boost
вариант? Если да, то вам стоит взглянуть на boost :: process.