Process.start: ¿cómo obtener el resultado?

306

Me gustaría ejecutar un programa de línea de comando externo desde mi aplicación Mono / .NET. Por ejemplo, me gustaría ejecutar mencoder . Es posible:

  1. ¿Para obtener la salida del shell de la línea de comandos y escribirla en mi cuadro de texto?
  2. Para obtener el valor numérico para mostrar una barra de progreso con el tiempo transcurrido?
fuerte
fuente

Respuestas:

458

Cuando crea su Processconjunto de objetos StartInfoadecuadamente:

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

luego comienza el proceso y lee de él:

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

Puede usar int.Parse()o int.TryParse()para convertir las cadenas en valores numéricos. Es posible que primero tenga que manipular algunas cadenas si hay caracteres numéricos no válidos en las cadenas que lee.

Ferruccio
fuente
44
Me preguntaba cómo podría lidiar con StandardError? Por cierto, ¡realmente me gusta este fragmento de código! bonito y limpio.
codea
3
Gracias, pero creo que no estaba claro: ¿debería agregar otro ciclo para hacerlo?
codea
@codea - Ya veo. Puede crear un bucle que finalice cuando ambas transmisiones lleguen a EOF. Eso puede ser un poco complicado porque una transmisión inevitablemente llegará a EOF primero y ya no querrás leer más. También puede usar dos bucles en dos hilos diferentes.
Ferruccio
1
¿Es más robusto leer hasta que el proceso en sí termina, en lugar de esperar el final de las transmisiones?
Gusdor
@ Gusdor - No lo creo. Cuando finaliza el proceso, sus transmisiones se cerrarán automáticamente. Además, un proceso puede cerrar sus secuencias mucho antes de que finalice.
Ferruccio
254

Puede procesar su salida de forma síncrona o asíncrona .

1. Ejemplo sincrónico

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

Tenga en cuenta que es mejor procesar tanto la salida como los errores : deben manejarse por separado.

(*) Para algunos comandos (aquí StartInfo.Arguments) debe agregar la /c directiva , de lo contrario, el proceso se congela en el WaitForExit().

2. Ejemplo asincrónico

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

Si no necesita complicar las operaciones con la salida, puede omitir el método OutputHandler, simplemente agregando los controladores directamente en línea:

//* Set your output and error (asynchronous) handlers
process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
process.ErrorDataReceived += (s, e) => Console.WriteLine(e.Data);
T30
fuente
2
tengo que amar asíncrono! Pude usar este código (con una pequeña transcripción) en VB.net
Richard Barker
note 'string output = process.StandardOutput.ReadToEnd ();' puede producir una cadena grande si hay muchas líneas de salida; el ejemplo asíncrono y la respuesta de Ferruccio procesan la salida línea por línea.
Andrew Hill
55
Nota: ¡ su primer enfoque (sincrónico) no es correcto! ¡NO debe leer StandardOutput y StandardError de forma sincronizada! causará bloqueos muertos. al menos uno de ellos debe ser asíncrono.
S.Serpooshan
66
Process.WaitForExit () es un bloqueo de subprocesos, por lo tanto, sincrónico. No es el punto de la respuesta, pero pensé que podría agregar esto. Agregue process.EnableRaisingEvents = true y haga uso del evento Exited para ser completamente asíncrono.
Tom
¿No es posible redirigir directamente? ¿Uso toda la coloración de la salida sass?
Ini
14

Muy bien, para cualquiera que quiera leer tanto Errores como Salidas, pero se bloquee con cualquiera de las soluciones, proporcionadas en otras respuestas (como yo), aquí hay una solución que construí después de leer la explicación de MSDN para la StandardOutputpropiedad.

La respuesta se basa en el código de 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
fuente
Gracias por agregar esto. ¿Puedo preguntar qué comando estabas usando?
T30
Estoy desarrollando una aplicación en C # que está diseñada para lanzar un mysqldump.exe, mostrar al usuario cada mensaje que genera la aplicación, esperar a que termine y luego realizar algunas tareas más. ¿No puedo entender de qué tipo de comando estás hablando? Toda esta pregunta trata sobre el lanzamiento de un proceso desde c #.
cubrman
1
si usa dos manejadores separados no obtendrá puntos muertos
Ovi
también en tu ejemplo, lees el proceso. Salida estándar solo una vez ... justo después de iniciarlo, pero uno querría leerlo continuamente mientras el proceso se está ejecutando, ¿no?
Ovi
7

La forma estándar de .NET de hacer esto es leer de la secuencia de Salida Estándar del Proceso . Hay un ejemplo en los documentos vinculados de MSDN. De manera similar, puede leer desde StandardError y escribir en StandardInput .

driis
fuente
4

puede usar la memoria compartida para los 2 procesos para comunicarse, consulte MemoryMappedFile

principalmente creará un archivo mapeado de memoria mmfen el proceso principal utilizando la instrucción "using", luego creará el segundo proceso hasta que finalice y le permita escribir el resultado en el mmfuso BinaryWriter, luego lea el resultado del mmfproceso principal, también puede pase el mmfnombre usando argumentos de línea de comando o codifíquelo.

cuando utilice el archivo asignado en el proceso principal, asegúrese de que el proceso secundario escriba el resultado en el archivo asignado antes de que el archivo asignado se libere en el proceso principal

Ejemplo: proceso padre

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

Proceso hijo

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

para usar este ejemplo, deberá crear una solución con 2 proyectos en su interior, luego tomar el resultado de compilación del proceso secundario de% childDir% / bin / debug y copiarlo en% parentDirectory% / bin / debug y luego ejecutar el proyecto principal

childDiry parentDirectoryson los nombres de carpeta de sus proyectos en la PC buena suerte :)

shakram02
fuente
1

Cómo iniciar un proceso (como un archivo bat, un script perl, un programa de consola) y mostrar su salida estándar en un formulario de 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();    

Puede encontrar ProcessCalleren este enlace: Iniciar un proceso y mostrar su salida estándar

abberdeen
fuente
1

Puede registrar la salida del proceso usando el siguiente código:

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) { 
}
PATIDAR DE SHUBHAM
fuente
1

La solución que funcionó para mí en win y linux es la siguiente

// 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
fuente