HOWTO · C++

La fonction system() en C++

Cet article explique comment utiliser la fonction `system()` de la bibliothèque standard C++.

Sur cette page

Ce guide explique comment utiliser la fonction system() de la bibliothèque standard C++ pour exécuter une commande shell, vérifier si un interpréteur de commandes est disponible et interpréter le résultat. Il explique aussi pourquoi les commandes shell conviennent mal aux entrées non fiables et quand préférer une API de gestion des processus.

Rôle de system() et cas d’utilisation

std::system est déclarée dans <cstdlib>. Elle transmet une chaîne de commande à l’interpréteur de commandes de l’environnement hôte et attend de façon synchrone que celui-ci se termine. Pendant cette attente, le thread appelant est bloqué : une commande interactive telle que top laisse donc le programme C++ en pause jusqu’à ce que l’utilisateur quitte le programme.

C’est un moyen concis d’appeler une commande lorsque la chaîne est fixe et que le programme peut attendre. Cela ne revient pas à créer et gérer directement un processus : system() confie l’analyse de la commande à un shell et masque une grande partie de la création et du nettoyage du processus. Cette commodité ajoute un surcoût et rend le comportement dépendant de l’interpréteur de commandes de l’environnement hôte. Sous POSIX, des solutions de plus bas niveau comprennent posix_spawn et la famille fork/exec ; sous Windows, les programmes utilisent souvent CreateProcess lorsqu’ils ont besoin d’un contrôle direct sur les processus.

Exécuter une commande shell avec system()

L’exemple suivant est destiné à un environnement de type POSIX doté d’un shell qui fournit printf. La commande écrit une ligne, et system() attend la fin de la commande shell avant que main ne se termine. La chaîne de commande est fixe : aucune entrée utilisateur n’est donc interprétée comme une syntaxe shell.

Exécutez cet exemple :

Le code et les libellés ou sorties affichés dans cette section restent en anglais.

#include <cstdlib>
#include <iostream>

int main() {
  const int status = std::system("printf 'Hello from the shell\n'");
  if (status == -1) {
    std::cout << "system() could not start the command processor\n";
    return EXIT_FAILURE;
  }
  return EXIT_SUCCESS;
}

La commande s’exécute dans le répertoire de travail actuel du processus ; ses chemins relatifs sont donc résolus à partir de ce répertoire. Le shell et la commande doivent être présents dans l’environnement, et la syntaxe des commandes varie selon le système d’exploitation. Par exemple, ls et top sont courantes sur les systèmes de type Unix, mais ne sont pas des commandes C++ portables. L’exemple avec top est interactif et attend volontairement que l’utilisateur quitte le programme ; évitez de le placer dans un programme sans surveillance, sauf si ce comportement est souhaité.

Vérifier la disponibilité du shell avec system(nullptr)

Passez nullptr pour vérifier si un interpréteur de commandes est disponible. Un résultat non nul indique qu’il en existe un ; zéro signifie qu’il n’y en a pas. Cette vérification ne permet pas de savoir si une commande précise existe, si sa syntaxe est acceptée ni si son exécution réussira. Le résultat dépend de l’environnement hôte.

Le code affiche « A command processor is available. » si un interpréteur de commandes est disponible, et « No command processor is available. » dans le cas contraire. Ces deux chaînes restent en anglais dans l’exemple ; elles ne sont pas localisées.

#include <cstdlib>
#include <iostream>

int main() {
  if (std::system(nullptr) != 0) {
    std::cout << "A command processor is available.\n";
  } else {
    std::cout << "No command processor is available.\n";
  }
  return 0;
}

Utilisez cette vérification pour connaître l’environnement, et non pour remplacer le traitement du résultat de la commande que vous exécutez réellement. La disponibilité peut aussi changer entre la vérification et l’appel de la commande.

Interpréter la valeur de retour et le statut de la commande

Une valeur de retour égale à -1 indique que l’interpréteur de commandes n’a pas pu démarrer ou que son statut n’a pas pu être obtenu ; sous POSIX, errno fournit des informations supplémentaires sur l’erreur. Toute autre valeur correspond au statut de terminaison de l’interpréteur de commandes, dans le format utilisé par la plateforme. Ce n’est pas simplement un indicateur booléen de réussite, ni nécessairement le code de sortie brut d’une commande exécutée par le shell.

Sous POSIX, incluez <sys/wait.h> et utilisez WIFEXITED avant d’appeler WEXITSTATUS. Ces macros interprètent le statut d’attente renvoyé par system() : WIFEXITED indique si le shell s’est terminé normalement, et WEXITSTATUS extrait son code de sortie uniquement dans ce cas. Si le shell a été interrompu par un signal, WIFSIGNALED et WTERMSIG permettent d’identifier cette situation et le signal correspondant. Ces macros sont propres à POSIX ; ne supposez pas qu’elles sont disponibles ou qu’elles ont la même signification sur d’autres plateformes.

Cet exemple POSIX demande au shell de se terminer avec le statut 7. Si le shell se termine normalement, le programme affiche le statut extrait ; il traite les autres résultats sans confondre le statut d’attente avec un code de sortie.

Le code et les libellés ou sorties affichés dans cette section restent en anglais.

#include <cstdlib>
#include <iostream>
#include <sys/wait.h>

int main() {
  const int status = std::system("exit 7");
  if (status == -1) {
    std::cout << "Could not start the command processor.\n";
    return EXIT_FAILURE;
  }

  if (WIFEXITED(status)) {
    std::cout << "Shell exit status: " << WEXITSTATUS(status) << '\n';
  } else if (WIFSIGNALED(status)) {
    std::cout << "Shell ended from signal " << WTERMSIG(status) << '\n';
  } else {
    std::cout << "Shell ended in another state.\n";
  }
  return 0;
}

Le statut du shell reflète la façon dont il a traité la chaîne de commande. Les pipelines et les commandes composées suivent les règles de statut définies par le shell ; cette valeur ne doit donc pas être présentée comme le statut de chacun des processus que le shell a pu démarrer. Pour une logique applicative portable, utilisez l’API de gestion des processus de la plateforme et examinez le format de résultat qu’elle documente.

Utiliser system() en toute sécurité et connaître ses limites

Ne concaténez jamais du texte non fiable dans une commande transmise à system(). Les métacaractères du shell, les substitutions et les règles de citation peuvent transformer des données en commandes supplémentaires et créer des vulnérabilités d’injection de commandes. Il est difficile d’échapper correctement à des entrées arbitraires, et les règles varient selon le shell. Une chaîne de commande fixe évite cette source particulière d’injection, mais ne rend pas l’exécution par le shell adaptée à toutes les applications.

Lorsque des arguments comprennent des valeurs contrôlées par l’utilisateur, préférez une API qui démarre directement un programme et transmet les arguments séparément, comme posix_spawn sous POSIX ou CreateProcess sous Windows, en traitant soigneusement les arguments. Vérifiez aussi s’il existe une API de bibliothèque pour l’opération concernée : éviter le shell améliore souvent la portabilité, la gestion des erreurs et le contrôle de la durée de vie du processus. Utilisez system() uniquement si l’appel à l’interpréteur de commandes de l’environnement hôte est intentionnel, si la chaîne de commande est fiable et si une attente synchrone est acceptable.