LINUX.ORG.RU

Прочитать stderr и stdout запускаемого приложения

 , ,


0

2

Гугл завален инструкциями с переадресацией stderr в stdout и использованием popen. Адекватного описания как сделать то, что мне надо не нашел. Такая задача никогда передо мной не вставала, поэтому прошу пояснить, как сделать то, что мне надо.

Что мне надо:

  1. Запустить заданную программу с аргументами
  2. По ее завершению прочитать весь её stdout и stderr отдельно

Переадресовать их в физические файлы и потом их прочитать нельзя - саму команду редактировать нельзя (там могут быть и редиректы и всё прочее), нужно только актуально то, что она выплюнула в out/err.

Вангую что тут надо как-то совокупить dup2, exec а потом вычитать через fgets. Но чот оно у меня не стыкуется.

Кто может направить в правильную сторону?

★★★

pipe, fork, dup2, exec, fdopen

Пример. Там главное лишние дескрипторы в обоих процессах закрывать. Если вывод у программы очень большой, то может иметь смысл читать потоки поочерёдно (если хоть один пайп переполнится, то дочерний процесс зависнет пока из него не вычитают данные).

xaizek ★★★★★ ()
Ответ на: комментарий от xaizek

если хоть один пайп переполнится, то дочерний процесс зависнет пока из него не вычитают данные

Блин, это прям совсем такое себе.

А без изменения самой строки команды дюп поможет просто в два файла переопределить? Типа сначала открыть два файла в parent thread, сказать «дочке» что это теперь твои stdout/err, а потом по завершению прочитать эти два файла?

PPP328 ★★★ ()

Как-тьо так, наверное:

pipe() x2 -> fork():
    [child] -> close(1), close(2) -> dup2(pipe1[1], 1), dup2(pipe2[1], 2) -> exec()
    [parent] -> read(pipe1[0]), read(pipe2[0])

а потом по завершению прочитать эти два файла?

Ты же хотел без внешних файлов. Узнай какой обычно размер у буфера пайпа, может тебе и хватит.

anonymous ()
Ответ на: комментарий от PPP328

А без изменения самой строки команды дюп поможет просто в два файла переопределить?

Конечно. Там даже без dup2() можно:

close(1);
close(2);
open("stdout", O_CREAT | O_WRONLY, 0777);
open("stderr", O_CREAT | O_WRONLY, 0777);
xaizek ★★★★★ ()

на тебе кода кусок:

вторая функция тебе не нужна.

последняя - пускает асинхронный процесс и читает его вывод.

IStringConsumer = это интерфейс, который умеет строки добавлять, просто абстракция нечта к чему можно добавить строку. например массива строк или вывода на экран. пока асинх процесс не завершится она будет читать его строки и добавлять их в интерфейс. cwxString это const wxString - класс строка в wxWidgets, довольно прозрачно заменяется на std::string.

сотвественно эта функция

bool runPipe(cwxString &fcommand, IStringConsumer* fout)  

заблокирует текущий поток, пока асинх процесс работает. если надо чтобы на блокировала, надо ее или в отдельном потоке пускать, или иначе делать.

static FILE * __openPipe(cwxString &fcommand){
	return popen(fcommand+ " 2>&1", "r"); //redirect stderr to stdout
	//return popen(fcommand, "r"); 
}

static int __closePipe(FILE* fpipe){
	return pclose(fpipe);
}


//read data from the opened pipe and close it at the end.
static bool __readPipeAndClose(FILE* fpipe, IStringConsumer* fout){
	if (fpipe == nullptr) return false;	/* Handle error */;

	const int __maxLen = 2000;

	char lbuf[__maxLen+2];
	while (fgets(lbuf,__maxLen,fpipe)){
		if(fout){
			fout->addString(wxString::FromUTF8(lbuf));
		}
	}

	int lstatus = __closePipe(fpipe);
	if (lstatus == -1) { /* Error reported by pclose() */
		return false;
	}
	if (!WIFEXITED(lstatus) ) return false;
	int lret  = WEXITSTATUS(lstatus);
	return lret == 0;
}

//create pipe and read it.
bool runPipe(cwxString &fcommand, IStringConsumer* fout){
	FILE* lfile = __openPipe(fcommand);
	if(lfile) __readPipeAndClose(lfile,fout);
	return lfile!=nullptr;
}


alysnix ()
Ответ на: комментарий от anonymous

Ты же хотел без внешних файлов.

Я хотел без внешних файлов потому что это подразумевало добавления 2>… в команду. Если без этого можно обойтись - норм.

Узнай какой обычно размер у буфера пайпа, может тебе и хватит.

2 мегабыра вроде. Может не хватить, если командой будет что-то типа ls -la на примаунченную нас-шару.

PPP328 ★★★ ()
Ответ на: комментарий от alysnix

//redirect stderr to stdout

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

P.S. Без претензий к оказанной вами помощи, Вас на работе не ругают за использование двойного подчеркивания в качестве префикса имени функции?

PPP328 ★★★ ()
Ответ на: комментарий от PPP328

Вас на работе не ругают за использование двойного подчеркивания в качестве префикса имени функции?

я сам себе работаю. :) это мой личный код. двойное подчеркивание не считаю особо правильным, но и не особо неправильным. часто обозначаю им статические, то есть локальные в файле обьекты.

alysnix ()
Ответ на: комментарий от PPP328

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

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

если вопрос был не об этом, то видимо ошибся.

там вторая функция нужна(я написал не нужна), она используется в третьей. разумеется это все можно написать одной функцией.

alysnix ()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от PPP328

Так и я умею и без абстракций.

там вообще-то просто завернут вывод stderr запущенного процесса в его stdout, то есть они об’еденены, чтобы не сажать доп тред на чтение stderr. и вообще мне был нужен объединенный вывод. вы можете не обьединять. главное в том, что этот код пускает процесс и вычитывает его вывод, в данном случае stdout+stderr.

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

alysnix ()
Ответ на: комментарий от PPP328

open() использует первый доступный дескриптор, так что если 0 занят, а 1 закрыт, то будет использоваться 1. Второй вызов open() будет использовать дескриптор 2, если он тоже был закрыт.

xaizek ★★★★★ ()
Ответ на: комментарий от xaizek

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

я бы тут лучше еще раз форкнулся, и читал разные потоки из разных процессов

Lrrr ()
Ответ на: комментарий от alysnix

Гугл завален инструкциями с переадресацией stderr в stdout и использованием popen. Что мне надо: Запустить заданную программу с аргументами По ее завершению прочитать весь её stdout и stderr отдельно

там вообще-то просто завернут вывод stderr запущенного процесса в его stdout, то есть они об’еденены, чтобы не сажать доп тред на чтение stderr. и вообще мне был нужен объединенный вывод. вы можете не обьединять. главное в том, что этот код пускает процесс и вычитывает его вывод, в данном случае stdout+stderr.

Т.е. само сообщение темы вы не читали? И да, popen не может читать stderr.

PPP328 ★★★ ()

Небольшой модуль для выполнения внешней программы и чтения её произвольных дескрипторов.

Usage:

  1. #include <popen2.h>
  2. POpenStream s[] = { { 1 }, { 2 } }; // Что читать: stdout и stderr.
  3. POpen po; // Исполнитель и читатель.
  4. popen2_init (&po);
  5. popen2_add_stream (&po, s, 2);
  6. popen2_exec (&po, «/bin/bash», args); // В случае ошибки - см. po.what.
  7. Полученные данные в (ptr=s[0].data, size=s[0].size),...
  8. popen2_destroy (&po); // Освобождаем ресурсы.

Файлы:

popen2.h:

#ifndef POPEN2_H
#define POPEN2_H

typedef struct {
	int	fd;
	char   *data;
	int	max_size;
	int	alloced;
	int	size;
	int	pipes[2];
} POpenStream;

typedef struct {
	int	      poll_fd;
	POpenStream **str;
	int	      nstr;
	const char   *what;
} POpen;

int popen2_init (POpen *);
int popen2_destroy (POpen *);
int popen2_add_stream (POpen *, POpenStream *, int nstream);
int popen2_exec (POpen *, const char *cmd, char *const argv[]);

#endif

popen2.c:

#include <sys/types.h>
#include <sys/epoll.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include "popen2.h"

#ifdef MAX
#undef MAX
#endif
#define MAX(x,y) ((x) >= (y)? (x): (y))

int popen2_init (POpen *p)
{
	p->poll_fd = -1;
	p->str  = 0;
	p->nstr = 0;
	p->what = "";

	return 0;
}

int popen2_add_stream (POpen *p, POpenStream *s, int nstream)
{
	int i;

	p->str = (POpenStream**) realloc (p->str, sizeof *p->str * (p->nstr + nstream));\

	for (i = 0; i < nstream; i++) {
		p->str[p->nstr++] = s + i;
		s[i].pipes[0] = s[i].pipes[1] = -1;
	}

	return 0;
}

int popen2_destroy (POpen *p)
{
	POpenStream **s = p->str, **e = p->str + p->nstr;

	for (; s < e; s++) {
		if ((*s)->pipes[0] >= 0)
			close ((*s)->pipes[0]);
		if ((*s)->pipes[1] >= 0)
		    close ((*s)->pipes[1]);
		if ((*s)->alloced)
			free ((*s)->data);
	}

	free (p->str);
	p->str  = 0;
	p->nstr = 0;

	if (p->poll_fd >= 0) {
		close (p->poll_fd);
		p->poll_fd = -1;
	}

	return 0;
}

int popen2_exec (POpen *p, const char *path, char *const argv[])
{
	POpenStream **s = p->str, **e = p->str + p->nstr, *ss;
	int i, maxfd = -1, maxpipe = -1, n, l;
	struct epoll_event event_data, fdevents[10];
	long flags;
	char buf[1024];
	
	p->poll_fd = epoll_create1 (EPOLL_CLOEXEC);
	if (p->poll_fd == -1) {
		p->what = "epoll_create1";
		return -1;
	}

	for (i = 0; i < p->nstr; i++) {
		s = p->str + i;
		event_data.events   = EPOLLIN | EPOLLHUP;
		event_data.data.ptr = *s;
		if (pipe ((*s)->pipes) != 0) {
			p->what = "pipe";
			return -1;
		}
		if (epoll_ctl (p->poll_fd, EPOLL_CTL_ADD, (*s)->pipes[0], &event_data) != 0) {
			p->what = "epoll_ctl";
			return -1;
		}
	}

	pid_t pid = fork ();

	if (!pid) {				       // child
		for (s = p->str; s < e; s++)
			close ((*s)->pipes[0]);
		
		for (s = p->str; s < e; s++) {
			if (maxfd < 0 || maxfd < (*s)->fd)
				maxfd = (*s)->fd;
			if (maxpipe < 0 || maxpipe < (*s)->pipes[1])
				maxpipe = (*s)->pipes[1];
		}

		if (maxpipe < maxfd)
			maxpipe = maxfd;

		for (s = p->str; s < e; s++) {
			if ((*s)->pipes[1] <= maxfd) {
				dup2 ((*s)->pipes[1], ++maxpipe);
				close ((*s)->pipes[1]);
				(*s)->pipes[1] = maxpipe;
			}
		}
	
		for (s = p->str; s < e; s++) {
			if ((*s)->fd != (*s)->pipes[1])
				dup2 ((*s)->pipes[1], (*s)->fd);
		}

		execv (path, argv);
		perror ("exec");
		exit (1);
	}

	if (pid == -1) {
		p->what = "fork";
		return -1;
	}

	for (s = p->str; s < e; s++) {
		close ((*s)->pipes[1]);
		(*s)->pipes[1] = -1;
		flags = fcntl ((*s)->pipes[0], F_GETFL);
		if (flags == -1) {
			p->what = "fcntl(G_GETFL)";
			return -1;
		}
		if (fcntl ((*s)->pipes[0], F_SETFL, flags | O_NONBLOCK) == -1) {
			p->what = "fcntl(F_SETF,O_NONBLOCK)";
			return -1;
		}
	}

	while ((n = epoll_wait (p->poll_fd, fdevents, sizeof fdevents / sizeof fdevents[0], -1)) > 0) {
		for (i = 0; i < n; i++) {
			ss = (POpenStream*)fdevents[i].data.ptr;
			while ((l = read (ss->pipes[0], buf, sizeof buf)) > 0) {
				if (ss->size + l > ss->alloced) {
					ss->alloced = MAX (ss->alloced * 2, ss->size + l);
					ss->data = (char*)realloc (ss->data, ss->alloced);
					memmove (ss->data + ss->size, buf, l);
					ss->size += l;
				}
			}
			if (l == 0) {
				close (ss->pipes[0]);
				ss->pipes[0] = -1;
			} else if (l == -1) {
				if (errno != EAGAIN) {
					p->what = "read";
					return -1;
				} else continue;
			}
		}
		n = 0;
		for (s = p->str; s < e; s++)
			if ((*s)->pipes[0] != -1) {
				n = 1;
				break;
			}
		if (!n)
			break;
	}

	if (n < 0) {
		p->what = "epoll_wait";
		return -1;
	}

	close (p->poll_fd);
	p->poll_fd = -1;

	waitpid (pid, 0, 0);

	return 0;
}

Тестовая программа, main.c:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "popen2.h"

int main ()
{
	// POpenStream s1 = { 1 } , s2 = { 2 }, s5 = { 5 };
	POpenStream s[] = {
		{ 1 },
		{ 2 },
		{ 5 }
	};
	POpen po;
	int err;
	unsigned i;
	const char *cmd = "/bin/bash";
	char *const args[] = { "bash", "-c", "echo file descriptor 1: 111; "
			       "echo file descriptor 2: 222 1>&2; "
			       "echo file descriptor 5: 555 1>&5; "
			       "no-such-command", 0 };
	const char *sep = "";

	if (popen2_init (&po) != 0) {
		perror ("popen2_init");
		return 1;
	}

	// if (popen2_add_stream (&po, &s1, 1) != 0) {
	// 	fputs ("Add stream 's1' failed.", stderr);
	// 	return 1;
	// }

	// if (popen2_add_stream (&po, &s2, 1) != 0) {
	// 	fputs ("Add stream 's2' failed.", stderr);
	// 	return 1;
	// }

	// if (popen2_add_stream (&po, &s5, 1) != 0) {
	// 	fputs ("Add stream 's5' failed.", stderr);
	// 	return 1;
	// }

	if (popen2_add_stream (&po, s, 3) != 0) {
		fputs ("Add stream 's1' failed.", stderr);
		return 1;
	}

	if (popen2_exec (&po, cmd, args) != 0) {
		err = errno;
		fprintf (stderr, "popen2_exec() failed. %s:%s\n", po.what, strerror (err));
		return 1;
	}

	printf ("Command: %s\nArgs: [", cmd);
	for (i = 0; args[i]; i++, sep = ", ")
		printf ("%s\"%s\"", sep, args[i]);
	printf ("]\n");
	
	for (i = 0; i < sizeof s / sizeof s[0]; i++)
		printf ("fd %d: %d bytes [%.*s]\n",
			s[i].fd,
			s[i].size,
			s[i].size,
			s[i].data);

	// fprintf (stdout, "fd %d: %d bytes [%.*s]\n", s1.fd, s1.size, s1.size, s1.buf);
	// fprintf (stdout, "fd %d: %d bytes [%.*s]\n", s2.fd, s2.size, s2.size, s2.buf);
	// fprintf (stdout, "fd %d: %d bytes [%.*s]\n", s5.fd, s5.size, s5.size, s5.buf);

	popen2_destroy (&po);

	return 0;
}

Вывод тестовой программы:

Command: /bin/bash
Args: ["bash", "-c", "echo file descriptor 1: 111; echo file descriptor 2: 222 1>&2; echo file descriptor 5: 555 1>&5; no-such-command"]
fd 1: 23 bytes [file descriptor 1: 111
]
fd 2: 81 bytes [file descriptor 2: 222
bash: no-such-command: команда не найдена
]
fd 5: 23 bytes [file descriptor 5: 555
]

loki231 ()
Ответ на: комментарий от loki231

Обновление.

  • Переименовал типы (popen2_t, popen2_stream_t).
  • Исправил ошибку (не читались хвосты).
  • Добавил опцию поиска исполняемой программы в $PATH (search_path). По-умолчанию выключена.
  • Добавил опцию exclusive. Закрывает все открытые файлы, кроме слушаемых, перед выполнением exec. По-умолчанию включена.
  • Добавил поддержку всех видов exec (execve, execv, execl, execle, execlp, execvp, execlpe, execvpe).

Ссылка на github https://github.com/exqver/popen2.

loki231 ()
Ответ на: Обновление. от loki231
FILE * fout = popen(command, "r");
while (fgets(getsbuff, EXEC_READ_SIZE - 1, fout)) {
    printf("%s\n", getsbuff);
}
pclose(fout);

Проверки возвращаемых значений по вкусу. Но это всё не читает stderr. У себя сделал через close(stdout/err) + open(fileout/err) а для Windows CreateProcess с аттрибутами в которых установлены хэндлеры файлов вместо STDOUT/ERR.

PPP328 ★★★ ()