Classe de gestion de processus

Description

Une question sur le forum revient très souvent: Comment lancer un processus et récupérer la sortie de celui-ci ?

Cette classe est là pour répondre à cette problématique, mais fait aussi bien plus que cela.
En effet, grâce à celle-ci vous pourrez:
- Lancer un processus et attendre la fin de son exécution.
- Lancer un process en parallèle, et possiblité de le "tuer" à tout moment.
- Lancer un processus et mettre une limite de temps.
- Lancer un processus et récupérer la sortie de celui-ci (récupération dans un flux, qui peut être converti en un std::string, voir directement écrit dans un fichier).

Limitation: Système de type Unix uniquement.

Techiquement, c'est essentiellement une utilisation de fork, des pipes, et de waitpid.

Source / Exemple :

#include <iostream>
#include "Process.hh"

/*!
** Exemple d'utilisation de la classe Sys::Process.
*/

namespace
{
  /*!
  ** Lance un simple "ls -la" et attends la fin de l'execution
  */
  void testLs()
  {
    Sys::Process process;
    process.setExecutable("ls");
    process.addArg("-la");
    process.setWorkingDirectory(".");

    std::cout << "Test script shell command: "
              << process.commandLine() << std::endl;

    if (process.run())
    {
      const int status = process.wait();
      std::cout << "Finish with status " << status << std::endl;
    }
    else
      std::cout << "Fork failed" << std::endl;
  }

  /*!
  ** Lance une commande de shell script (bash), et attend la fin
  ** de son exécution.
  */
  void testShellScript()
  {
    Sys::Process process;
    process.setExecutable("/bin/bash");
    process.addArg("-c").addArg("ls -laph --color | cat -ne");
    process.setWorkingDirectory(".");

    std::cout << "Test script shell command: "
              << process.commandLine() << std::endl;

    if (process.run())
    {
      const int status = process.wait();
      std::cout << "Finish with status " << status << std::endl;
    }
    else
      std::cout << "Fork failed" << std::endl;
  }

  /*!
  ** On exécute un ls -lph --color, et on met
  ** le résultat de l'exécution du process dans un buffer.
  ** Le buffer peut être un std::ostringstream qui peut ensuite
  ** être convertie en std::string, ou std::ofstream (l'écriture
  ** se fait alors directement dans un fichier).
  */
  void testLsWriteIntoBuffer()
  {
    Sys::Process process;
    process.setExecutable("ls");
    process.addArg("-lph").addArg("--color");
    process.setWorkingDirectory(".");

    std::ostringstream buff;
    process.setProcessOutput(buff);

    std::cout << "Test script shell command: "
              << process.commandLine() << std::endl;

    if (process.run())
    {
      const int status = process.wait();
      std::cout << "Finish with status " << status
                << ", result is: " << buff.str() << std::endl;
    }
    else
      std::cout << "Fork failed" << std::endl;
  }

  /*!
  ** On lance un processus très long (un sleep de 180 secondes).
  ** On attends une seconde, et on tue le processus car celui-ci
  ** met trop de temps à se terminer.
  */
  void testTerminateInfiniteLoop()
  {
    Sys::Process process;
    process.setExecutable("/bin/sh");
    process.addArg("-c").addArg("sleep 180");

    std::cout << "Test script shell command: "
              << process.commandLine() << std::endl;

    if (process.run())
    {
      int status = 0;
      process.timedWait(1, status);
      process.terminate();
      std::cout << "Finish with status " << status << std::endl;
    }
    else
      std::cout << "Fork failed" << std::endl;
  }

  /*!
  ** On lance un processus très long (un sleep de 15 secondes).
  ** Toute les 5 secondes on demande à l'utilisateur quoi faire.
  ** Celui-ci peut choisir d'attendre ou de tuer le processus.
  */
  void testTerminateInfiniteLoopWithChoice()
  {
    Sys::Process process;
    process.setExecutable("/bin/sh");
    process.addArg("-c").addArg("sleep 15");
    process.setNiceness(20); // Low priority, not mandatory

    std::cout << "Test script shell command: "
              << process.commandLine() << std::endl;

    if (process.run())
    {
      int status;
      while (!process.timedWait(5, status))
      {
        std::cout << "The process is not respondingn"
          "1) Kill itn2) Wait for it" << std::endl;
        int choice;
        std::cin >> choice;
        switch (choice)
        {
          case 1:
            std::cout << "Kill process" << std::endl;
            process.terminate();
            std::cout << "Aborted with status " << status << std::endl;
            return;
          case 2:
            std::cout << "Waiting for process" << std::endl;
            break;
          default:
            std::cout << "Unknow choice, waiting for process" << std::endl;
        }
      }

      std::cout << "Finish with status " << status << std::endl;
    }
    else
      std::cout << "Fork failed" << std::endl;
  }

  /*!
  ** On lance un processus très long (un cat de fichier assorti de sleeps
  ** pour ralentir volontairement le processus).
  ** On provoque l'arrêt de la tâche volontairement au bout de 2 secondes.
  ** Le buffer est alors rempli avec ce qui a pu être mis avant la fin du
  ** décompte.
  */
  void testTerminateInfiniteLoopWithOutputInBuffer()
  {
    Sys::Process process;
    process.setExecutable("/bin/sh");
    process.addArg("-c").addArg("for i in $(seq 1 5); do cat *.cc; sleep 1; done");
    process.setNiceness(20); // Low priority, not mandatory

    std::ostringstream buff;
    process.setProcessOutput(buff);

    std::cout << "Test script shell command: "
              << process.commandLine() << std::endl;

    if (process.run())
    {
      int status = 0;
      process.timedWait(2, status);
      process.terminate();
      std::cout << "Finish with status " << status << ", buff is: "
                << buff.str() << std::endl;
    }
    else
      std::cout << "Fork failed" << std::endl;
  }

  /*!
  ** Lance un simple "ls -la" et attends la fin de l'execution
  ** en utilisant un vfork au lieu d'un fork.
  */
  void testLsWithVFork()
  {
    Sys::Process process;
    process.setExecutable("ls");
    process.addArg("-la");
    process.setWorkingDirectory(".");

    std::cout << "Test script shell command: "
              << process.commandLine() << std::endl;

    if (process.vrun())
    {
      const int status = process.wait();
      std::cout << "Finish with status " << status << std::endl;
    }
    else
      std::cout << "Fork failed" << std::endl;
  }

  /*!
  ** Lance un simple "ls -la" et attends la fin de l'execution
  ** en utilisant un posix_spawn au lieu d'un fork.
  */
  void testLsWithSpawn()
  {
    Sys::Process process;
    process.setExecutable("/bin/sh");
    process.addArg("-c");
    process.addArg("cd . && ls -la");

    std::cout << "Test script shell command: "
              << process.commandLine() << std::endl;

    if (process.spawn())
    {
      const int status = process.wait();
      std::cout << "Finish with status " << status << std::endl;
    }
    else
      std::cout << "Fork failed" << std::endl;
  }
} //namespace

int main()
{
  testLs();
  testLsWriteIntoBuffer();
  testShellScript();
  testTerminateInfiniteLoop();
  testTerminateInfiniteLoopWithChoice();
  testTerminateInfiniteLoopWithOutputInBuffer();
  testLsWithVFork();
  testLsWithSpawn();

  return 0;
}

Conclusion :

Toutes les remarques constructives sont les bienvenues.

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.