Как я могу прочитать вывод процесса, который не был сброшен?

Считайте, что эта маленькая программа будет скомпилирована как application.exe

#include <stdio.h>

int main()
{
char str[100];
printf ("Hello, please type something\n");
scanf("%[^\n]s", &str);
printf("you typed: %s\n", str);
return 0;
}

Теперь я использую этот код для запуска application.exe и получить его вывод.

#include <stdio.h>
#include <iostream>
#include <stdexcept>

int main()
{
char buffer[128];
FILE* pipe = popen("application.exe", "r");
while (!feof(pipe)) {
if (fgets(buffer, 128, pipe) != NULL)
printf(buffer);
}
pclose(pipe);
return 0;
}

Моя проблема в том, что нет вывода, пока я не сделал свой ввод. Затем обе выходные строки извлекаются.
Я могу обойти эту проблему, добавив эту строку после первого printf заявление.

fflush(stdout);

Затем первая строка извлекается, прежде чем я сделаю свой ввод, как ожидалось.

Но как я могу получить выходные данные приложений, которые я не могу изменить и которые не используют fflush() в «реальном времени» (значит, перед тем как выйти)? ,
И как Windows CMD делает это?

10

Решение

Вас укусил тот факт, что буферизация для потоков, которые автоматически открываются в C-программе, изменяется в зависимости от типа подключенного устройства.

Это немного странно — одна из вещей, с которой * nixes приятно играть (и которые отражены в стандартной библиотеке C), заключается в том, что процессам не очень важно знать, откуда они получают данные и куда они их записывают. Вы просто транслируете и перенаправляете на досуге, и это обычно подключи и играй, и довольно быстро.

Одно очевидное место, где нарушается это правило — это взаимодействие; Вы представляете хороший пример. Если выходные данные программы буферизованы блоком, вы не увидите их до того, как, возможно, накопятся данные 4k или процесс завершится.

Программа может определить, пишет ли она в терминал через isatty() (и, возможно, с помощью других средств). Терминал концептуально включает в себя пользователя, предлагающего интерактивную программу. Код библиотеки, открывающий stdin и stdout, проверяет это и изменяет свою политику буферизации на буферизованную строку: при обнаружении новой строки поток сбрасывается. Это идеально подходит для интерактивных, линейно-ориентированных приложений. (Это менее чем идеально для редактирования строк, как это делает bash, который полностью отключает буферизацию.)

открыть страницу руководства группы для stdin является довольно расплывчатым в отношении буферизации, чтобы дать реализациям достаточную свободу действий, но она говорит:

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

Вот что происходит с вашей программой: стандартная библиотека видит, что она работает «не в интерактивном режиме» (запись в канал), пытается быть умной и эффективной и включает буферизацию блоков. Запись новой строки больше не сбрасывает вывод. Обычно это хорошо: представьте, что вы записываете двоичные данные, записываете на диск в среднем каждые 256 байтов! Грозный.

Следует отметить, что между вами и, скажем, диском, вероятно, существует целый каскад буферов; после стандартной библиотеки C появляются буферы операционной системы, а затем и диск.

Теперь к вашей проблеме: стандартный буфер библиотеки, используемый для хранения записываемых символов, находится в памяти программы. Несмотря на внешний вид, данные еще не покинули вашу программу и, следовательно, (официально) не доступны для других программ. Я думаю, что вам не повезло. Вы не одиноки: большинство интерактивных консольных программ будут работать плохо, если вы попытаетесь управлять ими через каналы.

10

Другие решения

ИМХО, это одна из менее логичных частей буферизации ввода-вывода: она действует по-разному, когда направляется на терминал, в файл или канал. Если IO направлен на файл или канал, это обычно буферизированный, это означает, что вывод фактически записывается только тогда, когда буфер заполнен или когда происходит явное очищение => это то, что вы видите, когда вы выполняете программу через popen,

Но когда IO направляется на терминал, возникает особый случай: все ожидающие выходные данные автоматически сбрасываются перед чтением с того же терминала. Этот особый случай необходим, чтобы позволить интерактивным программам отображать подсказки перед чтением.

Плохо то, что если вы попытаетесь провести интерактивное приложение через каналы, вы потеряете: подсказки могут быть прочитаны только тогда, когда приложение завершится или когда будет выведено достаточно текста для заполнения буфера. Вот почему разработчики Unix изобрели так называемые псевдо ттыс (pty). Они реализованы как драйверы терминала, так что приложение использует интерактивную буферизацию, но IO фактически управляется другой программой, владеющей главной частью pty.

К сожалению, как ты пишешь application.exe, Я предполагаю, что вы используете Windows, и я не знаю эквивалентного механизма в Windows API. Вызываемый должен использовать небуферизованный ввод-вывод (stderr по умолчанию небуферизовано), чтобы вызывающий абонент мог прочитать подсказки перед отправкой ответа.

7

Проблемы моего вопроса в моем оригинальном посте уже очень хорошо объяснены
в других ответах.
Консольные приложения используют функцию с именем isatty() обнаружить
если их stdout Обработчик подключен к трубе или реальной консоли. В случае трубы
весь вывод буферизируется и сбрасывается порциями, кроме случаев, когда вы напрямую вызываете fflush(),
В случае реальной консоли вывод небуферизован и напрямую выводится на
консольный вывод.
В Linux вы можете использовать openpty() создать псевдотерминал и создать в нем свой процесс. Как
В результате процесс будет думать, что он работает в реальном терминале и использует небуферизованный вывод.
Windows, кажется, не имеет
такой вариант.

После долгих поисков документации по winapi я обнаружил, что это не правда. На самом деле вы можете создать
свой собственный экранный буфер консоли и использовать его для stdout вашего процесса, который будет небуферизованным тогда.
К сожалению, это не очень удобное решение, потому что нет обработчика событий, и нам нужно опросить новые данные.
Также на данный момент я не уверен, как обрабатывать прокрутку, когда этот экранный буфер заполнен.
Но даже если есть еще проблемы
Слева, я думаю, я создал очень полезную (и интересную) отправную точку для тех из вас, кто когда-либо хотел получить небуферизованные (и неукрашенные)
вывод процесса консоли Windows.

#include <windows.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
char cmdline[] = "application.exe"; // process command
HANDLE scrBuff;                     // our virtual screen buffer
CONSOLE_SCREEN_BUFFER_INFO scrBuffInfo; // state of the screen buffer
// like actual cursor position
COORD scrBuffSize = {80, 25};       // size in chars of our screen buffer
SECURITY_ATTRIBUTES sa;             // security attributes
PROCESS_INFORMATION procInfo;       // process information
STARTUPINFO startInfo;              // process start parameters
DWORD procExitCode;                 // state of process (still alive)
DWORD NumberOfCharsWritten;         // output of fill screen buffer func
COORD pos = {0, 0};                 // scr buff pos of data we have consumed
bool quit = false;                  // flag for reading loop

// 1) Create a screen buffer, set size and clear

sa.nLength = sizeof(sa);
scrBuff = CreateConsoleScreenBuffer( GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
&sa, CONSOLE_TEXTMODE_BUFFER, NULL);
SetConsoleScreenBufferSize(scrBuff, scrBuffSize);
// clear the screen buffer
FillConsoleOutputCharacter(scrBuff, '\0', scrBuffSize.X * scrBuffSize.Y,
pos, &NumberOfCharsWritten);

// 2) Create and start a process
//      [using our screen buffer as stdout]

ZeroMemory(&procInfo, sizeof(PROCESS_INFORMATION));
ZeroMemory(&startInfo, sizeof(STARTUPINFO));
startInfo.cb = sizeof(STARTUPINFO);
startInfo.hStdOutput = scrBuff;
startInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
startInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
startInfo.dwFlags |= STARTF_USESTDHANDLES;
CreateProcess(NULL, cmdline, NULL, NULL, FALSE,
0, NULL, NULL, &startInfo, &procInfo);
CloseHandle(procInfo.hThread);

// 3) Read from our screen buffer while process is alive

while(!quit)
{
// check if process is still alive or we could quit reading
GetExitCodeProcess(procInfo.hProcess, &procExitCode);
if(procExitCode != STILL_ACTIVE) quit = true;

// get actual state of screen buffer
GetConsoleScreenBufferInfo(scrBuff, &scrBuffInfo);

// check if screen buffer cursor moved since
// last time means new output was written
if (pos.X != scrBuffInfo.dwCursorPosition.X ||
pos.Y != scrBuffInfo.dwCursorPosition.Y)
{
// Get new content of screen buffer
//  [ calc len from pos to cursor pos:
//    (curY - posY) * lineWidth + (curX - posX) ]
DWORD len =  (scrBuffInfo.dwCursorPosition.Y - pos.Y)
* scrBuffInfo.dwSize.X
+(scrBuffInfo.dwCursorPosition.X - pos.X);
char buffer[len];
ReadConsoleOutputCharacter(scrBuff, buffer, len, pos, &len);

// Print new content
// [ there is no newline, unused space is filled with '\0'
//   so we read char by char and if it is '\0' we do
//   new line and forward to next real char ]
for(int i = 0; i < len; i++)
{
if(buffer[i] != '\0') printf("%c",buffer[i]);
else
{
printf("\n");
while((i + 1) < len && buffer[i + 1] == '\0')i++;
}
}

// Save new position of already consumed data
pos = scrBuffInfo.dwCursorPosition;
}
// no new output so sleep a bit before next check
else Sleep(100);
}

// 4) Cleanup and end

CloseHandle(scrBuff);
CloseHandle(procInfo.hProcess);
return 0;
}
1

Ты не можешь
Потому что еще не сброшенные данные принадлежат самой программе.

0

Я думаю, что вы можете сбросить данные в stderr или инкапсулировать функцию fgetc а также fungetc не портить поток или использовать system("application.ext >>log") а потом mmap войти в память, чтобы делать то, что вы хотите.

-1
По вопросам рекламы [email protected]