Process.start: come ottenere l'output?


306

Vorrei eseguire un programma da riga di comando esterno dalla mia app Mono / .NET. Ad esempio, vorrei eseguire mencoder . È possibile:

  1. Per ottenere l'output della shell della riga di comando e scriverlo nella mia casella di testo?
  2. Per ottenere il valore numerico per mostrare una barra di avanzamento con il tempo trascorso?

Risposte:


458

Quando si crea il Processset di oggetti in modo StartInfoappropriato:

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

quindi avviare il processo e leggere da esso:

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

È possibile utilizzare int.Parse()o int.TryParse()per convertire le stringhe in valori numerici. Potrebbe essere necessario prima manipolare alcune stringhe se ci sono caratteri numerici non validi nelle stringhe che leggi.


4
Mi chiedevo come si potesse gestire StandardError? A proposito, mi piace molto questo frammento di codice! bello e pulito.
codea,

3
Grazie, ma penso di non essere stato chiaro: dovrei aggiungere un altro ciclo per farlo?
codea,

@codea - Capisco. È possibile creare un loop che termina quando entrambi i flussi raggiungono EOF. Questo può diventare un po 'complicato perché uno stream colpirà inevitabilmente EOF per primo e non vorrai più leggere da esso. È inoltre possibile utilizzare due loop in due thread diversi.
Ferruccio,

1
è più affidabile leggere fino al termine del processo stesso, piuttosto che attendere la fine dei flussi?
Gusdor,

@Gusdor - Non credo. Al termine del processo, i suoi flussi verranno automaticamente chiusi. Inoltre, un processo può chiudere i suoi flussi molto prima che termini.
Ferruccio,

254

È possibile elaborare l'output in modo sincrono o asincrono .

1. Esempio sincrono

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();
}

Si noti che è meglio elaborare sia l' output che gli errori : devono essere gestiti separatamente.

(*) Per alcuni comandi (qui StartInfo.Arguments) è necessario aggiungere la /c direttiva , altrimenti il ​​processo si blocca in WaitForExit().

2. Esempio asincrono

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);
}

Se non è necessario eseguire operazioni complicate con l'output, è possibile ignorare il metodo OutputHandler, semplicemente aggiungendo i gestori direttamente in linea:

//* Set your output and error (asynchronous) handlers
process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
process.ErrorDataReceived += (s, e) => Console.WriteLine(e.Data);

2
devo amare asincrono! Sono stato in grado di utilizzare questo codice (con una piccola trascrizione) in VB.net
Richard Barker,

note 'string output = process.StandardOutput.ReadToEnd ();' può produrre una stringa di grandi dimensioni se sono presenti molte righe di output; l'esempio asincrono e la risposta di Ferruccio elaborano entrambi l'output riga per riga.
Andrew Hill,

5
Nota: il tuo primo approccio (sincrono) non è corretto! NON devi leggere sia StandardOutput che StandardError in modo sincrono! causerà blocchi morti. almeno uno di loro deve essere asincrono.
S. Serpooshan

6
Process.WaitForExit () è il thread threading, quindi sincrono. Non è il punto della risposta, ma ho pensato di aggiungere questo. Aggiungi process.EnableRaisingEvents = true e usa l'evento Exited per essere completamente asincrono.
Tom

Non è possibile reindirizzare direttamente? Uso tutta la colorazione dell'output sass?
Ini

14

Va bene, per chiunque voglia leggere sia errori che output, ma ottiene deadlock con una qualsiasi delle soluzioni, fornite in altre risposte (come me), ecco una soluzione che ho costruito dopo aver letto la spiegazione di MSDN perStandardOutput proprietà.

La risposta si basa sul codice 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);
}

Grazie per averlo aggiunto. Posso chiedere quale comando stavi usando?
T30,

Sto sviluppando un'app in c # progettata per avviare un mysqldump.exe, mostrare all'utente ogni singolo messaggio generato dall'app, attendere che finisca e quindi eseguire altre attività. Non riesco a capire di che tipo di comando stai parlando? L'intera domanda riguarda l'avvio di un processo da c #.
Cubrman,

1
se usi due gestori separati non otterrai deadlock
Ovi,

anche in questo esempio, leggi il processo.StandardOutput solo una volta ... subito dopo averlo avviato, ma uno vorrebbe leggerlo continuamente mentre il processo è in esecuzione, no?
Ovi,



4

è possibile utilizzare la memoria condivisa per i 2 processi per comunicare attraverso, check out MemoryMappedFile

creerai principalmente un file mappato in memoria mmfnel processo padre usando l'istruzione "using", quindi creerai il secondo processo fino a quando non termina e lascerai che scriva il risultato mmfsull'uso BinaryWriter, quindi leggi il risultato mmfdall'uso del processo parent, puoi anche passa ilmmf nome usando gli argomenti della riga di comando o codificarlo.

quando si utilizza il file mappato nel processo padre, assicurarsi che il processo figlio scriva il risultato nel file mappato prima che il file mappato venga rilasciato nel processo parent

Esempio: processo parent

    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();
    }

Processo figlio

    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();
    }

per utilizzare questo esempio, dovrai creare una soluzione con 2 progetti all'interno, quindi prendere il risultato di generazione del processo figlio da% childDir% / bin / debug e copiarlo in% parentDirectory% / bin / debug quindi eseguire il progetto principale

childDire parentDirectorysono i nomi delle cartelle dei tuoi progetti sul pc buona fortuna :)


1

Come avviare un processo (come un file bat, uno script perl, un programma console) e visualizzare l'output standard su un modulo Windows:

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();    

È possibile trovare ProcessCallersu questo link: avvio di un processo e visualizzazione dell'output standard


1

È possibile registrare l'output del processo utilizzando il codice seguente:

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) { 
}

1

La soluzione che ha funzionato per me in Win e Linux è il folling

// 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 bibankingprd@bi.com.gt  --encrypt " + archivo;
            try{
                System.IO.File.WriteAllText(archivo, xml);                
                //String comando = "C:\\GnuPG\\bin\\gpg.exe --recipient licorera@local.com --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 };
            }
        }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.