Process.start: Wie erhalte ich die Ausgabe?

306

Ich möchte ein externes Befehlszeilenprogramm über meine Mono / .NET-App ausführen. Zum Beispiel möchte ich Mencoder ausführen . Ist es möglich:

  1. Um die Befehlszeilen-Shell-Ausgabe zu erhalten und sie in mein Textfeld zu schreiben?
  2. Um den numerischen Wert zu erhalten, um einen Fortschrittsbalken mit verstrichener Zeit anzuzeigen?
stighy
quelle

Antworten:

458

Wenn Sie Ihren ProcessObjektsatz StartInfoentsprechend erstellen :

var proc = new Process 
{
    StartInfo = new ProcessStartInfo
    {
        FileName = "program.exe",
        Arguments = "command line arguments to your executable",
        UseShellExecute = false,
        RedirectStandardOutput = true,
        CreateNoWindow = true
    }
};

Starten Sie dann den Prozess und lesen Sie daraus:

proc.Start();
while (!proc.StandardOutput.EndOfStream)
{
    string line = proc.StandardOutput.ReadLine();
    // do something with line
}

Sie können die Zeichenfolgen verwenden int.Parse()oder int.TryParse()in numerische Werte konvertieren. Möglicherweise müssen Sie zuerst eine Zeichenfolgenmanipulation durchführen, wenn die von Ihnen gelesenen Zeichenfolgen ungültige numerische Zeichen enthalten.

Ferruccio
quelle
4
Ich habe mich gefragt, wie Sie mit StandardError umgehen können. Übrigens mag ich dieses Code-Snippet wirklich! schön und sauber.
Codea
3
Danke, aber ich glaube mir war nicht klar: Soll ich dazu eine weitere Schleife hinzufügen?
Codea
@codea - ich verstehe. Sie können eine Schleife erstellen, die beendet wird, wenn beide Streams EOF erreichen. Das kann etwas kompliziert werden, da ein Stream zwangsläufig zuerst auf EOF trifft und Sie nicht mehr daraus lesen möchten. Sie können auch zwei Schleifen in zwei verschiedenen Threads verwenden.
Ferruccio
1
Ist es robuster zu lesen, bis der Prozess selbst beendet ist, als auf das Ende der Streams zu warten?
Gusdor
@ Gusdor - das glaube ich nicht. Wenn der Prozess beendet wird, werden seine Streams automatisch geschlossen. Außerdem kann ein Prozess seine Streams lange vor dem Beenden schließen.
Ferruccio
254

Sie können Ihre Ausgabe synchron verarbeiten oder asynchron verarbeiten .

1. Synchrones Beispiel

static void runCommand()
{
    Process process = new Process();
    process.StartInfo.FileName = "cmd.exe";
    process.StartInfo.Arguments = "/c DIR"; // Note the /c command (*)
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;
    process.Start();
    //* Read the output (or the error)
    string output = process.StandardOutput.ReadToEnd();
    Console.WriteLine(output);
    string err = process.StandardError.ReadToEnd();
    Console.WriteLine(err);
    process.WaitForExit();
}

Beachten Sie, dass es besser ist, beide Ausgaben zu verarbeiten als auch Fehler zu verarbeiten : Sie müssen separat behandelt werden.

(*) Für einige Befehle (hier StartInfo.Arguments) müssen Sie die hinzufügen/c Direktive , sonst friert der Prozess in der ein WaitForExit().

2. Asynchrones Beispiel

static void runCommand() 
{
    //* Create your Process
    Process process = new Process();
    process.StartInfo.FileName = "cmd.exe";
    process.StartInfo.Arguments = "/c DIR";
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;
    //* Set your output and error (asynchronous) handlers
    process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
    process.ErrorDataReceived += new DataReceivedEventHandler(OutputHandler);
    //* Start process and handlers
    process.Start();
    process.BeginOutputReadLine();
    process.BeginErrorReadLine();
    process.WaitForExit();
}

static void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine) 
{
    //* Do your stuff with the output (write to console/log/StringBuilder)
    Console.WriteLine(outLine.Data);
}

Wenn Sie keine komplizierten Operationen mit der Ausgabe ausführen müssen, können Sie die OutputHandler-Methode umgehen, indem Sie die Handler direkt inline hinzufügen:

//* Set your output and error (asynchronous) handlers
process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
process.ErrorDataReceived += (s, e) => Console.WriteLine(e.Data);
T30
quelle
2
Ich muss asynchron lieben! Ich konnte diesen Code (mit ein wenig Transkription) in VB.net verwenden
Richard Barker
note 'string output = process.StandardOutput.ReadToEnd ();' kann eine große Zeichenfolge erzeugen, wenn viele Ausgabezeilen vorhanden sind; Das asynchrone Beispiel und die Antwort von Ferruccio verarbeiten die Ausgabe zeilenweise.
Andrew Hill
5
Hinweis: Ihr erster (synchroner) Ansatz ist nicht korrekt! Sie sollten StandardOutput und StandardError NICHT synchron lesen! Dies führt zu Deadlocks. Zumindest einer von ihnen muss asynchron sein.
Serpooshan
6
Process.WaitForExit () blockiert den Thread und ist somit synchron. Nicht der Punkt der Antwort, aber ich dachte, ich könnte dies hinzufügen. Fügen Sie process.EnableRaisingEvents = true hinzu und verwenden Sie das Ereignis Exited, um vollständig asynchron zu sein.
Tom
Ist eine direkte Weiterleitung nicht möglich? Ich benutze alle Kolorierungen der Sass-Ausgabe?
Ini
14

In Ordnung, für alle, die sowohl Fehler als auch Ausgaben lesen möchten, aber Deadlocks mit einer der in anderen Antworten angegebenen Lösungen erhalten (wie ich), ist hier eine Lösung, die ich nach dem Lesen der MSDN-Erklärung für StandardOutputEigenschaften erstellt habe.

Die Antwort basiert auf dem Code von T30:

static void runCommand()
{
    //* Create your Process
    Process process = new Process();
    process.StartInfo.FileName = "cmd.exe";
    process.StartInfo.Arguments = "/c DIR";
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;
    //* Set ONLY ONE handler here.
    process.ErrorDataReceived += new DataReceivedEventHandler(ErrorOutputHandler);
    //* Start process
    process.Start();
    //* Read one element asynchronously
    process.BeginErrorReadLine();
    //* Read the other one synchronously
    string output = process.StandardOutput.ReadToEnd();
    Console.WriteLine(output);
    process.WaitForExit();
}

static void ErrorOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) 
{
    //* Do your stuff with the output (write to console/log/StringBuilder)
    Console.WriteLine(outLine.Data);
}
Cubrman
quelle
Vielen Dank für das Hinzufügen. Kann ich fragen, welchen Befehl Sie verwendet haben?
T30
Ich entwickle eine App in c #, mit der eine mysqldump.exe gestartet werden soll, zeige dem Benutzer jede einzelne Nachricht, die die App generiert, warte, bis sie abgeschlossen ist, und führe dann weitere Aufgaben aus. Ich kann nicht verstehen, über welche Art von Befehl Sie sprechen? Bei dieser gesamten Frage geht es darum, einen Prozess von c # aus zu starten.
Cubrman
1
Wenn Sie zwei separate Handler verwenden, erhalten Sie keine Deadlocks
Ovi
Auch in Ihrem Beispiel lesen Sie den Prozess. StandardOutput nur einmal ... direkt nachdem Sie ihn gestartet haben, aber man möchte ihn kontinuierlich lesen, während der Prozess ausgeführt wird, nein?
Ovi
7

Die Standardmethode für .NET besteht darin, aus dem StandardOutput- Stream des Prozesses zu lesen . In den verknüpften MSDN-Dokumenten gibt es ein Beispiel. Ebenso können Sie aus StandardError lesen und in StandardInput schreiben .

driis
quelle
4

Sie können den gemeinsam genutzten Speicher für die beiden Prozesse verwenden, über die kommuniziert werden soll MemoryMappedFile

Sie erstellen hauptsächlich eine Speicherzuordnungsdatei mmfim übergeordneten Prozess mit der Anweisung "using". Erstellen Sie dann den zweiten Prozess, bis er beendet wird, und lassen Sie das Ergebnis in den mmfusing schreiben. BinaryWriterLesen Sie dann das Ergebnis aus mmfdem übergeordneten Prozess Übergeben Sie den mmfNamen mit Befehlszeilenargumenten oder geben Sie ihn fest ein.

Stellen Sie bei Verwendung der zugeordneten Datei im übergeordneten Prozess sicher, dass der untergeordnete Prozess das Ergebnis in die zugeordnete Datei schreibt, bevor die zugeordnete Datei im übergeordneten Prozess freigegeben wird

Beispiel: übergeordneter Prozess

    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("Starting the child process");
            // Command line args are separated by a space
            Process p = Process.Start("ChildProcess.exe", "memfile");

            Console.WriteLine("Waiting child to die");

            p.WaitForExit();
            Console.WriteLine("Child died");

            using (MemoryMappedViewStream stream = mmf.CreateViewStream())
            {
                BinaryReader reader = new BinaryReader(stream);
                Console.WriteLine("Result:" + reader.ReadInt32());
            }
        }
        Console.WriteLine("Press any key to continue...");
        Console.ReadKey();
    }

Untergeordneter Prozess

    private static void Main(string[] args)
    {
        Console.WriteLine("Child process started");
        string mmfName = args[0];

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

        Console.WriteLine("Press any key to continue...");
        Console.ReadKey();
    }

Um dieses Beispiel zu verwenden, müssen Sie eine Lösung mit 2 Projekten erstellen. Anschließend nehmen Sie das Erstellungsergebnis des untergeordneten Prozesses aus% childDir% / bin / debug und kopieren es in% parentDirectory% / bin / debug. Führen Sie dann das aus übergeordnetes Projekt

childDirund parentDirectorysind die Ordnernamen Ihrer Projekte auf dem PC viel Glück :)

shakram02
quelle
1

So starten Sie einen Prozess (z. B. eine Bat-Datei, ein Perl-Skript oder ein Konsolenprogramm) und lassen die Standardausgabe in 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 = "Started function.  Please stand by.." + Environment.NewLine;

// the following function starts a process and returns immediately,
// thus allowing the form to stay responsive.
processCaller.Start();    

Sie finden ProcessCallerunter diesem Link: Starten eines Prozesses und Anzeigen seiner Standardausgabe

abberdeen
quelle
1

Sie können die Prozessausgabe 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) { 
}
SHUBHAM PATIDAR
quelle
1

Die Lösung, die für mich in Win und Linux funktioniert hat, ist die folgende

// GET api/values
        [HttpGet("cifrado/{xml}")]
        public ActionResult<IEnumerable<string>> Cifrado(String xml)
        {
            String nombreXML = DateTime.Now.ToString("ddMMyyyyhhmmss").ToString();
            String archivo = "/app/files/"+nombreXML + ".XML";
            String comando = " --armor --recipient [email protected]  --encrypt " + archivo;
            try{
                System.IO.File.WriteAllText(archivo, xml);                
                //String comando = "C:\\GnuPG\\bin\\gpg.exe --recipient [email protected] --armor --encrypt C:\\Users\\Administrador\\Documents\\pruebas\\nuevo.xml ";
                ProcessStartInfo startInfo = new ProcessStartInfo() {FileName = "/usr/bin/gpg",  Arguments = comando }; 
                Process proc = new Process() { StartInfo = startInfo, };
                proc.StartInfo.RedirectStandardOutput = true;
                proc.StartInfo.RedirectStandardError = true;
                proc.Start();
                proc.WaitForExit();
                Console.WriteLine(proc.StandardOutput.ReadToEnd());
                return new string[] { "Archivo encriptado", archivo + " - "+ comando};
            }catch (Exception exception){
                return new string[] { archivo, "exception: "+exception.ToString() + " - "+ comando };
            }
        }
Jorge Santos Neill
quelle