Comment lancer des processus externes avec Python et le module de sous-processus

Comment lancer des processus externes avec Python et le module de sous-processus

Dans nos scripts d'automatisation, nous devons souvent lancer et surveiller les programmes externes pour accomplir nos tâches souhaitées. Lorsque vous travaillez avec Python, nous pouvons utiliser le module de sous-processus pour effectuer lesdites opérations. Ce module fait partie de la bibliothèque standard du langage de programmation. Dans ce tutoriel, nous y examinerons rapidement, et nous apprendrons les bases de son utilisation.

Dans ce tutoriel, vous apprendrez:

  • Comment utiliser la fonction «Exécuter» pour engendrer un processus externe
  • Comment capturer une sortie standard du processus et une erreur standard
  • Comment vérifier l'état d'existence d'un processus et augmenter une exception s'il échoue
  • Comment exécuter un processus dans un shell intermédiaire
  • Comment définir un temps mort pour un processus
  • Comment utiliser la classe POPEN directement pour tuer deux processus
Comment lancer des processus externes avec Python et le module de sous-processus

Exigences et conventions logicielles utilisées

Exigences logicielles et conventions de ligne de commande Linux
Catégorie Exigences, conventions ou version logicielle utilisée
Système Distribution indépendante
Logiciel Python3
Autre Connaissance du python et de la programmation orientée objet
Conventions # - nécessite que les commandes Linux sont exécutées avec des privilèges racine soit directement en tant qu'utilisateur racine, soit par l'utilisation de Sudo commande
$ - Exige que les commandes Linux sont exécutées en tant qu'utilisateur non privilégié régulier

La fonction «exécuter»

Le courir La fonction a été ajoutée au sous-processus Module uniquement dans des versions relativement récentes de Python (3.5). L'utiliser est désormais le moyen recommandé de purger les processus et devrait couvrir les cas d'utilisation les plus courants. Avant tout le reste, voyons son utilisation la plus simple. Supposons que nous voulons exécuter le ls -al commande; Dans un shell python, nous courions:

>>> Importer Sous-processus >>> Processus = sous-processus.run (['ls', '-l', '-a']) 

La sortie de la commande externe s'affiche à l'écran:

Total 132 DRWX------. 22 egdoc egdoc 4096 nov 30 12:18 . drwxr-xr-x. 4 racine racine 4096 22 nov 13: 11… -rw-------. 1 egdoc egdoc 10438 décembre 1 12:54 .bash_history -rw-r - r--. 1 egdoc egdoc 18 juil .BASH_LOGOUT […] 

Ici, nous venons d'utiliser le premier argument obligatoire accepté par la fonction, qui peut être une séquence qui «décrit» une commande et ses arguments (comme dans l'exemple) ou une chaîne, qui doit être utilisée lors de l'exécution avec le shell = vrai Argument (nous le verrons plus tard).

Capturer la commande stdout et stderr

Et si nous ne voulons pas que la sortie du processus soit affichée à l'écran, mais plutôt capturée, il peut donc être référencé après la sortie du processus? Dans ce cas, nous pouvons définir le capture_output argument de la fonction de Vrai:

>>> procédé = sous-processus.run (['ls', '-l', '-a'], capture_output = true) 

Comment pouvons-nous récupérer la sortie (STDOUT et STDERR) du processus par la suite? Si vous observez les exemples ci-dessus, vous pouvez voir que nous avons utilisé le processus variable pour référencer ce qui est renvoyé par le courir Fonction: A Procédé complet objet. Cet objet représente le processus qui a été lancé par la fonction et possède de nombreuses propriétés utiles. Parmi les autres, stdout et stderr sont utilisés pour «stocker» les descripteurs correspondants de la commande si, comme nous l'avons dit, le capture_output L'argument est défini sur Vrai. Dans ce cas, pour obtenir le stdout du processus que nous dirigerions:

>>> processus.stdout 

Stdout et stderr sont stockés comme séquences octets par défaut. Si nous voulons qu'ils soient stockés comme des cordes, nous devons définir le texte argument du courir fonctionner à Vrai.



Gérer une défaillance du processus

La commande que nous avons exécutée dans les exemples précédents a été exécutée sans erreur. Lors de la rédaction d'un programme, cependant, tous les cas devraient être pris en compte, alors que se passe-t-il si un processus engendré échoue? Par défaut, rien de «spécial» ne se produirait. Voyons un exemple; Nous dirigeons le LS Commande à nouveau, en essayant de répertorier le contenu du /racine Répertoire, qui normalement, sur Linux n'est pas lisible par les utilisateurs normaux:

>>> procédé = sous-processus.run (['ls', '-l', '-a', '/ root']) 

Une chose que nous pouvons faire pour vérifier si un processus lancé a échoué, c'est de vérifier son statut d'existence, qui est stocké dans le Code de retour propriété du Procédé complet objet:

>>> processus.Code de retour 2 

Voir? Dans ce cas, le Code de retour était 2, confirmant que le processus a rencontré un problème d'autorisation et n'a pas été achevé avec succès. Nous pourrions tester la sortie d'un processus de cette façon, ou plus élégamment, nous pourrions faire pour qu'une exception soit augmentée lorsqu'un échec se produit. Entrer le vérifier argument du courir Fonction: quand il est défini sur Vrai Et un processus engendré échoue, le Appeléprocesserror L'exception est élevée:

>>> procédé = sous-processus.run (['ls', '-l', '-a', '/ root'], check = true) ls: Impossible d'ouvrir le répertoire '/ root': autorisation refusée trace (le dernier appel dernier): fichier "" , ligne 1, dans le fichier "/ usr / lib64 / python3.9 / sous-processus.py ", ligne 524, dans une augmentation de run appeléProcesserror (retcode, processus.Args, sous-processus.AppeléProcessError: commande '[' ls ',' -l ',' -a ',' / root ']' Renvoyé l'état de sortie non nul 2. 

Manutention des exceptions Dans Python est assez facile, donc pour gérer un échec de processus, nous pourrions écrire quelque chose comme:

>>> Essayez:… processus = sous-processus.run (['ls', '-l', '-a', '/ root'], check = true)… sauf le sous-processus.AppeléProcessError comme e:… # juste un exemple, quelque chose d'utile pour gérer l'échec doit être effectué!… Imprimer (f "e.CMD a échoué!")… LS: Impossible d'ouvrir le répertoire '/ root': autorisation refusée ['ls', '-l', '-a', '/ root'] a échoué! >>> 

Le Appeléprocesserror Une exception, comme nous l'avons dit, est élevée lorsqu'un processus sort avec un non 0 statut. L'objet a des propriétés comme Code de retour, CMD, stdout, stderr; Ce qu'ils représentent est assez évident. Dans l'exemple ci-dessus, par exemple, nous venons d'utiliser le CMD propriété, pour signaler la séquence qui a été utilisée pour décrire la commande et ses arguments dans le message que nous avons écrit lorsque l'exception s'est produite.

Exécuter un processus dans un shell

Les processus lancés avec le courir fonction, sont exécutés «directement», cela signifie qu'aucun shell n'est utilisé pour les lancer: aucune variable d'environnement n'est donc disponible pour le processus et que les extensions du shell ne sont pas effectuées. Voyons un exemple qui implique l'utilisation du $ Home variable:

>>> procédé = sous-processus.run (['ls', '-al', '$ home']) ls: impossible d'accéder à '$ home': pas de fichier ou de répertoire de ce type 

Comme vous pouvez le voir $ Home La variable n'a pas été élargie. Exécuter les processus de cette façon est recommandé pour éviter les risques de sécurité potentiels. Si dans certains cas, cependant, nous devons invoquer un shell en tant que processus intermédiaire, nous devons définir le coquille paramètre du courir fonctionner à Vrai. Dans de tels cas, il est préférable de spécifier la commande à exécuter et ses arguments en tant que chaîne:

>>> procédé = sous-processus.run ('ls -al $ home', shell = true) total 136 drwx------. 23 Egdoc Egdoc 4096 3 décembre 09:35 . drwxr-xr-x. 4 racine racine 4096 22 nov 13: 11… -rw-------. 1 Egdoc Egdoc 11885 Dec 3 09:35 .bash_history -rw-r - r--. 1 egdoc egdoc 18 juil .BASH_LOGOUT […] 

Toutes les variables existant dans l'environnement utilisateur peuvent être utilisées lors de l'invoquer un shell comme processus intermédiaire: bien que cela puisse sembler pratique, il peut être une source de problèmes, en particulier lorsqu'il s'agit d'une entrée potentiellement dangereuse, ce qui pourrait conduire à injections de coquille. Exécuter un processus avec shell = vrai est donc découragé et ne doit être utilisé que dans des cas sûrs.



Spécifiant un délai d'expiration pour un processus

Nous ne voulons généralement pas que les processus de mauvaise conduite s'exécutent pour toujours sur notre système une fois qu'ils sont lancés. Si nous utilisons le temps libre paramètre du courir fonction, nous pouvons spécifier une durée en quelques secondes que le processus devrait prendre pour terminer. S'il n'est pas terminé dans ce laps de temps, le processus sera tué avec un Sigkill signal, qui, comme nous le savons, ne peut pas être pris par un processus. Voyons-le en engendrant un processus de longue date et en fournissant un délai d'expiration en quelques secondes:

>>> procédé = sous-processus.courir ([«ping», «Google.com '], timeout = 5) ping google.com (216.58.206.46) 56 (84) octets de données. 64 octets de Mil07S07-in-F14.1E100.net (216.58.206.46): ICMP_SEQ = 1 TTL = 113 Time = 29.3 ms 64 octets de LHR35S10-in-F14.1E100.net (216.58.206.46): ICMP_SEQ = 2 TTL = 113 Time = 28.3 ms 64 octets de LHR35S10-in-F14.1E100.net (216.58.206.46): ICMP_SEQ = 3 TTL = 113 Time = 28.5 ms 64 octets de LHR35S10-in-F14.1E100.net (216.58.206.46): ICMP_SEQ = 4 TTL = 113 Time = 28.5 ms 64 octets de LHR35S10-in-F14.1E100.net (216.58.206.46): ICMP_SEQ = 5 TTL = 113 Time = 28.1 MS Traceback (dernier appel dernier): fichier "", ligne 1, dans le fichier "/ usr / lib64 / python3.9 / sous-processus.py ", ligne 503, dans run stdout, stderr = process.communiquer (entrée, timeout = timeout) fichier "/ usr / lib64 / python3.9 / sous-processus.py ", ligne 1130, en communication stdout, stderr = self._COMMUNICATIVE (entrée, fin, délai d'attente) Fichier "/ usr / lib64 / python3.9 / sous-processus.py ", ligne 2003, dans _communicate self.Attendez (temps mort = soi._reMaining_time (Fintime)) Fichier "/ usr / lib64 / python3.9 / sous-processus.py ", ligne 1185, en attente._Wait (timeout = timeout) fichier "/ usr / lib64 / python3.9 / sous-processus.py ", ligne 1907, dans _Ait augmenter le tempsoutexpired (self.args, délai d'attente) sous-processus.Timeatexpired: Commande '[' ping ',' Google.com ']' chronométré après 4.999826977029443 secondes 

Dans l'exemple ci-dessus, nous avons lancé le ping-ping commande sans spécifier une quantité fixe de Demande d'écho paquets, donc il pourrait potentiellement fonctionner pour toujours. Nous avons également spécifié un délai de temps 5 secondes via le temps libre paramètre. Comme nous pouvons observer le programme initialement couru, mais le TimeoutExpired Une exception a été soulevée lorsque le montant spécifié de secondes a été atteint, et le processus a été tué.

Les fonctions Call, Check_Output et Check_Call

Comme nous l'avons dit précédemment, le courir La fonction est le moyen recommandé d'exécuter un processus externe et devrait couvrir la majorité des cas. Avant qu'il ne soit introduit dans Python 3.5, les trois principales fonctions d'API de haut niveau utilisées pour lancer un processus étaient appel, check_output et check_call; Voyons-les brièvement.

Tout d'abord, le appel Fonction: il est utilisé pour exécuter la commande décrite par le args paramètre; il attend que la commande soit terminée et renvoie son Code de retour. Il correspond à peu près à l'utilisation de base du courir fonction.

Le check_call Le comportement de la fonction est pratiquement le même de celui du courir fonction quand le vérifier Le paramètre est défini sur Vrai: il exécute la commande spécifiée et attend qu'il termine. Si son statut existant n'est pas 0, un Appeléprocesserror L'exception est élevée.

Finalement, le check_output Fonction: il fonctionne de manière similaire à check_call, mais Retour la sortie du programme: il ne s'affiche pas lorsque la fonction est exécutée.

Travailler à un niveau inférieur avec la classe POPEN

Jusqu'à présent, nous avons exploré les fonctions d'API de haut niveau dans le module de sous-processus, en particulier courir. Toutes ces fonctions, sous le capot, interagissent avec le Pivoter classe. Pour cette raison, dans la grande majorité des cas, nous n'avons pas à travailler avec lui directement. Cependant, lorsque plus de flexibilité est nécessaire, créant Pivoter Les objets deviennent directement nécessaires.



Supposons, par exemple, nous voulons connecter deux processus, recréant le comportement d'un «tuyau» de coquille. Comme nous le savons, lorsque nous gardons deux commandes dans la coque, la sortie standard de celle du côté gauche du tuyau (|) est utilisé comme entrée standard de celui à droite (consultez cet article sur les redirections de coquille si vous voulez en savoir plus sur le sujet). Dans l'exemple ci-dessous, le résultat de la tuyauterie, les deux commandes sont stockées dans une variable:

$ output = "$ (dmesg | grep sda)" 

Pour imiter ce comportement en utilisant le module de sous-processus, sans avoir à définir le coquille paramètre Vrai Comme nous l'avons vu auparavant, nous devons utiliser le Pivoter classe directement:

DMESG = sous-processus.POPEN (['DMESG'], Stdout = sous-processus.Tuyau) grep = sous-processus.POPEN (['grep', 'sda'], stdin = dmesg.stdout) dmesg.stdout.ferme () sortie = grep.comunicate () [0] 

Pour comprendre l'exemple ci-dessus, nous devons nous rappeler qu'un processus a commencé par utiliser le Pivoter La classe ne bloque pas directement l'exécution du script, car il est maintenant attendu.

La première chose que nous avons faite dans l'extrait de code ci-dessus, a été de créer le Pivoter objet représentant le dmesg processus. Nous définissons le stdout de ce processus à sous-processus.TUYAU: cette valeur indique qu'un tuyau au flux spécifié doit être ouvert.

Nous avons créé une autre instance du Pivoter classe pour le grep processus. Dans le Pivoter Constructeur, nous avons spécifié la commande et ses arguments, bien sûr, mais, voici la partie importante, nous avons défini la sortie standard du dmesg processus à utiliser comme entrée standard (stdin = dmesg.stdout), donc pour recréer la coquille
Comportement des tuyaux.

Après avoir créé le Pivoter objet pour le grep commande, nous avons fermé le stdout flux du dmesg processus, en utilisant le fermer() Méthode: Ceci, comme indiqué dans la documentation, est nécessaire pour permettre au premier processus de recevoir un signal SigPipe. Essayons d'expliquer pourquoi. Normalement, lorsque deux processus sont connectés par un tuyau, si celui à droite du tuyau (grep dans notre exemple) devant celui à gauche (DMESG), ce dernier reçoit un Tuyau
signal (tuyau cassé) et par défaut, se termine.

Lorsque vous reproduisant le comportement d'un tuyau entre deux commandes dans Python, cependant, il y a un problème: le stdout du premier processus est ouvert à la fois dans le script parent et dans la saisie standard de l'autre processus. De cette façon, même si le grep Le processus se termine, le tuyau restera toujours ouvert dans le processus de l'appelant (notre script), donc le premier processus ne recevra jamais le Tuyau signal. C'est pourquoi nous devons fermer le stdout flux du premier processus de notre
script principal après avoir lancé le deuxième.

La dernière chose que nous avons faite a été d'appeler le communiquer() Méthode sur le grep objet. Cette méthode peut être utilisée pour transmettre éventuellement l'entrée à un processus; il attend que le processus se termine et renvoie un tuple où le premier membre est le processus stdout (qui est référencé par le sortir variable) et la seconde le processus stderr.

Conclusions

Dans ce tutoriel, nous avons vu le moyen recommandé de purger les processus externes avec Python en utilisant le sous-processus module et le courir fonction. L'utilisation de cette fonction devrait être suffisante pour la majorité des cas; Cependant, lorsqu'un niveau de flexibilité plus élevé est nécessaire, il faut utiliser le Pivoter classe directement. Comme toujours, nous suggérons de jeter un œil au
Documentation de sous-processus pour un aperçu complet de la signature des fonctions et des classes disponibles en
le module.

Tutoriels Linux connexes:

  • Une introduction à l'automatisation Linux, des outils et des techniques
  • Masterring Bash Script Loops
  • Choses à installer sur Ubuntu 20.04
  • Boucles imbriquées dans les scripts bash
  • Comment utiliser la commande tcpdump sur Linux
  • Mint 20: Mieux que Ubuntu et Microsoft Windows?
  • Gestion de la saisie des utilisateurs dans les scripts bash
  • Tutoriel de débogage GDB pour les débutants
  • Choses à faire après l'installation d'Ubuntu 20.04 Focal Fossa Linux
  • Comment surveiller l'activité du réseau sur un système Linux