Valid XHTML     Valid CSS2    

Cours CMI / M1 Données - séance 2

 

Résumé de la séance 2

On s'entraine ici à produire des scripts un peu plus conséquents et à utiliser Perl et Python pour "enrober" des commandes qui traitent des listes de fichiers. On apprend aussi à exécuter des scripts en tache de fond avec nohup.

 

1. Scripts et traitement de fichiers par lots

1.1 Traitements bioinformatiques et scripts

La bioinformatique regorge de scripts et de programmes à exécuter, avec des paramètres plus ou moins compliqués, avec des durées plus ou moins longues. Pour les utiliser efficacement, une fois installés, il faut souvent les évaluer, les tester avant de passer à leur exécution en masse ou en routine. Pour cela, il est bon de savoir réaliser un certain nombre d'actions classiques comme

  • afficher l'aide sur les paramètres avec des exemples ;

  • tester la présence d'un fichier ;

  • gérer des durées ;

  • traiter des fichiers fournis par une spécification ambigue ou par une liste de fichiers ;

  • exécuter des programmes qui durent plusieurs heures, plusieurs jours.

Aucune de ces taches n'est difficile en soi si on sait programmer et si on a beaucoup de rigueur. Toutefois écrire un script de plusieurs dizaines de lignes est un travail ingrat et long quand on débute. C'est pourquoi nous fournissons ici des modèles de scripts prêts à l'emploi ou, plus exactement prêts à être complétés en fonction des traitements bioinformatiques à réaliser.

Pour éviter de perdre du temps à écrire des fichiers, chacun des scripts est téléchargeable, par exemple avec wget. Nous fournissons aussi une archive dataCmi.zip qui contient des exemples de fichiers de données à traiter.

1.2 Quelques actions classiques pour les scripts

Afficher l'aide si -h ou --help

Fichier aide


     # # (gH)   -_-  aide  ;  TimeStamp (unix) : 23 Février 2019 vers 22:50
     
     if [[ -n $1 ]] ; then
        if [[ $1 = "-h" ]] || [[ $1 = "--help" ]]
        then
          echo "Voici l'aide sur le script..."
          exit 1
        fi # fin si le paramètre est -h ou --help
     
     fi # fin si il y a des paramètres
     
     echo " Exécution normale du programme..."
     

Fichier aide.pl


     # # (gH)   -_-  aide.pl  ;  TimeStamp (unix) : 23 Février 2019 vers 22:13
     
     use strict ;
     use warnings FATAL => 'all';
     
     if ($#ARGV > -1) {
     
        if (($ARGV[0] eq "-h") or ($ARGV[0] eq "--help")) {
          print "Voici l'aide sur le script...\n" ;
          exit(1) ;
        } ; # fin si le paramètre est -h ou --help
     
     } ; # fin si il y a des paramètres
     
     print " Exécution normale du programme...\n" ;
     

Fichier aide.py


     # -*- coding:latin1 -*-
     # # (gH)   -_-  aide.py  ;  TimeStamp (unix) : 05 Mars 2019 vers 15:35
     
     import sys
     
     if (len(sys.argv)>1) :
     
        if (sys.argv[1]=="-h") or (sys.argv[1]=="--help") :
                  aide = '''
                  Voici l'aide sur le script...
                  '''
                  print(aide)
                  sys.exit(1)
        # fin de si le paramètre est -h ou --help
     
     # fin de si il y a des paramètres
     
     print(" Exécution normale du programme...")
     

Tester les paramètres comme la présence d'un fichier avant d'exécuter une commande

Fichier testefic


     # (gH)   -_-  testefic  ;  TimeStamp (unix) : 23 Février 2019 vers 23:15
     
     # si pas de paramètre, rappel de la syntaxe et sortie
     
     if [ -z $1 ] ; then
        echo syntaxe : testefic NOM_DE_FICHIER
        exit 1
     fi # fin si pas de paramètre
     
     # si le paramètre est présent, on teste que c'est un fichier qui existe
     
     if [ ! -r $1 ] ; then
        echo fichier $1 non vu.
        exit 2
     fi # finsi
     
     echo Exécution normale du programme...
     

Fichier testefic.pl


     # # (gH)   -_-  testefic.pl  ;  TimeStamp (unix) : 23 Février 2019 vers 23:12
     
     use strict ;
     use warnings FATAL => 'all';
     
     # si pas de paramètre, rappel de la syntaxe et sortie
     
     if ($#ARGV == -1) {
        print "syntaxe : perl testefic.pl NOM_DE_FICHIER\n" ;
        exit(1) ;
     } ; # fin si pas de paramètre
     
     # si le paramètre est présent, on teste que c'est un fichier qui existe
     
     my $nomFic = $ARGV[0]    ;
     if (! -e $nomFic) {
       print "fichier $nomFic non vu.\n" ;
        exit(2) ;
     } ; # finsi
     
     print " Exécution normale du programme...\n" ;
     

Fichier testefic.py


     # -*- coding:latin1 -*-
     # # (gH)   -_-  testefic.py  ;  TimeStamp (unix) : 23 Février 2019 vers 22:27
     
     import sys
     import os
     
     # si pas de paramètre, rappel de la syntaxe et sortie
     
     if (len(sys.argv)==1) :
        print("syntaxe : python3 testefic.py NOM_DE_FICHIER")
        sys.exit(1)
     # fin de si pas de paramètre
     
     # si le paramètre est présent, on teste que c'est un fichier qui existe
     
     nomFic = sys.argv[1]
     if (not os.path.isfile(nomFic)) :
        print("fichier "+ nomFic + " non vu. STOP.")
        sys.exit(2)
     # fin de si fichier non vu
     
     print(" Exécution normale du programme...")
     

1.3 Comparaison des scripts Bash, Perl et Python

Les quelques exemples précédents montrent que les actions standards sont toutes réalisables avec les trois langages de scripts. Les scripts les plus lisibles sont sans doute ceux écrits en Python, les plus efficaces ceux écrits en Bash si on ne fait que d'exécuter des commandes systèmes, les plus efficaces pour le traitement de données textuelles (dont les séquences Fasta) ceux écrits en Perl.

Au final, il n'y a sans doute aucune raison de privilégier l'un de ces trois langages de script. Python et Perl ont toutefois l'avantage d'offrir des instructions de haut niveau et de nombreuses fonctions de calcul, conversion, que ce soit numérique ou textuel. Les bibliothèques Python fournissent aussi de nombreuses solutions "clés en main" pour de nombreux problèmes classiques.

Nous vous encourageons donc à vous entrainer à maitriser l'écriture de petits scripts dans ces trois langages avant d'approfondir celui qui vous plait le plus.

Par contre, dès que l'on veut effectuer des calculs, de la mise en forme "propre", les langages Python et Perl sont beaucoup plus agréables et faciles à utiliser.

2. Scripts Perl pour traiter des longues listes de fichiers

On suppose ici qu'on a une liste de fichiers à traiter, comme par exemple listeFic1.txt. Pour l'exemple ce fichier ne contient que peu de lignes, mais le script reste valable pour plusieurs centaines ou milliers de lignes. Les fichiers à utiliser sont vides et presque tous présents, ce qui suffira pour la démonstration.

Vous pouvez les récupérer dans l'archive dataCmi.zip.

Comme vu à la séance 1, une liste de fichiers peut être obtenue via la commande ls et la redirection des sorties, comme par exemple avec la commande


    ls fichier*txt >  listeFic1.txt 

Pour le premier script, nous supposons que la liste des fichiers est contenue dans listeFic1.txt. Le script Perl fournit un affichage minimal et incomplet (pourquoi ?) :


     # # (gH)   -_-  traiteListe1.pl  ;  TimeStamp (unix) : 04 Mars 2019 vers 13:54
     
     use strict ;
     use warnings FATAL => 'all';
     
     my $listeFic   = "listeFic1.txt" ;  # liste des fichiers à traiter
     open(LISTEFIC ,"<$listeFic") || die "\n impossible to read from the file $listeFic \n\n" ;
     my $nbFic = 0 ;
     while (my $nomFichier = <LISTEFIC>) {
       chop($nomFichier) ;
       $nbFic++ ;
       print "Traitement du fichier $nbFic : $nomFichier\n" ;
     } ; # fin tant que
     close(LISTEFIC) ;
     

     $gh> perl traiteListe1.pl
     
     Traitement du fichier 1 : fichier01.txt
     Traitement du fichier 2 : fichier02.txt
     Traitement du fichier 3 : fichier03.txt
     Traitement du fichier 4 : fichier05.txt
     

Pour le deuxième script, la liste des fichiers est fournie en paramètre. et nous fournissons un meilleur affichage :


     # # (gH)   -_-  traiteListe2.pl  ;  TimeStamp (unix) : 04 Mars 2019 vers 13:59
     
     use strict ;
     use warnings FATAL => 'all';
     
     # si pas de paramètre, rappel de la syntaxe et sortie
     
          if ($#ARGV == -1) {
             print "syntaxe : perl traiteListe2.pl NOM_DE_FICHIER\n" ;
             exit(1) ;
          } ; # fin si pas de paramètre
     
     # si le paramètre est présent, on teste que c'est un fichier qui existe
     
     my $listeFic = $ARGV[0]    ;
     if (! -e $listeFic) {
        print "fichier $listeFic non vu.\n" ;
        exit(2) ;
     } ; # finsi
     
     # exécution normale du programme
     
     open(LISTEFIC ,"<$listeFic") ;
     my $nbFic = 0 ;
     while (my $nomFichier = <LISTEFIC>) {
       chop($nomFichier) ;
       $nbFic++ ;
       if (!-e $nomFichier) {
         print "  ==> Fichier numéro $nbFic : $nomFichier non vu \n" ;
       } else {
         print "Traitement du fichier ".sprintf("%4d",$nbFic)." : $nomFichier \n" ;
       } ; # fin si
     } ; # fin tant que
     close(LISTEFIC) ;
     

     $gh> perl traiteListe2.pl
     
     syntaxe : perl traiteListe2.pl NOM_DE_FICHIER
     
     $gh> perl traiteListe2.pl  listeFic.txt
     
     fichier listeFic.txt non vu.
     
     $gh> perl traiteListe2.pl listeFic1.txt
     
     Traitement du fichier     1 : fichier01.txt
     Traitement du fichier     2 : fichier02.txt
     Traitement du fichier     3 : fichier03.txt
     Traitement du fichier     4 : fichier05.txt
     
     
     $gh> perl traiteListe2.pl listeFic2.txt
     
     Traitement du fichier     1 : fichier01.txt
     Traitement du fichier     2 : fichier02.txt
     Traitement du fichier     3 : fichier03.txt
       ==> Fichier numéro 4 : fichier04.txt non vu
     Traitement du fichier     5 : fichier05.txt
     

Pour le troisième script, nous générons la liste à la volée et nous ajoutons la date et l'heure. Le programme nommé quidure est un script Bash qui dure de 2 à 8 secondes.


     # # (gH)   -_-  traiteListe3.pl  ;  TimeStamp (unix) : 04 Mars 2019 vers 14:06
     
     use strict ;
     use warnings FATAL => 'all';
     
     # construction à la volée de la liste des fichiers
     
     my @list = split(/\n/,`ls fichier*txt`) ;
     
     # comptage explicite du nombre de fichiers
     
     my $nbf = 0 ;
     foreach my $file (@list) {
          $nbf++ ;
     } ; # fin pour chaque fichier
     
     # boucle de traitement
     
     my $idf = 0 ;
     foreach my $file (@list) {
        $idf++ ;
        print "Traitement du fichier ".sprintf("%3d",$idf)." / $nbf : $file ".`date`."\n" ;
        system("quidure") ;
     } ; # fin pour chaque fichier
     

     $gh> perl traiteListe3.pl
     
     Traitement du fichier   1 / 4 : fichier01.txt lundi 4 mars 2019, 14:22:16 (UTC+0100)
     Traitement du fichier   2 / 4 : fichier02.txt lundi 4 mars 2019, 14:22:18 (UTC+0100)
     Traitement du fichier   3 / 4 : fichier03.txt lundi 4 mars 2019, 14:22:20 (UTC+0100)
     Traitement du fichier   4 / 4 : fichier05.txt lundi 4 mars 2019, 14:22:22 (UTC+0100)
     

Enfin, pour le quatrième script, nous calculons la durée totale du script.


     # # (gH)   -_-  traiteListe3.pl  ;  TimeStamp (unix) : 04 Mars 2019 vers 14:06
     
     use strict ;
     use warnings FATAL => 'all';
     use POSIX ;
     
     ###############################################################################################################
     #
     # sous-programmes
     #
     ###############################################################################################################
     
      sub dateEtHeure {
     
     #################################################################################
     
               my ($sec,$min,$hour,$mday,$mmon,$year)=localtime();
               $mmon = $mmon + 1 ;
               $year = $year + 1900 ;
               #if (length($sec)<2)  { $sec = "0$sec" } ;
               #if (length($mday)<2) { $mday = "0$mday" } ;
               #if (length($mmon)<2) { $mmon = "0$mmon" } ;
               $sec  = sprintf("%02d",$sec) ;
               $min  = sprintf("%02d",$min) ;
               $mday = sprintf("%02d",$mday) ;
               $mmon = sprintf("%02d",$mmon) ;
               $hour = sprintf("%02d",$hour) ;
               my $now  = $mday."/".$mmon."/".$year;
               $now .= " ".$hour.":".$min ;
     
               return $now ;
     
           } ; # fin sub dateEtHeure
     
     #################################################################################
     
     sub duration {
     
     #################################################################################
     
             my $temps1 = $_[0] ;
             my $temps2 = $_[1] ;
             my $option = $_[2] ;
             my $durationInSeconds  = $temps2 - $temps1 ;
             my $retStr = "" ;
             if ($option eq "long") { $retStr .=  "Elapsed time:" ; } ;
     
             my $years = floor($durationInSeconds / 31536000) ;
             $durationInSeconds -= $years * 31536000 ;
             my $days = floor($durationInSeconds / 86400);
             $durationInSeconds -= $days * 86400;
             my $hours = floor($durationInSeconds / 3600);
             $durationInSeconds -= $hours * 3600;
             my $minutes = floor($durationInSeconds / 60);
             my $seconds = $durationInSeconds - $minutes * 60;
     
             if($years > 0) {
               $retStr .= $years . ' an';
               if ($years>1) { $retStr .= 's' ; } ;
             } ; # fin si
     
             if($days > 0) {
               $retStr .= ' '.$days . ' jour';
               if ($days>1) { $retStr .= 's' ; } ;
             } ; # fin si
     
             if($hours > 0) {
               $retStr .= ' ' . $hours . ' h';
             } ; # fin si
     
             if($minutes > 0) {
               $retStr .= ' ' . sprintf("%02d",$minutes) . ' min';
             } ; # fin si
     
             if($seconds > 0) {
               $retStr .= ' ' . sprintf("%02d",$seconds) . ' sec';
             } ; # fin si
     
             if (($retStr eq "") or ($retStr eq "Elapsed time:") ) { $retStr .= " 0 seconds" ; } ;
     
             return( $retStr ) ;
     
           } ; # fin sub duration
     
     
     ###############################################################################################################
     
     # message de début et initialisation
     
     print "\n" ;
     my $timeDeb = time() ;
     print &dateEtHeure().". DEBUT DE TRAITEMENT\n" ;
     print "\n" ;
     
     # construction à la volée de la liste des fichiers
     
     my @list = split(/\n/,`ls fichier*txt`) ;
     
     # comptage explicite du nombre de fichiers
     
     my $nbf = 0 ;
     foreach my $file (@list) {
          $nbf++ ;
     } ; # fin pour chaque fichier
     
     # boucle de traitement
     
     my $idf = 0 ;
     foreach my $file (@list) {
        $idf++ ;
        print "Traitement du fichier ".sprintf("%3d",$idf)." / $nbf : $file ".`date` ; # pas besoin de ."\n" ;
        system("quidure") ;
     } ; # fin pour chaque fichier
     
     print "\n" ;
     my $timeFin = time() ;
     my $dure    = &duration($timeDeb,$timeFin,"long") ;
     print &dateEtHeure().". FIN DE TRAITEMENT. $dure.\n\n" ;
     

     $gh> perl traiteListe4.pl
     
     04/03/2019 14:22. DEBUT DE TRAITEMENT
     
     Traitement du fichier   1 / 4 : fichier01.txt lundi 4 mars 2019, 14:22:33 (UTC+0100)
     Traitement du fichier   2 / 4 : fichier02.txt lundi 4 mars 2019, 14:22:35 (UTC+0100)
     Traitement du fichier   3 / 4 : fichier03.txt lundi 4 mars 2019, 14:22:37 (UTC+0100)
     Traitement du fichier   4 / 4 : fichier05.txt lundi 4 mars 2019, 14:22:39 (UTC+0100)
     
     04/03/2019 14:22. FIn DE TRAITEMENT. Elapsed time: 08 sec.
     

3. Scripts Python pour traiter des longues listes de fichiers

Fort des expériences précédentes avec les script Perl, nous pouvons écrire la même progression dans le traitement de listes de fichiers :

Script 1 : traiteListe1.py


     
     # -*- coding:latin1 -*-
     # # (gH)   -_-  traiteListe1.py  ;  TimeStamp (unix) : 04 Mars 2019 vers 15:28
     
     import sys
     import os
     
     listeFic = "listeFic1.txt" # liste des fichiers à traiter
     
     # on sort si le fichier n'existe pas
     
     if (not os.path.isfile(listeFic)) :
        print("fichier "+ listeFic + " non vu. STOP.")
        sys.exit(2)
     # fin de si fichier non vu
     
     # parcours des fichiers de la liste
     
     nbFic = 0
     with open(listeFic) as LISTEFIC :
          for nomFichier in LISTEFIC :
              nomFichier = nomFichier.strip()
              nbFic += 1
              print("Traitement du fichier " + str(nbFic)+ " : " + nomFichier)
          # fin pour
     LISTEFIC.close()
     # fin d'ouverture de fichier
     

Script 2 : traiteListe2.py


     
     # -*- coding:latin1 -*-
     # # (gH)   -_-  traiteListe2.py  ;  TimeStamp (unix) : 04 Mars 2019 vers 15:44
     
     import sys
     import os
     
     # si pas de paramètre, rappel de la syntaxe et sortie
     
     if (len(sys.argv)==1) :
             aide = '''
             syntaxe : python3 traiteListe2.py NOM_DE_FICHIER
             '''
             print(aide)
             sys.exit()
     # fin de si
     
     # on sort si le fichier n'existe pas
     
     listeFic = sys.argv[1]
     
     if (not os.path.isfile(listeFic)) :
        print("fichier "+ listeFic + " non vu. STOP.")
        sys.exit(2)
     # fin de si fichier non vu
     
     # parcours des fichiers de la liste
     
     nbFic = 0
     with open(listeFic) as LISTEFIC :
          for nomFichier in LISTEFIC :
              nomFichier = nomFichier.strip()
              nbFic += 1
              if (not os.path.isfile(nomFichier)) :
                 print("  ==> Fichier numéro " + str(nbFic)+ " : " + nomFichier + " non vu ")
              else :
                 print("Traitement du fichier " + ('%4d' % nbFic) + " : " + nomFichier)
     

Script 3 : traiteListe3.py


     # -*- coding:latin1 -*-
     # # (gH)   -_-  traiteListe3.py  ;  TimeStamp (unix) : 04 Mars 2019 vers 15:53
     
     import pathlib
     import subprocess
     
     # lecture à la volée de la liste des fichiers
     
     currentDirectory = pathlib.Path('.')
     currentPattern = "fichier*.txt"
     
     # comptage explicite du nombre de fichiers
     
     nbf = 0
     for file in currentDirectory.glob(currentPattern):
         nbf += 1
     
     idf = 0
     for file in currentDirectory.glob(currentPattern):
        idf += 1
        dateEtHeure = subprocess.run("date", stdout=subprocess.PIPE)
        print("Traitement du fichier "   + ('%4d' % idf) + " / " + str(nbf) + " : " + str(file ) + dateEtHeure.stdout.decode(),end="")
        #system("quidure") ;
     
     

Script 4 : traiteListe4.py


     # -*- coding:latin1 -*-
     # # (gH)   -_-  traiteListe4.py  ;  TimeStamp (unix) : 04 Mars 2019 vers 16:03
     
     import pathlib
     import subprocess
     import datetime
     from os import system
     
     # lecture à la volée de la liste des fichiers
     
     currentDirectory = pathlib.Path('.')
     currentPattern = "fichier*.txt"
     
     # comptage explicite du nombre de fichiers
     
     nbf = 0
     for file in currentDirectory.glob(currentPattern):
         nbf += 1
     
     dateEtHeure = subprocess.run("date", stdout=subprocess.PIPE)
     print(dateEtHeure.stdout.decode().strip() + "DEBUT DE TRAITEMENT" )
     date1 = datetime.datetime.now()
     
     idf = 0
     for file in currentDirectory.glob(currentPattern):
        idf += 1
        dateEtHeure = subprocess.run("date", stdout=subprocess.PIPE)
        print("Traitement du fichier "   + ('%4d' % idf) + " / " + str(nbf) + " : " + str(file ) + dateEtHeure.stdout.decode(),end="")
        system("./quidureLongtemps") ;
     
     dateEtHeure = subprocess.run("date", stdout=subprocess.PIPE)
     date2 = datetime.datetime.now()
     print(dateEtHeure.stdout.decode().strip() + "FIN DE TRAITEMENT. Duration ", str(date2 - date1))
     

4. Commandes pour gérer des traitements longs

Les scripts présentés ci-dessus sont prévus pour s'exécuter en mode terminal dans un temps "raisonnable". Si on ferme le terminal, si on ferme la fenêtre associée ou si on éteint l'ordinateur, alors le script s'arrête et tout est perdu. Comment faire alors pour exécuter des traitements longs ou très longs ?

Tout d'abord, il faut soit laisser l'ordinateur allumé, soit disposer d'un serveur qui, par définition, fonctionne en permanence. Ensuite, il ne faut pas lancer le script directement, mais le lancer en tâche de fin via la commande nohup. Lorsque nohup est lancé, on peut fermer le terminal, le script continue de s'exécuter. On peut ensuite consulter dans le fichier nohup.out ce qui est affiché par le script -- si on bien pensé à lui faire écrire ce qu'il fait !

En cours d'exécution, il est possible consulter en temps réel ce qu'écrit le script via la commande tail -f nohup.out.

Voici un exemple d'utilisation :


     ------- commandes du lundi
     
     $gh> # lundi, 9 h
     $gh> rm nohup.out # mais pourquoi ?
     $gh> nohup py monScriptTresLong.py &
     
     $gh> # lundi, 14 h
     $gh> tail -f nohup.out
     
          fichier 1589 / 3677411
          fichier 1590 / 3677411
          fichier 1591 / 3677411
     ...
     #  ici on tape ^C pour arreter le tail -f
     
     $gh> exit
     
     ------- commandes du mardi
     
     $gh> # mardi, 14 h
     $gh> tail -n 3 nohup.out
     
          fichier 35678 / 3677411
          fichier 35679 / 3677411
          fichier 35680 / 3677411
     
     ------- une semaine plus tard
     
     $gh> tail -n 3 nohup.out
     
          fichier 3677411 / 3677411
     
          (gH) : fin du script très long.
     

5. Quelques exemples de scripts Python et Perl sophistiqués

Vous trouverez ci-dessous quelques exemples de script Perl et Python nettement plus sophistiqués. Certains ont des pages Web d'explications, d'autres sont disponibles tels quels. Lire les scripts des autres est souvent très riche d'enseignement...

6. Questions/réponses pour tester vos connaissances

Question 6.1

Pourquoi faut-il noter les fichiers fic001.txt plutôt que fic1.txt ?

Question 6.2

A quoi sert la partie de code sprintf("%4d",$nbFic) dans le fichier traiteListe2.pl ?

Quel est son équivalent dans le fichier traiteListe2.py ?

Question 6.3

Traduire le script Bash nommé regnes en un script Python ou Perl, au choix.

Serait-ce beaucoup plus difficile de compter combien de fois chaque règne est vu dans le fichier ?

Voici ce qu'on doit trouver :


     774 lignes vues dans lea.tsv
     1 : Alveolata         1
     2 : Bacteria         38
     3 : Euryarchaeota     1
     4 : Fungi            11
     5 : Metazoa          23
     6 : Parabasalidea     1
     7 : Viridiplantae   698
     Total : 773 pour 7 règnes
     

Solutions :    afficher les solutions  

 

Cliquer  ici  pour revenir à la page de départ des cours CMI / M1.

 

 

retour gH    Retour à la page principale de   (gH)