397 votes

Process.start : comment obtenir la sortie ?

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

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

563voto

Ferruccio Points 51508

Lorsque vous créez votre Process objet défini StartInfo de manière appropriée :

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 de caractères en valeurs numériques. Il se peut que vous deviez d'abord manipuler les chaînes de caractères s'il y a des caractères numériques invalides dans les chaînes de caractères que vous lisez.

6 votes

Je me demandais comment vous pouviez traiter les StandardError ? En passant, j'aime beaucoup ce code, il est beau et propre.

3 votes

Merci, mais je pense que je n'ai pas été clair : Dois-je ajouter une autre boucle pour le faire ?

0 votes

@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 l'un des flux atteindra inévitablement EOF en premier et vous ne voudrez plus lire à partir de celui-ci. Vous pouvez également utiliser deux boucles dans deux threads différents.

370voto

T30 Points 170

Vous pouvez traiter vos résultats de manière synchrone ou de manière 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();
}

Note qu'il est préférable de traiter les deux sortie et erreurs Ils doivent être traités séparément.

(*) Pour certaines commandes (ici StartInfo.Arguments ), vous devez ajouter le /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 faire des opérations compliquées 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);

5 votes

Il faut aimer l'asynchronisme ! J'ai pu utiliser ce code (avec un peu de transcription) en VB.net

0 votes

Notez que 'string output = process.StandardOutput.ReadToEnd();' peut produire une grande chaîne s'il y a beaucoup de lignes de sortie ; l'exemple asynchrone et la réponse de Ferruccio traitent tous deux la sortie ligne par ligne.

9 votes

Note : votre première approche (synchrone) n'est pas correcte ! Vous ne devriez PAS lire StandardOutput et StandardError de manière synchrone ! Cela causera des blocages. Au moins l'un d'entre eux doit être asynchrone.

26voto

cubrman Points 170

Très bien, pour tous ceux qui veulent lire à la fois les erreurs et les sorties, mais qui obtiennent 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 StandardOutput propriété.

La réponse est basée sur le code 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);
}

0 votes

Merci de l'avoir ajouté. Puis-je vous demander quelle commande vous avez utilisée ?

0 votes

Je suis en train de développer une application en c# qui est conçue pour lancer un mysqldump.exe, montrer à l'utilisateur tous les messages que l'application génère, attendre qu'elle se termine, puis effectuer d'autres tâches. Je n'arrive pas à comprendre de quel type de commande vous parlez ? Toute cette question porte sur le lancement d'un processus à partir de C#.

1 votes

Si vous utilisez deux gestionnaires distincts, vous n'obtiendrez pas de blocages.

10voto

driis Points 70872

La méthode standard de .NET consiste à lire l'information contenue dans le processus. Sortie standard stream. Il y a un exemple dans les documents MSDN liés. De la même manière, vous pouvez lire StandardError et écrire à Entrée standard .

6voto

shakram02 Points 1933

Vous pouvez utiliser la mémoire partagée pour que les deux processus communiquent entre eux. MemoryMappedFile

vous allez principalement créer un fichier mappé en mémoire mmf dans le processus parent à l'aide de l'instruction "using", puis créez le second processus jusqu'à ce qu'il se termine et laissez-le écrire le résultat dans l'instruction "using". mmf en utilisant BinaryWriter puis lire le résultat dans le fichier mmf à l'aide du processus parent, vous pouvez également passer l'option mmf en utilisant des arguments de ligne de commande ou en le codant en dur.

lorsque vous utilisez le fichier mappé dans le processus parent, veillez à ce que le processus enfant écrive le résultat dans le fichier mappé avant que le fichier mappé ne soit libéré dans le processus parent.

Exemple : le processus des parents

    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 de l'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 à partir de %childDir%/bin/debug et le copier à %parentDirectory%/bin/debug puis exécuter le projet parent

childDir et parentDirectory les noms des dossiers de vos projets sur le pc. bonne chance :)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X