How do I launch an app and capture the output via stdout and maybe stderr?
I am writing an automated build system and I need to capture the output to analyze. I'd like to update the svn repo and grab the revision number so I can move the files in autobuild/revNumber/ if successful. I also would like to build using make and upload the compile text to my server for everyone to see the warnings and errors on a failed build.
I can't find the system() function, but I found the CreateProcess() function on MSDN. I am able to launch what I need but I have no idea how to capture the stderr and stdout. I notice the process launches separately unless I set a breakpoint and keep my app exiting which it then will keep all the text in my app console window. I would also want to wait until all processes are finished and then scan the data it produced to do any additional operations I need. How do I do any of this?
In real shells (meaning, not sea shells - I mean, not in C Shell or its derivatives), then:
program arg1 arg2 >/tmp/log.file 2>&1
This runs program with the given arguments, and redirects the stdout to /tmp/log.file; the notation (hieroglyph) '2>&1' at the end sends stderr (file descriptor 2) to the same place that stdout (file descriptor 1) is going. Note that the sequence of operations is important; if you reverse them, then standard error will go to where standard output was going, and then standard output (but not standard error) will be redirected to the file.
The choice of file name shown is abysmal for numerous reasons - you should allow the user to choose the directory, and probably should include the process ID or time stamp in the file name.
LOG=${TMPDIR:-/tmp}/log.$$.$(date +%Y%m%d-%H%M%S)
program arg1 arg2 >$LOG 2>&1
In C++, you can use the system() function (inherited from C) to run processes. If you need to know the file name in the C++ program (plausible), then generate the name in the program (strftime() is your friend) and create the command string with that file name.
(Strictly, you also need getenv() to get $TMPDIR, and the POSIX function getpid() to get the process ID, and then you can simulate the two-line shell script (though the PID used would be of the C++ program, not the launched shell).
You could instead use the POSIX popen() function; you'd have to include the '2>&1' notation in the command string that you create to send the standard error of the command to the same place as standard output goes, but you would not need a temporary file:
FILE *pp = popen("program arg1 arg2 2>&1", "r");
You can then read off the file stream. I'm not sure whether there's a clean way to map a C file stream into a C++ istream; there probably is.
You need to fill up the STARTUP_INFO structure, which has hStdInput, hStdOutput and hStdError. Remember to inherit handles when you CreateProcess.
/* Assume you open a file handle or pipe called myoutput */
STARTUP_INFO si_startinfo;
ZeroMemory(&si_startinfo, sizeof(STARTUP_INFO));
si_startinfo.cb = sizeof(STARTUP_INFO);
si_startinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
si_startinfo.hStdOutput = myoutput;
si_startinfo.hStdError = myoutput;
si_startifno.dwFlags != STARTF_USEHANDLES;
PROCESS_INFORMATION pi_procinfo;
ZeroMemory(&pi_procinfo, sizeof(PROCESS_INFORMATION);
CreateProcess(NULL, cmdline, NULL, NULL, true, 0, NULL, pathname, &si_startinfo, &pi_procinfo);
I have not shown the error handling aspects, which you will need to do. The 5th argument is set to true to inherit the handles. Others have explained how to create pipes so I won't repeat it here.
Microsoft's CRTs and the MSDN library do include the system function and the _popen function.
Related
I hope your programming is going well.
I have a question that I hope asserts an easy answer due to my lack of knowledge.
I've used this code from this question - CreateProcess cmd.exe read/write pipes deadlock
And everything works well.
The problem is when I run other commands from the cmd.exe shell that require interactivity, for example, python or powershell, I get the initial output then nothing gets written to the pipe.
So this is what my input/output looks like:
static PCSTR commands[] = { "powershell\r\n", "dir\r\n", "help\r\n"};
ULONG n = RTL_NUMBER_OF(commands);
PCSTR* psz = commands;
do
{
if (MessageBoxW(0,0, L"force close ?", MB_YESNO) == IDYES)
{
DisconnectNamedPipe(hFile);
break;
}
if (p = new U_IRP(&obj))
{
PCSTR command = *psz++;
p->Write(command, (ULONG)strlen(command) * sizeof(CHAR));
p->Release();
}
} while (--n)
When the code runs, I get the initial powershell.exe prompt as so
PS C:\Users>
But after that nothing gets written to the pipe.
The code is using CreateProcess(... "cmd.exe" ...) and I have tried changing it from "cmd.exe" to "cmd.exe /c" and "cmd.exe /k", neither of which work.
Perhaps you would know what I need to do read/write output to interpreted such as python or powershell from a CreateProcess() induced pipe? Thanks for your help!
you exec cmd.exe and send command to it via pipe to exec powershell. then all depended from powershell implementation
on window7:
powershell use ReadConsoleW for got input. so it not use you named pipe - not read from it. and you can note that console window become interactive after you exec powershell. so powershell not accept what you write to pipe (it simply not read from it at all) but read user input from screen. however after you manually input some command to console and press enter - you can got pipe output - powershell use (mix) both - WriteFile and WriteConsoleW for output. some information output via WriteFile and some via WriteConsoleW
on windows10:
powershell use ReadFile for got input. and WriteFile for output. so it read you commands from pipe and write results to it. and all perfect worked. also you can note that console window is inactive in this case - you can not enter any text to it (unlike win7)
so with code all absolute ok. problem only in how 3-rd program read and write data. if it not read from your pipe - you nothing can do here
On my home Linux laptop, I like to write wrapper programs and GUI helpers for things I use frequently. However, I don't like Bash scripting very much, so I do a lot of stuff in C++. However, a lot of times, this requires me to use the system() function from the cstdlib.
This system() command is awesome, but I wanted a way to call system() and receive the stdout/stderror. The system() command only returns the return code from the command. So, in a Bash script, one can do:
myVar=$(ls -a | grep 'search string')
echo $myVar
and myVar will output whatever the stdout was for the command. So I began writing a wrapper class that will add a pipe-to-file to the end of the command, open the file, read all of the piped stdout, and return it as either one long string or as a vector of strings. The intricacies of the class are not really relevant here (I don't think anyway), but the above example would be done like this:
SystemCommand systemCommand;
systemCommand.setCommand("ls -a | grep \'search string\' ");
systemCommand.execute();
std::cout << systemCommand.outputAsString() << std::endl;
Behind the scenes, when systemCommand.execute() is called, the class ensures that the command will properly pipe all stdout/stderr to a randomly generated filename, in the current working directory. So for example, the above command would end up being
"ls -a | grep 'search string' >> 1452-24566.txt 2>&1".
The class then goes attempts to open and read from that file, using ifstream:
std::ifstream readFromFile;
readFromFile.open(_outputFilename);
if (readFromFile.is_open()) {
//Read all contents of file into class member vector
...
readFromFile.close();
//Remove temporary file
...
} else {
//Handle read failure
}
So here is my main question will std::ifstream ever fail to open a recently created file in the current working directory? If so, what would be a way to make it more robust (specifically on Linux)?
A side/secondary question: Is there a very simplified way to achieve what I'm trying to achieve without using file pipes? Perhaps some stuff available in unistd.h? Thanks for your time.
So here is my main question will std::ifstream ever fail to open a recently created file in the current working directory?
Yes.
Mount a USB thumb drive (or some other removable media)
cd to the mount
Execute your program. While it's executing, remove the drive.
Watch the IO error happen.
There's a ton of other reasons too. Filesystem corruption, hitting the file descriptor limit, etc.
If so, what would be a way to make it more robust (specifically on Linux)?
Make temporary files in /tmp, whose entire purpose is for temporary files. Or don't create a file at all, and use pipes for communication instead (Like what popen does, like harmic suggested). Even so, there are no guarantees; try to gracefully handle errors.
I've been trying to send data to stdin of a running process. Here is what I do:
In a terminal I've started a c++ program that simply reads a string and prints it. Code excerpt:
while (true) {
cin >> s;
cout << "I've just read " << s << endl;
}
I get the PID of the running program
I go to /proc/PID/fd/
I execute echo text > 0
Result: text appears in the terminal where the program is run. Note, not I've just read text, but simply text.
What am I doing wrong and what should I do to get this thing to print 'I've just read text'?
When you're starting your C++ program you need to make sure its input comes from a pipe but not from a terminal. You may use cat | myapp to do that. Once it's running you may use PID of your application for echo text > /proc/PID/fd/0
It could be a matter of stdout not being properly flushed -- see "Unix Buffering". Or you could be in a different shell as some commentators have suggested.
Generally, it's more reliable to handle basic interprocess communication via FIFOs or NODs -- named pipes. (Or alternatively redirect stdout and/or stderr to a file and read from that with your c++ program.)
Here's some good resources on how to use those in both the terminal and c++.
"FIFO – Named pipes: mkfifo, mknod"
"Using Pipes in Linux Processes"
"Programming with FIFO: mkfifo(), mknod()"
FD 0 is the terminal the program is running from. When you write to FD 0, you are writing to the terminal the program is running from. FD 0 is not required to be opened in read-only mode; in practice it seems to be read/write mode, so you can write to it. (I suspect this is because FDs 0, 1 and 2 all refer to the same file description)
So echo text > /proc/PID/fd/0 just echoes text to the terminal.
To pipe input to the program, you would need to write to the other end of the pipe (actually a PTY, which mostly behaves like a pair of pipes). Most likely, whatever terminal emulator you're using (xterm, konsole, gnome-terminal) will have the other end open, so you could try writing to that.
I have a project to search, rename or delete files and folders on a selected drive on the computer using a data structure(A tree, a stack, or a queue). My question is, how do I add file paths and directories as nodes in C++?
Comment in other answer suggests using one of the exec() functions. Then parsing and studying the output.
I approve of that idea, but I find it easier to use popen(). Each of the following examples are part of the Linux API, so the calls are c compatible and can be used directly by C++. I expect popen() will be available on other OS's.
To clarify,
1) popen() is a function call for your C++ code to invoke.
2) You will also need to create strings for your OS to generate the lists you want, and submit them to your invocation of popen(). The 1st parameter is the command string
3) in read mode, the output of your "ls -lsa " or "dir" command will be written into the output pipe of the spawned process, and your code will need to 'suck it in', I recommend capturing it to a std::stringstream.
4) after capture of the "dir -r" output, then parse and extract dir's and file names from the stringstream.
Examples of C++ access to popen:
FILE* m_pipe = nullptr; // popen return a FILE*
// use m_pipe to read from sub-process std::out
m_pipe = ::popen (m_cmd.c_str(), "r"); // read mode
// ^^ because popen is not in a namespace
m_pipe = ::popen(m_cmd.c_str(), "w"); // write to sub-process std::in
int32_t pcloseStat = ::pclose(m_pipe);
{
(void)memset(buff, 0, BUFF_SIZE);
// Reads characters from stream and stores them as a C string
// into buff until
// a) (BUFF_SIZE-1) characters have been read or
// b) a newline or
// c) the end-of-file is reached,
// whichever happens first.
char* stat = fgets (buff, BUFF_SIZE, m_pipe); // returns buff or null
int myErrno = errno; //^^^^^^ -- created by popen
}
Example of building a linux command for popen 1st parameter ...
std::string md5sumCmd ("head --bytes=1M " + mPFN +" | md5sum");
This command grabs the 1st 1Megabyte of file name in mPFN (a std::string), and pipes that output into md5sum ... essentially generating an md5sum of the 1s Meg of the file. The md5sum output is what will be received by the calling process.
You will need to create appropriate commands (to pass to popen) to show dir's and folder's and file names, etc.
What ever works from the command line should be fine, but some options might make parsing the output easier.
For your node based structures, add a string property that would serve as your file path. You might need to replace "\" with "/" in it however as the forward slash is often an escape character in most languages. For example in a queue:
class Node {
Node next;
char[50] path;
}
And you can create accessors and mutators the same way you would anything else in a class. This will allow you to assign it values and to read the values.
Folders could be used as a parent and the files are children. A tree structure would likely be the easiest way to do this.
I'm having some trouble with a program. My goal is to have it open several .exe files with optional args passed. For example if I wanted to open up a pdf I could type the string below into a cmd window.
// If used in a cmd window it will open up my PDF reader and load MyPDF.pdf file
"c:\Test space\SumatraPDF.exe" "c:\Test space\Sub\MyPDF.pdf"
Here are two tries I used. The first opens the PDF but of course doesn't load the file. The second simply doesn't work.
// Opens the PDF in my program
system("\"C:\\Test space\\SumatraPDF.exe\"");
// Error I get inside of a cmd window is the comment below
// 'C:\Test' is not recognized as an internal or external command, operable program or batch file.
//system("\"C:\\Test space\\SumatraPDF.exe\" \"C:\\Test space\\Sub\\MyPDF.pdf\"");
I'm unsure of the reason why the second one does not work. It could be I'm misunderstanding something about system, or I'm not using delimiters right.
I feel like there is a library out there designed for this rather than creating a long string that uses so many delimiters.
Thanks for any help.
Welcome to Stack Overflow!
The system method works by passing it's argument to cmd /c. So you will need an extra set of quotes around it. See related question posted by sled.
As an alternative to system, take a look at the ShellExecute or ShellExecuteEx Win32 API function. It has more features although it is not as portable.
// ShellExecute needs COM to be initialized
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
SHELLEXECUTEINFO sei = {0};
sei.cbSize = sizeof(sei);
sei.lpFile = prog; // program like c:\Windows\System32\notepad.exe
sei.lpParameters = args; // program arguments like c:\temp\foo.txt
sei.nShow = SW_NORMAL; // app should be visible and not maximized or minimized
ShellExecuteEx(&sei); // launch program
CoUninitialize();
More information here.