Partage
  • Partager sur Facebook
  • Partager sur Twitter

[subprocess] Question sur le module

Sujet résolu
    15 février 2013 à 15:42:45

    Bonjour.

    On m'a conseillé ici de changer mes instructions os.popen pour subprocess.Popen. Ayant lu un peu de doc depuis, j'en comprends maintenant mieux l'intérêt. J'aurais cependant quelques questions à poser.

    print '\nread:'
    proc = subprocess.Popen('echo "to stdout"',
                           shell=True,
                           stdout=subprocess.PIPE,
                           )
    stdout_value = proc.communicate()[0]
    print '\tstdout:', repr(stdout_value)

    Prenons le code suivant par exemple. Mettre la variable shell à true, ça veut dire que les variables comme $HOME vont être traitées, et retourner la valeur du système ? Par exemple chez moi, /home/djipey, c'est bien ça ?

    Mais mon plus gros problème est l'écriture dans ma commande système. Prenons le code suivant:

    print '\nwrite:'
    proc = subprocess.Popen('cat -',
                           shell=True,
                           stdin=subprocess.PIPE,
                           )
    proc.communicate('\tstdin: to stdin\n')



    Comment je passe un argument à cat ? Est-ce que c'est le tiret après cat la variable ?

    Commençons simplement, j'ai encore quelques questions après celles-la :)

    Bien à vous.

    • Partager sur Facebook
    • Partager sur Twitter
    http://maymayhem.fr/
    Anonyme
      15 février 2013 à 16:13:42

      Il me semble que si le paramètre args de Popen est une chaîne, tu ne peux pas exécuter une commande avec paramètres.

      Pour cela ça doit être une séquence, c'est à dire que ta commande doit être splittée, ce qui donnera une liste composée de ta commande et de tes paramètres.

      Maintenant je suis pas sûr et j'ai pas le temps de vérifier.

      Bonne continuation...

      • Partager sur Facebook
      • Partager sur Twitter
      Anonyme
        15 février 2013 à 17:49:58

        Avec shell=True, subprocess passe la chaîne telle qu'elle est écrite dans le programme au shell du système, donc cmd.exe sur Windows ou bash/zsh/… sous Linux. Ce qui permet d'utiliser les spécificités du shell, par exemple les opérateurs comme le * (extension), le | (qui permet de diriger la sortie d'une commande vers l'entrée d'une autre) ou les variables, comme tu le dis. Mais souvent, on peut se passer de tout cela et il vaut alors mieux prendre l'habitude de ne pas utiliser ce shell=True, car il peut être source de problèmes et/ou de failles de sécurité, selon l'application.

        Quand on utilise shell=False (ou si on ne le précise pas), la commande n'est plus traitée par le shell et il faut fournir à subprocess une liste dont le premier élément est l'exécutable à appeler (si on lui donne une chaîne cat -n file.txt, subprocess cherchera un exécutable portant le nom cat -n file.txt au lieu de cat, d'où la nécessité de passer par une liste).

        Par exemple, pour appeler cat avec un argument :

        proc = subprocess.Popen(['cat', '-n', 'file.txt'], stdout=subprocess.PIPE)
        stdout_value = proc.communicate()[0]
        

        (Si tu tenais vraiment à utiliser shell=True, ce serait simplement subprocess.Popen('cat -n file.txt', shell=True, stdout=subprocess.PIPE)).

        Pour obtenir la décomposition de la commande, tu peux t'aider du module shlex :

        >> shlex.split('cat -n file.txt')
        ['cat', '-n', 'file.txt']
        

        Bien sûr, tout cela suppose que tu connaisses au préalable les arguments des commandes que tu souhaites utiliser et leur syntaxe.

        -
        Edité par Anonyme 15 février 2013 à 18:02:29

        • Partager sur Facebook
        • Partager sur Twitter
          15 février 2013 à 22:16:53

          Réponse très claire @Graphox, j'apprécie.

          Ok, pour parler plus clairement, voilà à quoi ressemble une de mes instructions pour l'instant:

          cmd = os.popen("ffmpegthumbnailer -i \"{0}\" -o \"{1}_thumb.png\" > /dev/null".format(path, name))

          Est-ce que je peux/dois donc utiliser la liste:

          ['ffmpegthumbnailer', '-i', path, '-o', name + '_thumb.png']

          ?

          Ce serait idéal. Mais je suppose que je vais devoir garder l'échappement de mon chemin (des fois il contient des espaces).

          Mais du coup, ce que je ne comprends pas, c'est à quoi sert le stdin ?

          • Partager sur Facebook
          • Partager sur Twitter
          http://maymayhem.fr/
            16 février 2013 à 12:56:01

            À préciser d'où va venir le flux d'entrée du processus fils.

            En gros, en jouant sur les attributs stdin et stdout de l'objet Popen, tu peux envoyer ce que tu veux sur l'entrée standard d'un processus, récupérer sa sortie standard sur une chaîne de caractères, rediriger sa sortie d'erreur, ou créer des pipes, pour rediriger la sortie d'un processus fils vers l'entrée d'un autre.

            • Partager sur Facebook
            • Partager sur Twitter
            Zeste de Savoir, le site qui en a dans le citron !
              16 février 2013 à 17:07:19

              Oui, je me doutais que ça servait d'entrée pour la commande. Ce que je me demandais, c'est comment l'utiliser. Parce que si je comprends bien, en passant la liste d'arguments de mon dernier post à Popen, je n'ai pas besoin de stdin. Et dans le cas d'arguments qui ne varient pas, j'en vois encore moins l'intérêt.

              Tu pourrais m'expliquer ce point s'il te plait ?

              • Partager sur Facebook
              • Partager sur Twitter
              http://maymayhem.fr/
                16 février 2013 à 23:47:53

                djipey a écrit:

                Oui, je me doutais que ça servait d'entrée pour la commande. Ce que je me demandais, c'est comment l'utiliser. Parce que si je comprends bien, en passant la liste d'arguments de mon dernier post à Popen, je n'ai pas besoin de stdin. Et dans le cas d'arguments qui ne varient pas, j'en vois encore moins l'intérêt.

                Ben si tu n'en as pas besoin, ne l'utilise pas. :D Il suffit de ne rien spécifier, l'argument stdin est à None par défaut.

                C'est complètement décorrélé des arguments qui varient ou non. Tu peux très bien avoir une commande qui sera toujours, par exemple, ssh host 'cat > /some/file.data' à laquelle tu envoies du contenu via STDIN pour qu'elles soient écrites sur un fichier sur un hôte distant over SSH, par exemple. Les arguments ne changeront jamais, mais les données, oui.

                Sinon pour répondre à ta question :

                djipey a écrit:

                Est-ce que je peux/dois donc utiliser la liste:

                ['ffmpegthumbnailer', '-i', path, '-o', name + '_thumb.png']

                ?

                Voilà.

                Sinon tu peux toujours passer ta chaîne toute faite à la fonction shlex.split() si tu as un doute sur la façon dont les arguments doivent être séparés.

                -
                Edité par nohar 16 février 2013 à 23:57:33

                • Partager sur Facebook
                • Partager sur Twitter
                Zeste de Savoir, le site qui en a dans le citron !
                  19 février 2013 à 21:56:12

                  D'accord. J'ai donc commencé ma réécriture de code avec vos précisions:

                  def caps(path):
                  
                      """ Fct permettant de créer le cap d'un fichier """
                  
                      name = path.split("/")[-1]
                      percent = 5
                  
                      while percent < 100:
                  
                          #TODO: option pr gérer la taille des images
                          #TODO: mettre les miniatures temporaires ds un dossier temp
                          #On génère des miniatures à intervalle de % régulier, avec une largeur particulière
                          try:
                              #make_pictures = os.popen("ffmpegthumbnailer -s 256 -i \"{0}\" -o cap-{1}.png -t {2} > /dev/null".format(path, percent, percent))
                              cmd = subprocess.Popen(['ffmpegthumbnailer', '-s', '256', '-i',
                                  path, '-o', 'cap-' + str(percent) + '.png', '-t', percent],
                                  stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
                              stdout_value = cmd.communicate()
                              percent += 10
                              #make_pictures.read()
                          except:
                              print("Erreur lors de la génération de la mosaïque")
                              pass
                  

                  Sauf que ça lève une exception, assez bizarre:

                  Traceback (most recent call last):
                  ^C  File "/home/djipey/informatique/python/bibli/screen.py", line 43, in caps
                    File "/usr/lib/python3.3/subprocess.py", line 784, in __init__
                    File "/usr/lib/python3.3/subprocess.py", line 1271, in _get_handles
                  OSError: [Errno 24] Trop de fichiers ouverts

                  Est-ce que vous savez à quoi c'est dû ?


                  • Partager sur Facebook
                  • Partager sur Twitter
                  http://maymayhem.fr/
                    19 février 2013 à 22:05:37

                    L'hypothèse qui me semble la plus probable :

                    Tu ne wait() pas tes processus fils, donc ceux-ci ne se terminent jamais vraiment et restent à l'état de zombies (et ne libèrent donc pas leurs ressources). Au bout d'un moment, le nombre de fichiers ouverts par ces processus fils dépasse la limite allouée par le noyau.

                    Note qu'il est probable que cette erreur soit la conséquence du fait que d'autres programmes consommateurs (et gourmands) en fichiers soient en train de tourner sur la machine, comme MySQL ou un client bittorrent.

                    Tu peux augmenter cette limite en te servant du fichier /proc/sys/fs/file-max.

                    -
                    Edité par nohar 19 février 2013 à 22:13:04

                    • Partager sur Facebook
                    • Partager sur Twitter
                    Zeste de Savoir, le site qui en a dans le citron !
                      19 février 2013 à 22:16:01

                      Comment je wait du coup ? Parce qu'un simple wait() après la commande ne marche pas. J'ai même essayé l'exemple de la doc:

                      proc = subprocess.Popen(...)
                      try:
                          outs, errs = proc.communicate(timeout=15)
                      except TimeoutExpired:
                          proc.kill()
                          outs, errs = proc.communicate()
                      

                      EDIT:

                      ╭─djipey-laptop ~/informatique/python/bibli ‹unstable*›  22:19:59
                      ╰─$ cat /proc/sys/fs/file-max
                      392624

                      lol...

                      -
                      Edité par djipey 19 février 2013 à 22:20:35

                      • Partager sur Facebook
                      • Partager sur Twitter
                      http://maymayhem.fr/
                        19 février 2013 à 22:22:10

                        J'ai rien dit, communicate() implique un wait. Il n'y a donc aucune raison que ce genre de trucs se produise spécifiquement sur ton code. C'est peut-être une autre couille sur ton système, indépendante de ce script.

                        Essaye de voir le nombre de file handles déjà ouverts sur ton système avec un lsof |wc -l, et compare cette valeur avec celle de cat /proc/sys/fs/file-max ?

                        -
                        Edité par nohar 19 février 2013 à 22:23:18

                        • Partager sur Facebook
                        • Partager sur Twitter
                        Zeste de Savoir, le site qui en a dans le citron !
                          19 février 2013 à 22:25:22

                          ╭─djipey-laptop ~  22:23:21
                          ╰─$ lsof |wc -l
                          26791
                          

                          Je suis loin de ma limite là. Et surtout, mon code avec les os.popen marche toujours.
                          • Partager sur Facebook
                          • Partager sur Twitter
                          http://maymayhem.fr/
                            19 février 2013 à 22:34:58

                            Sous quelle version de Python tournes-tu ?

                            D'après ce thread du bugtracker de Python, il y a une issue proche de ton problème qui a été réparée pour Python 3.1.

                            Oh, il est aussi possible que ce soit les file descriptors de tes pipes, que tu ouvres inutilement, d'ailleurs. Vire les arguments "stdout" et "stderr" de ton appel à Popen.

                            -
                            Edité par nohar 19 février 2013 à 22:42:34

                            • Partager sur Facebook
                            • Partager sur Twitter
                            Zeste de Savoir, le site qui en a dans le citron !
                              19 février 2013 à 22:41:01

                              Mec j'en suis à la 3.3.0 :)
                              • Partager sur Facebook
                              • Partager sur Twitter
                              http://maymayhem.fr/
                                19 février 2013 à 22:48:06

                                J'avais pas vu que tu avais posté entre temps, mais j'ai édité : tu devrais virer les arguments stdout et stderr du constructeur de Popen. Tu ne te sers nulle part des pipes qui sont créés (et qui sont eux aussi représentés par des file descriptors sous Linux).

                                -
                                Edité par nohar 19 février 2013 à 22:50:47

                                • Partager sur Facebook
                                • Partager sur Twitter
                                Zeste de Savoir, le site qui en a dans le citron !
                                  19 février 2013 à 23:01:07

                                  Ça ne résout pas le problème, j'avais déjà essayé. J'ai essayé d'enlever les try et j'ai une autre exception,bien bizarre elle aussi, vu qu'aucune ligne n'est indiquée comme générant une erreur:

                                    File "/home/djipey/informatique/python/bibli/gui.py", line 157, in verification
                                      liste.verification(self.options, self.majProgression)
                                    File "/home/djipey/informatique/python/bibli/liste.py", line 298, in verification
                                      importation(each_dossier, callback)
                                    File "/home/djipey/informatique/python/bibli/liste.py", line 439, in importation
                                      screen.caps(path)
                                    File "/home/djipey/informatique/python/bibli/screen.py", line 45, in caps
                                      path, '-o', 'cap-' + str(percent) + '.png', '-t', percent]) 
                                    File "/usr/lib/python3.3/subprocess.py", line 818, in __init__
                                      restore_signals, start_new_session)
                                    File "/usr/lib/python3.3/subprocess.py", line 1363, in _execute_child
                                      restore_signals, start_new_session, preexec_fn)
                                  TypeError: Can't convert 'int' object to str implicitly
                                  



                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                  http://maymayhem.fr/
                                    20 février 2013 à 0:24:53

                                    djipey a écrit:

                                    J'ai essayé d'enlever les try et j'ai une autre exception,bien bizarre elle aussi, vu qu'aucune ligne n'est indiquée comme générant une erreur:

                                    Si. Ici :

                                      File "/home/djipey/informatique/python/bibli/screen.py", line 45, in caps
                                        path, '-o', 'cap-' + str(percent) + '.png', '-t', percent]) 
                                    

                                    Le dernier argument dans ta liste est supposé être une chaîne de caractères, alors que c'est un int.

                                    Pour le coup ton problème de base (avec les filehandles) peut aussi être un effet de bord vicieux de celui-là, occulté par un except: trop large (si tu utilises un try/except, précise TOUJOURS le type d'erreur que tu t'attends à rattraper, ça évite au programme de continuer à tourner en dépit d'une erreur critique de ce genre), et empiré par les PIPES que tu ouvres pour rien.

                                    -
                                    Edité par nohar 20 février 2013 à 0:52:51

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                    Zeste de Savoir, le site qui en a dans le citron !
                                      20 février 2013 à 16:50:19

                                      Oui, tu avais raison, c'était bien ça. Et pour l'exception, je suis d'accord. J'ai codé ça il y a quelques temps déjà, j'ai un peu progressé depuis :)

                                      Surtout en lisant ça : http://sametmax.com/pourquoi-il-faut-specifier-lexception-quon-gere/

                                      Du coup j'ai rajouté un FileNotFoundError comme exception.

                                      Par contre, juste pour ma culture. Prenons par exemple l'autre bout de ma fonction:

                                          try:
                                              #On construit une mosaique ac les images générées précédemment. 1 pixel entre chaque image.
                                              #2 images de large sur 5 de haut.
                                              #http://dptnt.com/2008/12/create-photo-montage-with-imagemagick/
                                              mosaic = os.popen("montage -geometry +1+1 -tile 2x5 cap-*.png \"{0}\"_mosaic.png > /dev/null".format(name))
                                              mosaic.read()
                                      
                                              #On déplace la mosaique dans le dossier des images
                                              os.renames("{0}/{1}_mosaic.png".format(os.getcwd(), name), "{0}/screens/{1}_mosaic.png".format(os.getcwd(), name) )
                                      
                                              #On supprime les images séparées constituant la mosaique
                                              for fichier in os.listdir(os.getcwd()):
                                                  #Module fnmatch, qui permet de faire des recherches
                                                  #façon unix
                                                  if fnmatch.fnmatch(fichier, 'cap-*.png'):
                                                      os.remove(fichier)
                                          except:
                                              print("La création de la mosaïque a échoué. Retour.")
                                              return
                                      

                                      (on oublie les os.popen, je vais les changer). Là encore, j'ai du mal à voir ce que je spécifie comme exception. À part le fileNotFound, il peut se passer plein de trucs je pense. Comment tu gèrerais ça ?

                                      Et les pipes, je voudrais m'en servir un jour peut-être, pour des logs ou je ne sais quoi. Par exemple, là, je ne pourrais pas m'en servir pour afficher ce qu'il s'est mal passé ?

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                      http://maymayhem.fr/
                                        21 février 2013 à 11:11:44

                                        djipey a écrit:

                                        Oui, tu avais raison, c'était bien ça. Et pour l'exception, je suis d'accord. J'ai codé ça il y a quelques temps déjà, j'ai un peu progressé depuis :)

                                        Faut avouer que c'était pas un bug facile à diagnostiquer, mais ça illustre bien le problème des except:. Je vais garder ce topic comme référence pour la prochaine fois que je devrai expliquer pourquoi il faut toujours préciser ce qu'on cherche à rattraper : c'est presque un cas d'école ! :D

                                        djipey a écrit:

                                        Là encore, j'ai du mal à voir ce que je spécifie comme exception. À part le fileNotFound, il peut se passer plein de trucs je pense. Comment tu gèrerais ça ?

                                        Je commencerais par virer le except: et puis je ferais tourner le script dans plein de conditions susceptibles de planter. Chaque fois qu'une exception n'est pas rattrapée, je déciderais si c'est quelque chose qu'il faut rattraper (une erreur "normale") ou bien plutôt un bug dû au code. Dans le premier cas, je rajouterais à chaque fois le except Truc: correspondant avec un message d'erreur qui fasse office de diagnostic pour l'utilisateur ("This file is missing", "Please check /proc/sys/[truc]", etc.).

                                        Et les pipes, je voudrais m'en servir un jour peut-être, pour des logs ou je ne sais quoi. Par exemple, là, je ne pourrais pas m'en servir pour afficher ce qu'il s'est mal passé ?

                                        Oui, si tu veux récupérer ce qui s'est mal passé et utiliser ces données, alors il faut setter stdout et stderr à subprocess.PIPE. Là je te les ai fait virer parce que dans la suite tu ne t'en servais pas.

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                        Zeste de Savoir, le site qui en a dans le citron !
                                          21 février 2013 à 22:27:08

                                          Merci à toi pour les astuces. Au final, je suis arrivé à quelque chose de potable pour l'instant, bien que je n'ai pas encore eu l'occasion de beaucoup tester:

                                          def caps(path):
                                          
                                              """ Fct permettant de créer le cap d'un fichier """
                                          
                                              name = path.split("/")[-1]
                                              percent = 5
                                          
                                              while percent < 100:
                                          
                                                  #TODO: option pr gérer la taille des images
                                                  #TODO: mettre les miniatures temporaires ds un dossier temp
                                                  #On génère des miniatures à intervalle de % régulier, avec une largeur particulière
                                                  try:
                                                      cmd = subprocess.Popen(['ffmpegthumbnailer', '-s', '256', '-i',
                                                          path, '-o', 'cap-' + str(percent) + '.png', '-t', str(percent)],
                                                          stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
                                                      out, err = cmd.communicate()
                                          
                                                      if cmd.returncode != 0:
                                                          print("La commande ffmpegthumbnailer s'est mal passée. La mosaïque n'a pas été générée pour {0}".format(path))
                                                          return
                                          
                                                  except FileNotFoundError:
                                                      print("La création de la mosaïque a échoué, le fichier n'existe pas.")
                                                      return
                                          
                                                  percent += 10
                                          

                                          Si la commande s'est mal passé, le code de sortie n'est pas bon, et je peux gérer au moins cette partie. Et je ne spécifie qu'une exception, au moins les autres seront visibles en cas de problème.

                                          Merci à toi :)

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                          http://maymayhem.fr/

                                          [subprocess] Question sur le module

                                          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                          • Editeur
                                          • Markdown