popen в windows и пробелы

Убил достаточно большое количество времени, но так и не смог найти самостоятельно ответ на вопрос, посему решил обратиться за помощью в сообщество. Сразу скажу, IDE - Visual Studio 2019, работаю под Windows 10. Суть вопроса такова: имеем 2 приложения и желание сделать а-ля pipe.

Первое приложение - test1:

/** \brief Это тестовый модуль, вызываемый через popen */
int main(int argc, char** argv)
{
    /** \brief Просто вывожу все аргументы */
    for (int i = 0; i < argc; i++)
        printf("argv[%d] - %s\n", i, argv[i]);

    return 0;
}

Второе приложение - test2:

/** \brief Это тестовый модуль для проверки работы popen */
int main()
{
    /** \brief Формирование строки для запуска внешнего модуля */
    char buffer[2048];
    auto mProgram = "F:/Super Test/test1/test1.exe";
    sprintf(buffer, "\"%s\"", mProgram);

    /** \brief Запуск программы */
    auto pipe = _popen(buffer, "r");

    /** \brief Перехват stdout */
    std::array<char, 128> words;
    while (fgets(words.data(), words.size(), pipe) != nullptr) {
        std::cout << words.data();
    }
}

Запускаю test2 - все отлично, получаем то, что и ждём.

введите сюда описание изображения

А теперь я хочу, чтобы в test1 приходил путь к файлу, который мне нужно обработать и ключ -in по которому я буду это понимать. Добавляю параметр mFile и меняю строку, передаваемую в _popen:

auto mProgram = "F:/Super Test/test1/test1.exe";
auto mFile = "F:/Super Test/test1/CMakeLists.txt";
sprintf(buffer, "\"%s\" -in %s", mProgram, mFile);

Запускаем test2 еще раз:

второй запуск

Видим не то, что мы хотели получить, но оно и правильно, ведь кавычки то я не поставил, а мы знаем как командная строка windows реагирует на пробелы...

Корректируем строчку:

sprintf(buffer, "\"%s\" -in \"%s\"", mProgram, mFile);

и запускаем еще разок

Третий запуск

На этом наши полномочия всё! (с) Который час пытаюсь решить этот вопрос, но тщетно. Пытался вручную через cmd проводить данные операции, там все нормально. С _popen (да и с system, чего скрывать), ничего не выходит. Конечно, можно удалить пробел в названии папки и все будет прекрасно, но этот вариант меня не устраивает) Объясните, люди добрые, в чем проблема, где же зарыта эта собака!?


Ответы (1 шт):

Автор решения: user7860670

При выполнении popen в качестве интерпретатора по-умолчанию запускается cmd /c после чего идет строка, переданная в popen. У cmd хитрое поведение при разборе кавычек:

If /C or /K is specified, then the remainder of the command line after the switch is processed as a command line, where the following logic is used to process quote (") characters:

  1. If all of the following conditions are met, then quote characters on the command line are preserved:
  • no /S switch
  • exactly two quote characters
  • no special characters between the two quote characters, where special is one of: &<>()@^|
  • there are one or more whitespace characters between the two quote characters
  • the string between the two quote characters is the name of an executable file.
  1. Otherwise, old behavior is to see if the first character is a quote character and if so, strip the leading character and remove the last quote character on the command line, preserving any text after the last quote character.

Сооветсвенно решение - еще одна пара кавычек:

sprintf(buffer, R"(""%s" "-in" "%s"")", mProgram, mFile);

Ну и тут я еще использую raw string literal, чтобы снизить количество крякозябр.

PS Советую использовать boost.process, там можно передать пачку аргументов посредством boost::process::args не заботясь о пробелах и кавычках.

→ Ссылка