Process.start: comment obtenir la sortie?

306

Je voudrais exécuter un programme de ligne de commande externe à partir de mon application Mono / .NET. Par exemple, je voudrais exécuter mencoder . C'est possible:

  1. Pour obtenir la sortie du shell de ligne de commande et l'écrire sur ma zone de texte?
  2. Pour obtenir la valeur numérique pour afficher une barre de progression avec le temps écoulé?
lourd
la source

Réponses:

458

Lorsque vous créez correctement votre Processensemble d'objets StartInfo:

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

puis lancez le processus et lisez-le:

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

Vous pouvez utiliser int.Parse()ou int.TryParse()pour convertir les chaînes en valeurs numériques. Vous devrez peut-être d'abord effectuer une manipulation de chaîne s'il y a des caractères numériques non valides dans les chaînes que vous lisez.

Ferruccio
la source
4
Je me demandais comment vous pourriez gérer StandardError?. BTW J'aime vraiment cet extrait de code! Agréable et propre.
codea
3
Merci, mais je pense que je n'étais pas clair: dois-je ajouter une autre boucle pour le faire?
codea
@codea - je vois. Vous pouvez créer une boucle qui se termine lorsque les deux flux atteignent EOF. Cela peut devenir un peu compliqué, car un flux frappera inévitablement EOF en premier et vous ne voulez plus en lire. Vous pouvez également utiliser deux boucles dans deux threads différents.
Ferruccio
1
est-il plus robuste de lire jusqu'à la fin du processus lui-même, plutôt que d'attendre la fin des flux?
Gusdor
@Gusdor - Je ne pense pas. À la fin du processus, ses flux seront automatiquement fermés. En outre, un processus peut fermer ses flux bien avant sa fin.
Ferruccio
254

Vous pouvez traiter votre sortie de manière synchrone ou asynchrone .

1. Exemple synchrone

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

Notez qu'il est préférable de traiter à la fois la sortie et les erreurs : elles doivent être traitées séparément.

(*) Pour certaines commandes (ici StartInfo.Arguments) vous devez ajouter la /c directive , sinon le processus se fige dans le WaitForExit().

2. Exemple asynchrone

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 vous n'avez pas besoin de compliquer les opérations avec la sortie, vous pouvez contourner la méthode OutputHandler, en ajoutant simplement les gestionnaires directement en ligne:

//* Set your output and error (asynchronous) handlers
process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
process.ErrorDataReceived += (s, e) => Console.WriteLine(e.Data);
T30
la source
2
dois aimer async! J'ai pu utiliser ce code (avec un peu de transcription) dans VB.net
Richard Barker
note 'string output = process.StandardOutput.ReadToEnd ();' peut produire une grande chaîne s'il y a plusieurs lignes de sortie; l'exemple asynchrone et la réponse de Ferruccio traitent tous les deux la sortie ligne par ligne.
Andrew Hill
5
Remarque: votre première approche (synchrone) n'est pas correcte! Vous ne devez PAS lire à la fois StandardOutput et StandardError de manière synchrone! cela entraînera des blocages. au moins l'un d'entre eux doit être asynchrone.
S.Serpooshan
6
Process.WaitForExit () bloque les threads, donc synchrone. Pas le but de la réponse, mais j'ai pensé que je pourrais ajouter ceci. Ajoutez process.EnableRaisingEvents = true et utilisez l'événement Exited pour être complètement asynchrone.
Tom
N'est-il pas possible de rediriger directement? J'utilise toute la colorisation de la sortie sass?
Ini
14

D'accord, pour tous ceux qui souhaitent lire à la fois les erreurs et les sorties, mais obtiennent des blocages avec l'une des solutions, fournies dans d'autres réponses (comme moi), voici une solution que j'ai construite après avoir lu l'explication de MSDN pour la StandardOutputpropriété.

La réponse est basée sur le code 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
la source
Merci d'avoir ajouté ceci. Puis-je demander quelle commande vous utilisiez?
T30
Je développe une application en c # qui est conçue pour lancer un mysqldump.exe, montrer à l'utilisateur chaque message généré par l'application, attendre qu'elle se termine, puis effectuer quelques tâches supplémentaires. Je ne comprends pas de quel type de commande vous parlez? Toute cette question concerne le lancement d'un processus à partir de c #.
cubrman
1
si vous utilisez deux gestionnaires distincts, vous n'aurez pas de blocages
Ovi
également dans votre exemple, vous lisez le processus.StandardOutput une seule fois ... juste après l'avoir démarré, mais on voudrait le lire en continu pendant que le processus s'exécute, non?
Ovi
7

Pour cela, la méthode standard .NET consiste à lire le flux StandardOutput du processus . Il y a un exemple dans les documents MSDN liés. De même, vous pouvez lire depuis StandardError et écrire dans StandardInput .

driis
la source
4

vous pouvez utiliser la mémoire partagée pour que les 2 processus communiquent, consultez MemoryMappedFile

vous allez principalement créer un fichier mappé en mémoire mmfdans le processus parent en utilisant l'instruction "using", puis créer le deuxième processus jusqu'à ce qu'il se termine et le laisser écrire le résultat à l' mmfaide BinaryWriter, puis lire le résultat à l' mmfaide du processus parent, vous pouvez également passez le mmfnom en utilisant des arguments de ligne de commande ou codez-le en dur.

assurez-vous que lorsque vous utilisez le fichier mappé dans le processus parent, vous faites en sorte que le processus enfant écrive le résultat dans le fichier mappé avant que le fichier mappé ne soit publié dans le processus parent

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

Processus enfant

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

pour utiliser cet exemple, vous devez créer une solution avec 2 projets à l'intérieur, puis vous prenez le résultat de la construction du processus enfant de% childDir% / bin / debug et le copiez dans% parentDirectory% / bin / debug puis exécutez le projet parent

childDiret parentDirectorysont les noms de dossier de vos projets sur le pc bonne chance :)

shakram02
la source
1

Comment lancer un processus (tel qu'un fichier bat, un script perl, un programme de console) et afficher sa sortie standard sur un formulaire 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();    

Vous pouvez trouver ProcessCallersur ce lien: Lancer un processus et afficher sa sortie standard

abberdeen
la source
1

Vous pouvez enregistrer la sortie du processus à l'aide du code ci-dessous:

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
la source
1

La solution qui a fonctionné pour moi dans Win et Linux est le suivant

// 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
la source