Jump to content


Photo

Retrieve standard output (StdOut) of a program


  • Please log in to reply
53 replies to this topic

#1 drifter

drifter
  • Members
  • 121 posts

Posted 09 August 2008 - 12:13 PM

This is ConsoleApps, the script previously known as RunRedStdio.

Retrieves the standard output (StdOut) of a program.
The functions included are:
RunConsoleAppWait() Runs a program and returns its standard output when it exits.
RunConsoleApp() Runs a program and returns immediately. The program's standard output can be retrieved using GetConsoleAppStdOut(). The return value of this function must be passed to the CloseConsoleAppHandle() function when no longer needed.
GetConsoleAppStdOut() Retrieves the standard output of a program that was started using the RunConsoleApp() function.
CloseConsoleAppHandle() Must be called to free resources allocated by calls to RunConsoleApp()Here is an example using the RunConsoleAppWait() function:

SIMPLE EXAMPLE
MsgBox, % RunConsoleAppWait("cmd.exe /c echo Hello World.")

Here is an example using the RunConsoleApp() function:

REAL-TIME EXAMPLE
#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#Include ConsoleApps.ahk

;Create GUI window with an edit control to print the output.
Gui, Add, Edit, x15 y15 w460 h300 -Wrap ReadOnly hscroll vtxtStdout,
Gui, Show, w490 h330, Standard Input/Output Redirection Example

; Create the process
ConsoleAppHandle := RunConsoleApp("ConsoleApps_TestFile.bat -p nothing", A_ScriptDir)
; In reality, this function will never return with a ConsoleAppHandle value of 0, but
; it is good to check for this possibility in case future versions do return a value of
; 0 for failure.
if (ConsoleAppHandle == 0)
{
    MsgBox, Error running cmd.exe
    ExitApp
}

/* Continuously retrieve the process's output and write it to the edit control 
 * in a loop as it arrives.
 */
Loop
{
    ; Append the ConsoleApps standard output to StdOut.
    ConsoleAppStillRunning := GetConsoleAppStdOut(ConsoleAppHandle, StdOut, BytesAppended, ExitCode)
    ; If StdOut was appended with new text, write it to the GUI window.
    if (BytesAppended)
       GuiControl, , txtStdOut, %StdOut%  ; append the output to the edit control.
    ; If the ConsoleApp is no longer running, then stop checking for output.
    if (!ConsoleAppStillRunning)
       break
    ; Give up remainder of time-slice to give the child process a chance to generate more output
    ; before checking again.
    sleep 0
}
; This function must be called when the ConsoleAppHandle is no longer needed.
CloseConsoleAppHandle(ConsoleAppHandle)
if (!ExitCode)
    MsgBox, The program has completed successfully.
else
    MsgBox, The program has exitted with an error code of %ExitCode%.
return

; The following label is receives control when the user closes the GUI window.
GuiClose:
ExitApp

Here is the code for the latest version of the script:

http://www.autohotke...eApps.2.1.1.zip

CHANGES
Version 2.1.1NEW: Script now initializes itself regardless of how it is loaded.
Version 2.1FIX: Script can now initialize properly on startup when loaded as a library by calling the CONSOLEAPPS() function.Version 2.0NEW: Functions renamed to be loaded from as a library.Version 1.2FIX: Now correctly redirects the StdErr stream.
DOC: Updated and revised documentation.Version 1.1DOC: Revised example code.[/list]KNOWN ISSUES/RESOLUTION

ConsoleApps fails to capture error messages produced by applications.
CAUSE: This is caused by a bug in the design of the script, because of which output is not captured from the StdErr stream of the application.
RESOLUTION: Download and include version 1.2 of this script.

#2 Guests

  • Guests

Posted 09 August 2008 - 12:44 PM

Very nice. Works well on Win98 and XP. I will have to do some comparisons to cmdret.ahk.

#3 BoBo²

BoBo²
  • Guests

Posted 10 August 2008 - 12:01 AM

Thx. :D

#4 disFunc_tnl

disFunc_tnl
  • Guests

Posted 03 January 2010 - 04:40 AM

I'm trying to change the directory by entering it or passing it as a variable to the RunRed function.

hRedProcess := RunRed("[color=red]MyApp.exe arg1","C:\Documents and Settings\Uname\Desktop\Sub folder\bin"[/color])

; also no luck with..

hRedProcess := RunRed("MyApp.exe arg1",[color=red]WorkingDir=[/color]"C:\Documents and Settings\Uname\Desktop\Sub folder\bin")

I have to run the commandline application from the script directory or the system32 folder. All other configurations fail.

I've tried setworkingDir, changing to cmd.exe and adding a working directory, scriptDir, all with no luck.

So am I to assume I cannot pass directory and arguments as variables to the function?

Also is there no way to use RunWait for the function?

Some clarification please.

#5 drifter

drifter
  • Members
  • 121 posts

Posted 03 January 2010 - 08:26 AM

RunRed's failure to set the working directory is unintended behavior. Thank you for the bug report. I have updated the script in the first post. If you paste the new code into your current version of RedStdio, it should produce the behavior you expected. I am currently unable to test it, however, so let me know if it's still not working and I will take a closer look.

This function cannot be used with RunWait. However, the desired result can be achieved with the following code:

RedStdio_StdOutToVar(CmdLine, WorkingDir="")
{
	;Create the process
	hRedProcess := RunRed(CmdLine, WorkingDir)
	if (hRedProcess == 0)
		return

	;Retrieve the process's output and append it to the edit control in a loop.  Break when the process
	;has closed.
	Loop
	{
		retv := GetChildStdout(hRedProcess, strStdout, BytesAppended)  ; append output of child process to strStdout
		if (BytesAppended)
			var := var . strStdout  ; append the output to the var
		if (retv == 1)
			return var  ; a return value of 1 indicates the process no longer exists.
		else if (retv >= 2)
		{
			MsgBox % "Unexpected error in RedStdio_StdOutToVar"
			RedStdioDestroy()
			return var
		}
		sleep 0  ; Give up remainder of time-slice to give the child process a chance to generate more output.
	}
}

Call the above function from within your code and it will return with the text the process sent to StdOut after it has finished executing. Again, I am not able to test it at this time, but if you have any trouble, let me know.

#6 disFunc_tnl

disFunc_tnl
  • Guests

Posted 03 January 2010 - 09:56 PM

Posted ImageThank you so much for the fast response!

RunRed's failure to set the working directory is unintended behavior. Thank you for the bug report. I have updated the script in the first post. If you paste the new code into your current version of RedStdio, it should produce the behavior you expected.

I'm surprised that no one else reported this (I was thinking it was just my syntax). Do I have to specify ,WorkingDir="" ? Correct me if I'm wrong but I also cannot pass variables to the parameters?

Call the above function from within your code and it will return with the text the process sent to StdOut

As I'm fairly new to using functions, I had a hard enough time figuring out the 1st one was strStdout.
Your saying in the case of this new 'RunWait' func, that I should replace strStdout with StdOut?

I will try everything out and let you know it goes.

Thanks again drifter for these functions and your continued support of them :)!

#7 Laszlo

Laszlo
  • Fellows
  • 4713 posts

Posted 03 January 2010 - 10:21 PM

WorkingDir="..." in a function paramenter is a comparision, evaluated to 0 or 1. You need here ":=".

#8 disFunc_tnl

disFunc_tnl
  • Guests

Posted 03 January 2010 - 11:33 PM

WorkingDir="..." in a function paramenter is a comparision, evaluated to 0 or 1. You need here ":=".

:shock: What you said probably make total sense but you just lost me there..


I replaced the entire function with the update and again tried both:
hRedProcess := RunRed("[color=red]MyApp.exe arg1","C:\Documents and Settings\Uname\Desktop\Sub folder\bin"[/color])

; also no luck with..

hRedProcess := RunRed("MyApp.exe arg1",[color=red]WorkingDir=[/color]"C:\Documents and Settings\Uname\Desktop\Sub folder\bin")
I still get the error message

Unable to start MyApp.exe

So now I'm stumped and will have to resort to 1st moving/installing everything to the script dir, calling the function, then removing everything after parsing. Should not add too much to cycles and process times so it'l be a feasible alternative until this gets fully resolved.

I Will check back often as it still may just be the way I'm entering the dir.

Also thanks drifter for the RunWait function.
Can I just add its code to the main functions file? Or does it have to be in a certain place?

Anyway going to try that one now ;D

#9 drifter

drifter
  • Members
  • 121 posts

Posted 04 January 2010 - 01:19 AM

The first syntax will work fine for this situation:
hRedProcess := RunRed("MyApp.exe arg1","C:\Documents and Settings\Uname\Desktop\Sub folder\bin")
As opposed to the other syntax, which will not correctly pass the parameter:
hRedProcess := RunRed("MyApp.exe arg1",WorkingDir="C:\Documents and Settings\Uname\Desktop\Sub folder\bin")

The WorkingDir parameter specifies which directory the program will be running in, and not which directory the program is located. It is much like A_WorkingDir in AutoHotkey.

For example, the following code will look in your script directory for the file "MyApp.exe" and attempt to run it (passing "arg1" as a command line argument). Then, if successful, it will immediately set the new program's working directory to "C:\Documents and Settings\Uname\Desktop\Sub folder\bin":
hRedProcess := RunRed("MyApp.exe arg1","C:\Documents and Settings\Uname\Desktop\Sub folder\bin")
If you meant to look for the program in the "C:\Documents and Settings\Uname\Desktop\Sub folder\bin" folder, try this code instead:
hRedProcess := RunRed("C:\Documents and Settings\Uname\Desktop\Sub folder\bin\MyApp.exe arg1")
Also, as you can see, the WorkingDir parameter is optional and does not need to be specified.

Can I just add its code to the main functions file?

I think adding the RunWait function to the main functions file would be very appropriate. Please, let me know if this does not solve your problem.

#10 disFunc_tnl

disFunc_tnl
  • Guests

Posted 04 January 2010 - 03:05 AM

If you meant to look for the program in the "C:\Documents and Settings\Uname\Desktop\Sub folder\bin" folder, try this code instead:

hRedProcess := RunRed("C:\Documents and Settings\Uname\Desktop\Sub folder\bin\MyApp.exe arg1")
Also, as you can see, the WorkingDir parameter is optional and does not need to be specified.

I was somewhat aware that the parameters were optional but your clarification is much appreciated ;)!
I tried writing it the way you showed above (also before the update) and again its still giving me the error message :?.
Actually now only the original version of redstdio.ahk works, no matter what I write.
Hrm I'm now thoroughly confused.
I will go back to the original way I was thinking: Move the files into place, call the function, parse the data, remove the files.

I think adding the RunWait function to the main functions file would be very appropriate. Please, let me know if this does not solve your problem.

Sorry, I misinterpreted the code you supplied to resolve for RunWait as a function that I could just add to the original.
As you can see I still have a lot to learn!

Not to be a nuisance but its ideal for me to be able to run a command from any folder. I'm sure its probably something I'm doing but I haven't the faintest clue.

I will check back on this later.

#11 Laszlo

Laszlo
  • Fellows
  • 4713 posts

Posted 04 January 2010 - 07:55 AM

...the other syntax, which will not correctly pass the parameter:

hRedProcess := RunRed("MyApp.exe arg1",WorkingDir="C:\Documents and Settings\Uname\Desktop\Sub folder\bin")

It does. The 2nd parameter is an expression, an x=y comparison of two strings: the content of the WorkingDir variable and the literal string "C:\Documents and Settings\Uname\Desktop\Sub folder\bin". Since WorkingDir has not been assigned a value, the two strings are not equal, therefore the comparison returns FALSE, which is 0. The calling syntax correctly passes the number 0 in the second parameter of the RunRed function.

If the intent was to pass an expression, to be dynamically evaluated, the second parameter has to be
"WorkingDir=C:\Documents and Settings\Uname\Desktop\Sub folder\bin"
If the intent was to pass the path, and also assign it to a variable, the second parameter has to be
WorkingDir := "C:\Documents and Settings\Uname\Desktop\Sub folder\bin"

If the 2nd parameter of the RunRed function is defined ByRef, the whole function call could be ignored in the current AHK version (which can be explained, but it is counterintuitive):
f(1,x=2) ; MsgBox shows '0'

f(a,x) {
   Msgbox %x%
}
but:
f(1,x=2) ; no MsgBox

f(a,ByRef x) {
   Msgbox %x%
}


#12 drifter

drifter
  • Members
  • 121 posts

Posted 07 January 2010 - 02:26 AM

Thank you for clarifying, Laszlo. When I said the other syntax will not correctly pass the parameter, I meant that it would not pass the string that he intended to pass.

Thank you for your posts, disFunc_tnl. Because you have taken interest in this script I have refactored the code with the following improvements:
Increased reliability
Better error reporting
Improved readability
Increased performance
Additional functionality
More meaningful documentation
More meaningful file and function namesThe documentation is in XML format (similar to Visual Studio's documentation format). This is to make the documentation machine readable. I recommend using this version over the previous version, as several bugs were eliminated from the previous version. Again, thank you for taking an interest in this script.

#13 Murp-e

Murp-e
  • Members
  • 531 posts

Posted 07 January 2010 - 07:57 AM

Can someone clarify the similarities/differences between this and cmdret?

#14 drifter

drifter
  • Members
  • 121 posts

Posted 08 January 2010 - 02:44 AM

To answer your question I reviewed CMDret and ran some performance tests. Here is what I've come up with:

Differences
ConsoleApps reports an error and aborts the script if it is passed invalid input, whereas RunReturn and Stream (the CMDret scripts) return without indicating whether an error occurred.
Stream sends the StdOut line-by-line to a user defined callback function named 'CMDret_Output', whereas RunConsoleApp returns immediately and the user calls GetConsoleAppStdOut to retrieve the StdOut.
ConsoleApps appears to execute 1.4x faster than RunReturn and 2x faster than Stream on my PC.
CMDret scripts are 5k in size each, whereas ConsoleApps is 22k in size.Similarities
RunReturn is similar to the RunConsoleAppWait function in ConsoleApps.
Stream and RunConsoleApp both allow a user to process the StdOut as it comes through the stream.

#15 Murp-e

Murp-e
  • Members
  • 531 posts

Posted 08 January 2010 - 07:37 AM

drifter: Thanks for the quick and concise clarification! So your script has superior error reporting and better performance at the cost of a few kbs, I'll definitely give this a try.