Mis a jour le 2025-04-14, 12:10

Lancement de process

La nouvelle méthode de haut niveau est run :
  • cp = subprocess.run(['ls', '-ls']) : appel système avec la liste des arguments, le stdout et le stderr du programme appelé sont redirigés vers celui du script python. Renvoie un object subprocess.CompletedProcess.
  • cp = subprocess.run(['ls', '-ls'], capture_output = True, text = True) : cp.stdout renvoie du texte, pas des bytes.
  • cp.returncode : statut de retour du process.
  • cp.check_returncode() : vérifie le statut de retour et produit une exception s'il est différent de 0.
  • subprocess.run(['ls', '-ls'], check = True) : si le statut de sortie est différent de 0, lance une exxception (défaut est check = False).
  • subprocess.run(['ls -ls', shell = True) : permet de passer directement une commande shell.
  • cp = subprocess.run('ls -ls', shell = True, check = True, capture_output = True) : permet de récupérer à la fois le stdout et le stderr dans cp.stdout et cp.stderr : ce sont des bytes.
  • cp = subprocess.run('ls -ls', shell = True, check = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE): équivalent, mais permet de le faire pour l'un et pas pour l'autre.
  • cp = subprocess.run('ls -ls', shell = True, check = True, stdout = subprocess.PIPE, text = True) : on récupère dans cp.stdout une string plutôt que des bytes.
Comment lancer un commande avec un pipe tout en vérifiant que tout s'est bien passé ?
  • quand on lance une commande avec un pipe, le statut de retour est celui de la dernière commande : ls -zz|at ; echo $? donne 0 !
  • on peut éviter ce comportement en bash avec set -o pipefail : ne retourne un statut 0 que si tous les programmes sont sortis en statut 0, et c'est spécifique bash, pas sh.
  • mais les appels systèmes python utilisent sh par défaut, et non bash ...
  • solution : cp = subprocess.run('set -o pipefail ; ls -zz|cat', shell = True, executable = 'bash', check = True, text = True) : lance une exception comme attendu, alors que sans le set -o pipefail, aucune exception n'est produite !
Anciennes méthodes :
  • status = subprocess.call(['ls', '-ls']) : appel système avec la liste des arguments, le stdout et le stderr du programme appelé sont redirigés vers celui du script python. Le statut de retour est fourni et l'appel est bloquant.
  • status = subprocess.call('ls -ls', shell = True) : exécute directement la ligne de commande (avec les risques de sécurité qui vont avec). L'appel est bloquant.
  • status = subprocess.call('ls -ls', stdout = myFh, shell = True) : on fournit un filehandle, myFh, dans lequel le stdout du programme fini (pareil avec stderr et stdin).
  • subprocess.call(['ls', '-ls'], stdout = sys.stderr.fileno()) : pour rediriger le stdout d'un programme vers stderr.
  • fh = open(os.devnull, 'w'); subprocess.call('ls -ls', stdout = fh, stderr = fh, shell = True) : pour supprimer toute sortie stdout ou stderr du programme appelé.
  • subprocess.check_call('ls -lsX', shell = True) : lève une exception subprocess.CalledProcessError si erreur (et l'erreur contient le code de retour, la commande appelé et l'output éventuel). L'appel est bloquant également.
    try:
        subprocess.check_call('ls -lsz', shell = True)
    except subprocess.CalledProcessError as e:
        print(e.returncode)
        print(e.cmd)
        print(e.output)
        
  • out = subprocess.check_output('ls -ls', shell = True, text = True) : lève aussi une exception subprocess.CalledProcessError, mais renvoie l'output de la commande dans la variable (ici out). L'appel est bloquant.
  • subprocess.check_output('my command', shell = True, executable = '/bin/bash') : indique d'utiliser bash (par défaut, c'est sh qui est utilisé).
  • si on veut exécuter la commande, récupérer stdout, mais sans vérifier le statut de retour, on peut exécuter la fonction sous-jascente : out = subprocess.run('ls -ls', shell = True, text = True, stdout = subprocess.PIPE, check = False).stdout (check = False est en fait le défaut).
Si on veut lancer une commande dont on veut récupérer le stdout et le stderr même si l'exécution s'est mal passée : popen = subprocess.Popen(myCommand, shell = True, stdin = None, stdout = subprocess.PIPE, stderr = subprocess.PIPE, encoding = 'utf8'); (out, err) = popen.communicate() on peut alors avoir le code de sortie avec popen.returncode pour vérifier s'il est à 0 ou non.
Le paramètre encoding est nécessaire si on veut récupérer des strings, sinon, ce sont des bytes par défaut.
Pour lancer une commande et lire l'output immédiatement au fur et à mesure :
myPopen = subprocess.Popen(com, shell = True, stdout = subprocess.PIPE, encoding = 'ascii')
while True:
  line = myPopen.stdout.readline()
  if line == '' and myPopen.poll() is not None:
    break
  ...
returnStatus = myPopen.poll()
if returnStatus != 0:
  raise RuntimeError('Problem')
  
Pour lancer une commande qui génère un tableau et lire l'output au fur et à mesure :
popen = subprocess.Popen('samtools view ' + bamFile, stdout = subprocess.PIPE, shell = True, encoding = 'utf8')
reader = csv.reader(popen.stdout, delimiter = '\t')
for line in reader:
   ...
popen.wait()
if popen.returncode != 0:
  raise RuntimeError('Error')
Appel système général avec Popen, bloquant ou non-bloquant :
  • myPopen = subprocess.Popen(['cat', '-n']) : fait l'appel système non bloquant.
  • si on veut pouvoir envoyer des données sur le stdin du process, il faut faire : myPopen = subprocess.Popen(['cat', '-n'], stdin = subprocess.PIPE, encoding = 'utf8').
  • si on veut pouvoir récupérer des données de la sortie standard et de la sortie d'erreur du process, il faut faire : myPopen = subprocess.Popen(['cat', '-n'], stdout = subprocess.PIPE, stderr = subprocess.PIPE).
  • myPopen.stdin, myPopen.stdout, myPopen.stderr : les entrées et sorties standard, quand elles ont été mises a subprocess.PIPE dans le constructeur.
  • myPopen.stdout est de la classe file, dont on peut faire simplement :
    for line in myPopen.stdout:
      ...
        
  • myPopen.pid : le process id.
  • myPopen.returncode : le code de retour (0 si ça s'est bien passé), mais attention : avant d'appeler cette méthode, il faut appeler myPopen.wait() ou myPopen.poll()
  • myPopen.terminate() et myPopen.kill() : stoppe ou tue le process (avec un signal SIGTERM ou SIGKILL respectivement).
  • myPopen.poll() : renvoie le statut de retour si le programme appelé a terminé, sinon None s'il n'est pas fini.
  • appel système bloquant :
    • (stdoutData, stderrData) = myPopen.communicate('my data') : envoie sur le stdin 'my data', attend la fin du process et récupère les sorties, mais il faut avoir mis comme paramètres stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, donc ici, c'est une forme bloquante.
    • exemple avec forme bloquante :
      myPopen = subprocess.Popen(['cat', '-n'], stdin = subprocess.PIPE,
                               stdout = subprocess.PIPE, stderr = subprocess.PIPE, encoding = 'utf8')
      print('PID', myPopen.pid)
      (stdoutData, stderrData) = myPopen.communicate('ligne1\nligne2\nligne3\n')
      print(stdoutData)
      print(myPopen.returncode)
            
  • appel système non bloquant :
    • utiliser myPopen.poll() pour savoir si le programme a terminé, et utiliser myPopen.stdin, myPopen.stdout et myPopen.stderr pour communiquer avec le programme.
    • exemple avec forme non bloquante :
      myPopen = subprocess.Popen(['cat', '-n'], stdin = subprocess.PIPE,
                               stdout = subprocess.PIPE, stderr = subprocess.PIPE, encoding = 'utf8')
      myPopen.stdin.write('ligne1\nligne2\nligne3\n')
      myPopen.stdin.close()
      while True:
          status = myPopen.poll()
          if status is not None:
              break
      for line in myPopen.stdout:
          sys.stdout.write(line)
      print(status)
            
    • attention : s'il y a une sortie à lire et que celle-ci ne tient pas dans le buffer courant, poll() continuera à renvoyer None. Il faut alors lire la sortie avant !
  • attention : il y a aussi une fonction myPopen.wait() qui attend que le process soit terminé, mais si stdout ou stderr ont été fixés à PIPE, et que le buffer est plein (et non lu), la fonction va deadlocker (bloquer indéfiniment).

Copyright python-simple.com
programmer en python, tutoriel python, graphes en python, Aymeric Duclert