[PYTHON] En savoir plus sur le sous-processus (série 3, version mise à jour)

introduction

Article écrit en 2017 est basé sur 2 systèmes, et j'ai pensé qu'il serait préférable de mettre à jour les informations bâclées, et par conséquent, j'ai commencé à éditer Cet article a été nouvellement créé car il semblait qu'un grand nombre d'ajouts tels que subprocess.run () seraient nécessaires.

Comme objectif, tout en expliquant la fonction de sous-processus d'avant (le support n'est pas terminé), nous la décrirons de manière synonyme de subprocess.run () et subprocess.Popen (). De plus, nous aborderons une plus grande variété de descriptions en les utilisant.

Si vous êtes pressé, il n'y a pas de problème si vous lisez les règles communes de description de cmd et run () et versions ultérieures.

L'histoire de ce que fait le module en premier lieu est Officiel ou [Cet article](https://qiita.com/ Veuillez vous référer à caprest / items / 0245a16825789b0263ad).

Quelle est la conclusion? ** Laissons tout à subprocess.run () autant que possible. Si un traitement plus compliqué est requis, utilisez subprocess.Popen (), qui est également la base de subprocess.run (). ** Pour autant que je sache, aucune autre fonction introduite dans cet article n'est nécessaire (toutes peuvent être remplacées).

Module utilisé


import io
import os
import time
import subprocess

Dossier expérimental

hello.py


print('Hello world!')

Ancienne méthode d'exécution de la commande shell

Je n'ai plus la chance de l'utiliser, mais je vais le résumer car cela aidera lors de l'excavation de l'ancien code.

os.system()

ʻOs.system (commande) `(Officiel) Exécutez la commande (chaîne) dans le sous-shell. Cette fonction est implémentée en utilisant le système de fonction C standard () et a les mêmes restrictions que system (). Les modifications apportées à sys.stdin etc. ne seront pas reflétées dans l'environnement de la commande exécutée. (Omis) Le module de sous-processus fournit des fonctionnalités plus puissantes pour exécuter de nouveaux processus et obtenir des résultats. Il est recommandé d'utiliser le module de sous-processus au lieu de cette fonction.

os.system()Exemple


#Le résultat est sorti sur le terminal
print(os.system('ls')) # 0

#Sur le terminal → sh: 1: tekito: not found
print(os.system('tekito')) #32512 (← dépendant de l'environnement?Renvoie un nombre différent de 0)

Fonctions ʻOs.spawn(ʻos.spawnl (mode, chemin, ...), ʻos.spawnle (mode, chemin, ..., env), ʻos.spawnlp (mode, fichier ,. ..) , ʻos.spawnlpe (mode, fichier, ..., env), ʻos.spawnv (mode, chemin, args) , ʻos.spawnve (mode, chemin, args, env), ʻOs.spawnvp (mode, fichier, args) , ʻos.spawnvpe (mode, fichier, args, env)`) est ~~ Je ne comprends pas ~~ Il semble avoir une nuance telle que "(processus) est créé").

os.popen()

ʻOs.popen (cmd, mode = 'r', buffering = -1) `(Officiel) Ouvrez l'entrée / sortie du tube vers ou depuis la commande cmd. La valeur de retour est un objet fichier ouvert connecté au tube qui peut être lu ou écrit selon que le mode est «r» (par défaut) ou «w». (Omis) Il est implémenté à l'aide de subprocess.Popen. Consultez la documentation de la classe pour des moyens plus puissants de gérer et de communiquer avec les sous-processus.

os.popen()Exemple


#Lire le résultat de sortie
print(os.popen('ls h*.py','r').readlines())
'''
['hello.py\n', 'hello2.py\n']
'''

Règles communes pour la description de cmd dans le sous-processus

Donner comme chaîne de caractères → shell = True ・ Risque de dysfonctionnement dû aux devis Donner comme une liste de chaînes → shell = False (par défaut) ・ Faible degré de liberté car les jokers ne peuvent pas être utilisés

(Officiel) Si shell vaut True, la commande spécifiée sera exécutée par le shell. Vous utilisez principalement Python pour des flux de contrôle améliorés (plus que la plupart des shells système), ainsi que des tubes shell, des caractères génériques de nom de fichier, l'expansion des variables d'environnement et l'expansion vers votre répertoire de base utilisateur. Cela peut être utile si vous souhaitez accéder facilement à d'autres fonctionnalités du shell. (Officiel) Si vous appelez explicitement le shell avec shell = True, il est de la responsabilité de l'application de garantir des guillemets corrects pour tous les espaces et métacaractères afin de résoudre la vulnérabilité d'injection de shell.

shell=Commandes qui ne peuvent être réalisées qu'avec True


#Comment donner dans une liste de chaînes (les 5 suivants ne sont pas possibles avec cette notation)
print(subprocess.call(['ls','-l'], shell=False)) # 0

#Pipeline Shell
print(subprocess.call('echo -e "a\nb\nc" | wc -l', shell=True)) # 0
#point virgule
print(subprocess.call('echo Failed.; exit 1', shell=True)) # 1
#Caractère générique
print(subprocess.call('ls -l *.py', shell=True)) # 0
#Variable d'environnement
print(subprocess.call('echo $HOME', shell=True)) # 0
#Symbole Tilda pour HOME
print(subprocess.call('ls -l ~', shell=True)) # 0

Fonctions avant intégration par subprocess.run ()

Il existe trois types illustrés ci-dessous. Ils sont grossièrement classés dans les trois éléments du tableau ci-dessous. Dans les deux cas, ON / OFF peut être commuté dans subprocess.run (), donc ce n'est plus nécessaire.

Nom de la fonction Statut de résiliation Résultat de sortie CalledProcessError
(.run()Argument / attribut) .returncode .stdout check
subprocess.call()
subprocess.check_call()
subprocess.check_output()

subprocess.call()

subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None) Exécution de la commande. La valeur de retour est l'état de fin (0 signifie fin normale).

Options principales ① (entrée / sortie standard, timeout)

** (commun à d'autres fonctions de sous-processus) **

Lorsque vous spécifiez un fichier pour une entrée / sortie standard, placez-le dans ʻopen () . C'est presque un aparté, mais dans la série Python3, il n'y a presque aucun problème si vous sélectionnez le type d'octets (mode = 'rb' / 'wb') plutôt que le type str ( mode =' r '/' w') (mode = 'rb' / 'wb'`). Comme vous pouvez le voir en l'examinant, les deux sont pris en charge, ou seul le type d'octets est pris en charge dans de nombreux cas).

Exemples d'utilisation des options clés


print(subprocess.call(['cat', 'hello.py'])) # 0
#Spécifiez l'entrée standard / la sortie standard (la seconde est hello2.py est créé)
print(subprocess.call(['cat'], stdin=open('hello.py','rb'))) # 0
print(subprocess.call(['cat', 'hello.py'], stdout=open('hello2.py','wb'))) # 0

#Spécifiez le délai d'expiration
print(subprocess.call(['sleep', '3'], timeout=1)) # TimeoutExpired

#(Supplément) Même avec l'option cwd qui spécifie le répertoire d'exécution~N'est pas pris en charge.
print(subprocess.call(['ls','-l'], shell=False, cwd = "~")) # FileNotFoundError

subprocess.check_call()

subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None) Exécution de la commande. La valeur de retour est l'état de fin (0 signifie fin normale). Renvoie «CalledProcessError» à une fin anormale.

check_call()Exemple d'utilisation


#Réussite
print(subprocess.call(['cat', 'hello.py'])) # 0
print(subprocess.check_call(['cat', 'hello.py'])) # 0

#Terminaison anormale: renvoie une erreur d'exception au lieu d'un état d'erreur
print(subprocess.call(['cat', 'undefined.py'])) # 1
print(subprocess.check_call(['cat', 'undefined.py'])) # CalledProcessError

subprocess.check_output()

subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, cwd=None, encoding=None, errors=None, universal_newlines=None, timeout=None, text=None) Exécution de la commande. La valeur de retour est la sortie standard.

(Officiel) Par défaut, cette fonction renvoie les données sous forme d'octets codés.

Par conséquent, s'il s'agit d'une chaîne de caractères, etc., elle est décodée puis traitée.

check_output()Exemple d'utilisation


o = subprocess.check_output('ls h*.py', shell=True)
print(o) # b'hello.py\nhello2.py\n'
print(o.decode().strip().split('\n')) # ['hello.py', 'hello2.py']
Option principale (2) (sortie d'erreur standard, valeur spéciale)

** (commun à d'autres fonctions de sous-processus) **

La destination de sortie peut être modifiée en donnant à stderr.

--subprocess.DEVNULL: Spécifiez la destination d'entrée / sortie standard comme os.devnull (compartiment de bits, trou noir) --subprocess.PIPE: Spécifiez le tube vers la destination d'entrée / sortie standard --subprocess.STDOUT: Spécifiez que la sortie d'erreur standard est sortie vers le même handle que la sortie standard (uniquement pour 2> 1 & .stderr)

Opérations liées à la sortie d'erreur standard


#Supprimer toutes les sorties
o = subprocess.call(['cat', 'undefined.py'], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)

#Sortez la sortie standard
try:
    o = subprocess.check_output(['cat', 'hello.py'])
    print(o) # b"print('Hello world!')"
except subprocess.CalledProcessError as e:
    print('ERROR:', e.output)

#Extraire la sortie d'erreur standard
try:
    o = subprocess.check_output(['cat', 'undefined.py'], stderr=subprocess.PIPE)
    print(o)
except subprocess.CalledProcessError as e:
    print('ERROR:', e.stderr) # ERROR: b'cat: undefined.py: No such file or directory\n'

#Intégration des erreurs standard dans la sortie standard
try:
    o = subprocess.check_output(['cat', 'undefined.py'], stderr=subprocess.STDOUT)
    print(o)
except subprocess.CalledProcessError as e:
    # e.e au lieu de stderr.Notez qu'il s'agit de stdout. En outre, e.La sortie est également possible.
    print('ERROR:', e.stdout) # ERROR: b'cat: undefined.py: No such file or directory\n'

Remplacement par subprocess.run ()

(Officiel) La méthode recommandée pour démarrer un sous-processus est d'utiliser la fonction run (), qui peut gérer toutes les utilisations. Pour une utilisation plus avancée, vous pouvez également utiliser directement l'interface Popen sous-jacente.

Donc, remplacez le code ci-dessus par run (). Voir également le tableau ci-dessus pour les options spécifiées.

subprocess.run()

subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None)

Remplacement de Subprocess.call ()

Ajoutez .return code. En plus du stdin utilisé lors de l'introduction de subprocess.call (), il existe une option appelée ʻinput. Il s'agit d'une spécification d'entrée standard utilisant subprocess.Popen (). Communicate ()` décrite plus loin, et spécifie une chaîne de caractères.

run()Remplacement: exemple d'utilisation d'une option clé


print(subprocess.run('ls -l h*.py', shell=True).returncode) # 0
#Spécifiez l'entrée standard / la sortie standard (hello2).py est créé)
print(subprocess.run(['cat'], stdin=open('hello.py','rb'), stdout=open('hello2.py','wb')).returncode) # 0
# (Utilisez l'entrée. Presque synonyme de ci-dessus)
print(subprocess.run(['cat'], input=open('hello.py','rb').read(), stdout=open('hello2.py','wb')).returncode) # 0

#Spécifiez le délai d'expiration
print(subprocess.run(['sleep', '3'], timeout=1).returncode) # TimeoutExpired
Subprocess.check_call () remplacement

Ajoutez check = True. Il existe aussi une méthode plus récente appelée .check_returncode ().

run()Remplacer: vérifier_call()Exemple d'utilisation


#Statut de réussite
print(subprocess.run(['cat', 'hello.py']).returncode) # 0
print(subprocess.run(['cat', 'hello.py'], check=True).returncode) # 0

#Renvoie une erreur d'exception au lieu d'un statut d'erreur
print(subprocess.run(['cat', 'undefined.py']).returncode) # 1
print(subprocess.run(['cat', 'undefined.py'], check=True).returncode) # CalledProcessError

#(Supplément) chèque_returncode()Sortie d'erreur ultérieure en utilisant
p = subprocess.run(['cat', 'undefined.py'], check=False)
print(p.returncode) # 1
print(p.check_returncode()) # CalledProcessError
Subprocess.check_output () remplacement

Utilisez l'option stdout et .stdout.

run()Remplacer: vérifier_output()Exemple d'utilisation


o = subprocess.run('ls h*.py', shell=True, stdout=subprocess.PIPE, check=True).stdout
print(o) # b'hello.py\nhello2.py\n'
print(o.decode().strip().split('\n')) # ['hello.py', 'hello2.py']

Avec la nouvelle fonction optionnelle, les opérations liées à la sortie d'erreur standard ont plus de liberté lors de l'utilisation de run () que lors de l'utilisation de check_output ().

Opérations liées à la sortie d'erreur standard


# check=False (default)Ainsi, vous pouvez regarder dans strerr sans vous arrêter avec une erreur
o = subprocess.run(['cat', 'undefined.py'], check=False, capture_output=True)
print((o.stdout, o.stderr)) # (b'', b'cat: undefined.py: No such file or directory\n')

#Intégrez la sortie d'erreur standard dans la sortie standard.
o = subprocess.run(['cat', 'undefined.py'], check=False, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
print(o.stdout) # b'cat: undefined.py: No such file or directory\n'

# capture_output=Si Vrai, PIPE est spécifié pour la sortie standard et la sortie d'erreur standard.
try:
    o = subprocess.run(['cat', 'undefined.py'], check=True, capture_output=True)
    print(o.stdout)
except subprocess.CalledProcessError as e:
    print('ERROR:',e.stderr) # ERROR: b'cat: undefined.py: No such file or directory\n'

Traitement extrêmement flexible par subprocess.Popen ()

Officiel Dans ce module, la création et la gestion des processus sous-jacents sont gérées par la classe Popen. La classe Popen offre beaucoup de flexibilité afin que les développeurs puissent gérer des cas moins courants qui ne sont pas couverts par des fonctions simples.

Donc, une introduction à la flexibilité. En particulier, nous nous concentrerons sur les choses qui ne peuvent pas être reproduites avec run (). En tant que fonctionnalité majeure, subprocess.Popen () ne crée qu'un processus ** et n'attend pas la fin **. Utilisez .poll () pour confirmer la fin et pour continuer après avoir confirmé la fin. Exécutez .wait ().

Exemple de remplacement (call ())

** De nombreux arguments et attributs de Popen () sont communs à run (). ** ** Pour reproduire subprocess.call (), utilisez .return code qui était également utilisé dans subprocess.run (). Lors de la reproduction d'un autre code, cela peut être fait de la même manière que lorsque run ().

Popen()Remplacer: appeler()Exemple d'utilisation


#Spécifiez l'entrée standard / la sortie standard (la seconde est hello2.py est créé)
p = subprocess.Popen('ls -l h*.py', shell=True)
p.wait()
print(p.returncode) # 0

#Spécifiez l'entrée standard / la sortie standard (hello2).py est créé)
p = subprocess.Popen(['cat'], stdin=open('hello.py','rb'), stdout=open('hello2.py','wb'))
p.wait()
print(p.returncode) # 0

#Spécifiez le délai d'expiration
p = subprocess.Popen(['sleep', '3'])
p.wait(timeout=1) # TimeoutExpired

Deadlock et Popen.communicate ()

Popen.communicate () est une méthode qui renvoie un taple sous la forme de (sortie standard, sortie d'erreur standard) (avec une entrée de chaîne de caractères si nécessaire).

(Officiel) Communiquer avec le processus: envoyer les données vers l'entrée standard. Lisez les données de stdout et stderr jusqu'à la fin du fichier. Attendez que le processus se termine. (Omitted) communiquez () renvoie un taple (stdout_data, stderr_data). (Officiel) Avertissement L'utilisation de .stdin.write, .stdout.read, .stderr.read peut remplir le tampon de canal du système d'exploitation d'un autre canal et provoquer un blocage. Utilisez communique () pour éviter cela.

[Cet article](https://qiita.com/caprest/items/0245a16825789b0263ad#%E3%83%91%E3%82%A4%E3%83%97%E3%81%AB%E3%81%A4% E3% 81% 84% E3% 81% A6) donne également une explication détaillée. S'il y a un risque que les données d'entrée / sortie soient trop accumulées dans le tampon, utilisez communiquez () qui gère tout en même temps. Il y a aussi une personne qui a effectivement répondu au blocage en utilisant communique () (article).

L'inconvénient est que ** il devient impossible d'échanger des entrées et des sorties plusieurs fois dans un même processus **. Il ne peut pas être utilisé avec communiquez () pour la gestion des sorties en temps réel et le traitement interactif présenté ci-dessous.

Une autre différence est que l'option «timeout» requise pour le traitement du timeout n'existe pas dans «Popen (). Strout», etc., mais existe dans «commun ()». Il existe une solution à ce problème, présentée dans cet article.

Reproduction du pipeline shell

Il est également introduit dans Official. Vous pouvez le donner sous forme de chaîne de caractères comme shell = True, mais vous pouvez le reproduire avec une grammaire qui l'évite comme indiqué ci-dessous. Pour plus d'informations sur SIGPIPE en cours de route, voir ici ou ici. / 39190250 / sous-quelle-condition-fait-un-sous-processus-python-get-a-sigpipe).

Popen()Exemple: pipeline Shell


p1 = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE) #Diriger vers la destination de sortie
p2 = subprocess.Popen(['grep', 'python'], stdin=p1.stdout, stdout=subprocess.PIPE) #Recevoir p1 pour l'entrée
p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
print(p2.communicate()[0].decode().strip().split('\n')) #obtenir stdout

Confirmation de l'état et gestion des sorties en temps réel par .poll ()

J'ai mentionné que Popen () n'attend pas la fin, mais vous pouvez vérifier s'il est dans l'état final par .poll (). S'il n'est pas terminé, il renvoie Aucun, et s'il est terminé, son état est retourné. Vous pouvez obtenir la sortie ligne par ligne avec .stdout.readline (). Ceci peut être obtenu ** chaque fois que la sortie est interrompue en temps réel **. D'un autre côté, si vous utilisez une méthode telle que .stdout.read () qui obtient toutes les sorties à la fois, elle entrera automatiquement dans l'état d'attente de fin **.

Popen()Exemple: gestion des sorties en temps réel


cmd = 'echo Start;sleep 0.5;echo 1;sleep 0.5;echo 2;sleep 0.5;echo 3;sleep 0.5;echo Finished'
p = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
#Obtenez en temps réel
while p.poll() is None:
    print('status:',p.poll(), p.stdout.readline().decode().strip())
print('status:',p.poll())
print()

p = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
#Aucune sortie après 2 secondes
print('status:',p.poll(), p.stdout.read().decode().strip())
'''
status: None Start
status: None 1
status: None 2
status: None 3
status: None Finished
status: 0

status: None Start
1
2
3
Finished
'''

Un modèle plus approprié de code de gestion de sortie en temps réel est présenté dans cet article.

Entrée / sortie interactive

L'échange d'envoi de l'entrée en plusieurs fois et d'obtention de la sortie à chaque fois est réalisé comme suit. J'ai fait référence à cet article. Comme il ne peut pas être reproduit par communiquez (), qui termine l'entrée et la sortie des données à la fois, il y a un risque de blocage lorsqu'un échange de données important est nécessaire.

Tout d'abord, en guise de préparation, créez un code python qui nécessite plusieurs entrées et génère une sortie à chaque fois.

(Préparation) calc.py


a = int(input())
print('n    =', a)
b = int(input())
print(' + {} ='.format(b), a+b)
c = int(input())
print(' - {} ='.format(c), a-c)
d = int(input())
print(' * {} ='.format(d), a*d)
e = int(input())
print(' / {} ='.format(e), a/e)

Procédez comme suit. Notez que si vous ne videz pas, il restera dans l'état d'être accumulé dans le tampon et de ne pas être entré.

Popen()Exemple: entrée / sortie interactive


numbers = range(1, 6)

#Non interactif (communiquer)()Envoyez tout en même temps en utilisant
p = subprocess.Popen(['python', 'calc.py'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
#Concaténer avec le code de saut de ligne
str_nums = '\n'.join(map(str, numbers))
o, e = p.communicate(input=str_nums.encode())
print(o.decode())

'''
n    = 1
 + 2 = 3
 - 3 = -2
 * 4 = 4
 / 5 = 0.2
'''

#Interactif (.stdin.write()Envoyer ligne par ligne en utilisant
p = subprocess.Popen(['python', 'calc.py'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
for n in numbers:
    #Envoyer des numéros
    p.stdin.write(str(n).encode())
    #Envoyer un saut de ligne (entrée()Obligatoire car il s'agit d'une entrée pour)
    p.stdin.write('\n'.encode())
    #Libérez le tampon (important)
    p.stdin.flush()
    #Lisez le résultat sur une ligne (utilisez l'instruction while si elle s'étend sur plusieurs lignes)
    print(p.stdout.readline().decode().strip())

'''
n    = 1
 + 2 = 3
 - 3 = -2
 * 4 = 4
 / 5 = 0.2
'''

Exécution parallèle (manque d'informations)

Puisque Popen () n'attend pas la fin, il est possible d'exécuter plusieurs processus en même temps. Le traitement asynchrone est géré par le module ʻasyncio, mais il y en a qui correspondent à sous-processus` (Officiel. asyncio-subprocess.html # asyncio-subprocess)). De plus, il existe divers processus asynchrones tels que cet article et cet article. Il existe de nombreux articles. Je pense qu'il est nécessaire de saisir "** quel est le délai le plus bas et le moindre risque **" lors de la sélection parmi différentes méthodes, mais comme je n'ai pas beaucoup de connaissances à l'heure actuelle, c'est ici. La discussion ci-dessus est omise. Ce qui suit est une version simplifiée pour le moment.

Exécution parallèle


#permutation
print('start')
s = time.time()
running_procs = [
    subprocess.run(['sleep', '3']),
    subprocess.run(['sleep', '2']),
    subprocess.run(['sleep', '1']),
    ]
# run()Si tel est le cas, il faut environ 6 secondes au total pour attendre l'exécution dans l'ordre
print('finish [run()]', time.time() - s)
# ----------------------------
#Parallèle
print('start')
s = time.time()
procs = [
    subprocess.Popen(['sleep', '3']),
    subprocess.Popen(['sleep', '2']),
    subprocess.Popen(['sleep', '1']),
    ]
# Popen()Ensuite, si vous venez de commencer, cela se terminera immédiatement
print('finish [run Popen()]', time.time() - s)

#En attente de la fin de chaque processus
[p.wait() for p in procs]
#Le traitement est terminé en environ 3 secondes, ce qui équivaut au processus le plus long
print('finish [wait Popen()]', time.time() - s)

À la fin

On peut voir que la plupart des différents processus peuvent être exécutés immédiatement en utilisant run (), et qu'une application beaucoup plus flexible, asynchrone et interactive est possible en utilisant pleinement Popen ().

Recommended Posts

En savoir plus sur le sous-processus (série 3, version mise à jour)
Une note sur le sous-processus