405 Stimmen

Process.start: Wie erhält man die Ausgabe?

Ich möchte ein externes Befehlszeilenprogramm von meiner Mono/.NET-Anwendung aus ausführen. Zum Beispiel würde ich gerne Folgendes ausführen mencoder . Ist es möglich:

  1. Um die Ausgabe der Kommandozeile zu erhalten und sie in mein Textfeld zu schreiben?
  2. Um den numerischen Wert für die Anzeige eines Fortschrittsbalkens mit der verstrichenen Zeit zu erhalten?

6voto

shakram02 Punkte 8742

Sie können gemeinsamen Speicher verwenden, damit die 2 Prozesse miteinander kommunizieren können, schauen Sie sich MemoryMappedFile an

Sie erstellen hauptsächlich eine Memory-Mapped-Datei mmf im Elternprozess mit der "using"-Anweisung, erstellen dann den zweiten Prozess, bis er beendet ist, und lassen ihn das Ergebnis mit BinaryWriter in die mmf schreiben und lesen Sie dann das Ergebnis aus der mmf mit dem Elternprozess. Sie können auch den Namen der mmf über Befehlszeilenargumente oder fest codieren.

Stellen Sie sicher, dass der Kindprozess das Ergebnis in die Memory-Mapped-Datei schreibt, bevor die Memory-Mapped-Datei im Elternprozess freigegeben wird

Beispiel: Elternprozess

    private static void Main(string[] args)
    {
        using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("memfile", 128))
        {
            using (MemoryMappedViewStream stream = mmf.CreateViewStream())
            {
                BinaryWriter writer = new BinaryWriter(stream);
                writer.Write(512);
            }

            Console.WriteLine("Starten des Kindprozesses");
            // Befehlszeilenargumente sind durch ein Leerzeichen getrennt
            Process p = Process.Start("ChildProcess.exe", "memfile");

            Console.WriteLine("Warten auf das Beenden des Kindes");

            p.WaitForExit();
            Console.WriteLine("Kind verstorben");

            using (MemoryMappedViewStream stream = mmf.CreateViewStream())
            {
                BinaryReader reader = new BinaryReader(stream);
                Console.WriteLine("Ergebnis:" + reader.ReadInt32());
            }
        }
        Console.WriteLine("Drücken Sie eine beliebige Taste, um fortzufahren...");
        Console.ReadKey();
    }

Kindprozess

    private static void Main(string[] args)
    {
        Console.WriteLine("Kindprozess gestartet");
        string mmfName = args[0];

        using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(mmfName))
        {
            int readValue;
            using (MemoryMappedViewStream stream = mmf.CreateViewStream())
            {
                BinaryReader reader = new BinaryReader(stream);
                Console.WriteLine("Kind liest: " + (readValue = reader.ReadInt32()));
            }
            using (MemoryMappedViewStream input = mmf.CreateViewStream())
            {
                BinaryWriter writer = new BinaryWriter(input);
                writer.Write(readValue * 2);
            }
        }

        Console.WriteLine("Drücken Sie eine beliebige Taste, um fortzufahren...");
        Console.ReadKey();
    }

Um dieses Beispiel zu verwenden, müssen Sie eine Lösung mit 2 Projekten erstellen, dann nehmen Sie das Build-Ergebnis des Kindprozesses aus %childDir%/bin/debug und kopieren es nach %parentDirectory%/bin/debug und führen dann das Elternprojekt aus

childDir und parentDirectory sind die Ordnernamen Ihrer Projekte auf dem PC Viel Glück :)

3voto

SHUBHAM PATIDAR Punkte 41

Sie können den Prozessausgabeverlauf mit dem folgenden Code protokollieren:

ProcessStartInfo pinfo = new ProcessStartInfo(item);
pinfo.CreateNoWindow = false;
pinfo.UseShellExecute = true;
pinfo.RedirectStandardOutput = true;
pinfo.RedirectStandardInput = true;
pinfo.RedirectStandardError = true;
pinfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
var p = Process.Start(pinfo);
p.WaitForExit();
Process process = Process.Start(new ProcessStartInfo((item + '>' + item + ".txt"))
{
    UseShellExecute = false,
    RedirectStandardOutput = true
});
process.WaitForExit();
string output = process.StandardOutput.ReadToEnd();
if (process.ExitCode != 0) { 
}

3voto

Jason Harris Punkte 326

Ich stieß auf das berüchtigte Deadlock-Problem, wenn ich Process.StandardOutput.ReadLine und Process.StandardOutput.ReadToEnd aufrufe.

Mein Ziel/Anwendungsfall ist einfach. Starten Sie einen Prozess und leiten Sie seine Ausgabe um, damit ich diese Ausgabe erfassen und über ILogger von .NET Core an die Konsole protokollieren kann und die umgeleitete Ausgabe auch an eine Datei anhängen kann.

Hier ist meine Lösung unter Verwendung der integrierten asynchronen Ereignishandler Process.OutputDataReceived und Process.ErrorDataReceived.

var p = new Process
{
    StartInfo = new ProcessStartInfo(
        command.FileName, command.Arguments
    )
    {
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        UseShellExecute = false,
    }
};

// Pusht StdOut- und StdErr-Zeilen asynchron in eine threadsichere FIFO-Warteschlange
var logQueue = new ConcurrentQueue();
p.OutputDataReceived += (sender, args) => logQueue.Enqueue(args.Data);
p.ErrorDataReceived += (sender, args) => logQueue.Enqueue(args.Data);

// Starten Sie den Prozess und beginnen Sie mit dem Streamen von StdOut/StdErr
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();

// Schleife, bis der Prozess beendet ist oder das Abbruchtoken ausgelöst wird
do
{
    var lines = new List();
    while (logQueue.TryDequeue(out var log))
    {
        lines.Add(log);
        _logger.LogInformation(log)
    }
    File.AppendAllLines(_logFilePath, lines);

    // Asynchrones Warten für eine bestimmte Zeit
    try
    {
        Task.Delay(5000, stoppingToken).Wait(stoppingToken);
    }
    catch(OperationCanceledException) {}

} while (!p.HasExited && !stoppingToken.IsCancellationRequested);

2voto

abberdeen Punkte 233

So können Sie einen Prozess (wie eine Bat-Datei, ein Perl-Skript, ein Konsolenprogramm) starten und dessen Standardausgabe auf einem Windows-Formular anzeigen:

processCaller = new ProcessCaller(this);
//processCaller.FileName = @"..\..\hello.bat";
processCaller.FileName = @"commandline.exe";
processCaller.Arguments = "";
processCaller.StdErrReceived += new DataReceivedHandler(writeStreamInfo);
processCaller.StdOutReceived += new DataReceivedHandler(writeStreamInfo);
processCaller.Completed += new EventHandler(processCompletedOrCanceled);
processCaller.Cancelled += new EventHandler(processCompletedOrCanceled);
// processCaller.Failed += no event handler for this one, yet.

this.richTextBox1.Text = "Funktionsaufruf gestartet. Bitte warten.." + Environment.NewLine;

// die folgende Funktion startet einen Prozess und kehrt sofort zurück,
// was es dem Formular ermöglicht, responsiv zu bleiben.
processCaller.Start();    

Sie können ProcessCaller über diesen Link finden: Starten eines Prozesses und Anzeigen seiner Standardausgabe

2voto

Tyrrrz Punkte 2145

System.Diagnostics.Process ist nicht sehr angenehm zu arbeiten, daher möchten Sie vielleicht CliWrap ausprobieren. Es bietet viele verschiedene Modelle für die Arbeit mit Ausgaben, einschließlich Piping, Buffering und echtzeit Streaming. Hier sind einige Beispiele (übernommen von der readme).

Einfach ein Befehlszeilen-Programm starten:

using CliWrap;

var result = await Cli.Wrap("pfad/zum/exe")
    .WithArguments("--foo bar")
    .WithWorkingDirectory("arbeits/verzeichnis/pfad")
    .ExecuteAsync();

// Der Result enthält:
// -- result.ExitCode        (int)
// -- result.StartTime       (DateTimeOffset)
// -- result.ExitTime        (DateTimeOffset)
// -- result.RunTime         (TimeSpan)

Starten Sie ein Befehlszeilen-Programm und puffern Sie stdout/stderr im Speicher:

using CliWrap;
using CliWrap.Buffered;

// Der Aufruf von `ExecuteBufferedAsync()` anstelle von `ExecuteAsync()`
// konfiguriert implizit Pipes, die in Speicherpuffer schreiben.
var result = await Cli.Wrap("pfad/zum/exe")
    .WithArguments("--foo bar")
    .WithWorkingDirectory("arbeits/verzeichnis/pfad")
    .ExecuteBufferedAsync();

// Der Result enthält:
// -- result.StandardOutput  (string)
// -- result.StandardError   (string)
// -- result.ExitCode        (int)
// -- result.StartTime       (DateTimeOffset)
// -- result.ExitTime        (DateTimeOffset)
// -- result.RunTime         (TimeSpan)

Starten Sie ein Befehlszeilen-Programm mit manueller Pipe-Konfiguration:

using CliWrap

var buffer = new StringBuilder();

var result = await Cli.Wrap("foo")
    .WithStandardOutputPipe(PipeTarget.ToFile("output.txt"))
    .WithStandardErrorPipe(PipeTarget.ToStringBuilder(buffer))
    .ExecuteAsync();

Starten Sie ein Befehlszeilen-Programm als Ereignis-Stream:

using CliWrap;
using CliWrap.EventStream;

var cmd = Cli.Wrap("foo").WithArguments("bar");

await foreach (var cmdEvent in cmd.ListenAsync())
{
    switch (cmdEvent)
    {
        case StartedCommandEvent started:
            _output.WriteLine($"Prozess gestartet; ID: {started.ProcessId}");
            break;
        case StandardOutputCommandEvent stdOut:
            _output.WriteLine($"Out> {stdOut.Text}");
            break;
        case StandardErrorCommandEvent stdErr:
            _output.WriteLine($"Err> {stdErr.Text}");
            break;
        case ExitedCommandEvent exited:
            _output.WriteLine($"Prozess beendet; Code: {exited.ExitCode}");
            break;
    }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X