Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capture command line output (by character output, not having to wait per line)

Tags:

c#

I'm currently rendering the output of a command line process into a text box. The problem is that in a normal command prompt window, one of the lines that is written has a load bar kind of thing... where every few seconds it outputs a "." to the screen.... After a few dots, it will start a new line and then continue loading until it has completed its process.

With the following code, instead of getting these "." appear one by one, my OutputDataRecieved is waiting for the whole line to be written out... so the load bar is useless... Ie, it waits for "............." and thennnn it acts upon it.

Is there a way to keep track of every character being output to the screen rather than what seems to be per line outputs?

//Create process
System.Diagnostics.Process process = new System.Diagnostics.Process();

// arguments.ProcessStartInfo contains the following declaration:
// ProcessStartInfo = new ProcessStartInfo( "Cmd.exe" )
// {
//     WorkingDirectory = executableDirectoryName,
//     UseShellExecute = false,
//     RedirectStandardInput = true,
//     RedirectStandardOutput = true,
//     CreateNoWindow = true,
// }
process.StartInfo = arguments.ProcessStartInfo;

//Start the process
StringBuilder sb = new StringBuilder();

bool alreadyThrownExit = false;

// The following event only seems to be run per line output rather than each character rendering the command line process useless
process.OutputDataReceived += ( sender, e ) =>
{
    sb.AppendLine( e.Data );
    CommandLineHelper.commandLineOutput = sb.ToString();
    arguments.DelegateUpdateTextMethod();

    if( !alreadyThrownExit )
    {
        if( process.HasExited )
        { 
            alreadyThrownExit = true;
            arguments.DelegateFinishMethod();
            process.Close();
        }
    }
};

process.Start();
process.StandardInput.WriteLine( arguments.Command );
process.StandardInput.WriteLine( "exit" );

process.BeginOutputReadLine();
like image 458
Jimmyt1988 Avatar asked Oct 24 '25 17:10

Jimmyt1988


1 Answers

If you want asynchronous processing of the stdout of the given process on a per-character basis, you can use the TextReader.ReadAsync() method. Instead of the code you have to handle the OutputDataReceived event, just do something like this:

process.Start();

// Ignore Task object, but make the compiler happy
var _ = ConsumeReader(process.StandardOutput);

process.StandardInput.WriteLine( arguments.Command );
process.StandardInput.WriteLine( "exit" );

where:

async Task ConsumeReader(TextReader reader)
{
    char[] buffer = new char[1];

    while ((await read.ReadAsync(buffer, 0, 1)) > 0)
    {
        // process character...for example:
        Console.Write(buffer[0]);
    }
}

Alternatively, you could just create a dedicated thread and use that to call TextReader.Read() in a loop:

process.Start();

new Thread(() =>
{
    int ch;

    while ((ch = process.StandardOutput.Read()) >= 0)
    {
        // process character...for example:
        Console.Write((char)ch);
    }
}).Start();

process.StandardInput.WriteLine( arguments.Command );
process.StandardInput.WriteLine( "exit" );

IMHO the latter is more efficient, as it doesn't require as much cross-thread synchronization. But the former is more similar to the event-driven approach you would have had with the OutputDataReceived event.

like image 69
Peter Duniho Avatar answered Oct 26 '25 05:10

Peter Duniho