30. Le langage Perl

30.1. Langage, scalaires, listes et tableaux

  1. De quelle version de Perl disposez-vous ?

    
vanvincq@CP2L ~/Bureau/CPLL/JournalDeBord $ perl -v
    
    This is perl, v5.10.1 (*) built for x86_64-linux-gnu-thread-multi
    (with 56 registered patches, see perl -V for more detail)
    
    Copyright 1987-2009, Larry Wall
    
    Perl may be copied only under the terms of either the Artistic License or the
    GNU General Public License, which may be found in the Perl 5 source kit.
    
    Complete documentation for Perl, including FAQ lists, should be found on
    this system using "man perl" or "perldoc perl".  If you have access to the
    Internet, point your browser at http://www.perl.org/, the Perl Home Page.
    
  2. Écrivez une fonction bonjour qui prend le prénom d'une personne en paramètre et qui lui souhaite bonjour. L'appel bonjour('Julie') affichera Bonjour Julie ! à l'écran.

    
#!/usr/bin/perl -w
    use strict;
    
    # simple fonction de salutations
    sub bonjour {
        my ($prenom) = @_; # un argument attendu
        print "Bonjour $prenom !\n";
    }
    
    bonjour('Clément');
    
    
vanvincq@CP2L ~/Bureau $ ./bonjour.pl
    Bonjour Clément !
    

    Dans une deuxième étape, testez si la fonction est appelée sans paramètre : bonjour(). Dans ce cas l'affichage suivant sera Bonjour belle inconnue !

    
#!/usr/bin/perl -w
    use strict;
    
    # simple fonction de salutations
    sub bonjour {
        my ($prenom) = @_; # un argument attendu
    
        if (defined($prenom)) {
            print "Bonjour $prenom !\n";
        } else {
            print "Bonjour belle inconnue !\n";
        }
    }
    
    bonjour('Clément');
    bonjour();
    
    
vanvincq@CP2L ~/Bureau $ ./bonjour.pl
    Bonjour Clément !
    Bonjour belle inconnue !
    

    Dans une dernière étape, faite en sorte de pouvoir donner le prénom de votre belle en argument de votre script (tableau @ARGV) et de le passer en paramètre à votre fonction.

    
#!/usr/bin/perl -w
    use strict;
    
    # simple fonction de salutations
    sub bonjour {
        my ($prenom) = @_; # un argument attendu
    
        if (defined($prenom)) {
            print "Bonjour $prenom !\n";
        } else {
            print "Bonjour belle inconnue !\n";
        }
    }
    
    bonjour('Clément');
    bonjour();
    bonjour(@ARGV);
    
    
vanvincq@CP2L ~/Bureau $ ./bonjour.pl Chloé
    Bonjour Clément !
    Bonjour belle inconnue !
    Bonjour Chloé !
    
  3. Écrivez une fonction TableMult qui affiche la table de multiplication de 1 à n en utilisant foreach. Les colonnes pourront être alignées sur cinq caractères à l'aide de la fonction printf, par exemple vous pouvez utiliser printf("%5s,...");

    
#!/usr/bin/perl -w
    
    sub TableMult {
        my ($chiffre) = @_;
    
        foreach my $i (1..$chiffre) {
            foreach my $j (1..$chiffre) {
                printf("%5d", $j*$i);
            }
            print "\n";
        }
    }
    
    TableMult(@ARGV);
    
    
vanvincq@CP2L ~/Bureau $ ./tableMultiplication.pl 8
        1    2    3    4    5    6    7    8
        2    4    6    8   10   12   14   16
        3    6    9   12   15   18   21   24
        4    8   12   16   20   24   28   32
        5   10   15   20   25   30   35   40
        6   12   18   24   30   36   42   48
        7   14   21   28   35   42   49   56
        8   16   24   32   40   48   56   64
    
  4. Écrire un programme qui successivement :

    • crée un tableau contenant 1, 2 et 3,

    • ajoute 4 puis 5 à la fin du tableau,

    • affiche le tableau (les éléments seront séparés par des espaces),

    • ajoute 0 puis -1 au début du tableau,

    • remplace la valeur d'indice 3 par 9,

    • multiplie par 2 chaque terme du tableau (fonction map),

    • ne garde dans le tableau que les termes positifs (fonction grep),

    • trie le tableau par ordre décroissant (fonction sort).

    
#!/usr/bin/perl -w
    
    # Création du tableau
    my @tableau = (1, 2, 3);
    
    # Ajout de 4 et 5 à la fin du tableau
    push(@tableau, 4, 5);
    
    # Affichage du contenu (en utilisant le foreach)
    foreach (@tableau) {
        print "$_ ";
    }
    print "\n";
    
    # Ajout de 0 et -1 au début du tableau
    unshift(@tableau, -1, 0);
    print "@tableau\n";
    
    # Remplace la valeur d'indice 3 par 9
    $tableau[3] = 9;
    print "@tableau\n";
    
    # Multiplie chaque élément du tableau par 2
    map { $_ *= 2 } @tableau; # ou @tableau = map { $_ * 2 } @ tableau;
    print "@tableau\n";
    
    # Ne garder que les termes > 0
    @tableau = grep { $_ > 0 } @tableau;
    print "@tableau\n";
    
    # Trie décroissant
    @tableau = sort( { $b <=> $a } @tableau);
    print "@tableau\n";
    
    
vanvincq@CP2L ~/Bureau $ ./tableau.pl
    1 2 3 4 5 
    -1 0 1 2 3 4 5
    -1 0 1 9 3 4 5
    -2 0 2 18 6 8 10
    2 18 6 8 10
    18 10 8 6 2
    
  5. Écrivez une fonction Diviseurs qui calcul et affiche tous les diviseurs d'un nombre passé en paramètre (utilisez l'opérateur modulo).

    
#!/usr/bin/perl -w
    
    sub Diviseurs {
        my ($entier) = @_;
        print "Liste des diviseurs de $entier :\n";
    
        foreach my $i (1..$entier) {
            if ($entier % $i == 0) {
                printf("%d\n", $entier/$i);
            }
        }
    }
    
    Diviseurs(@ARGV);
    
    
vanvincq@CP2L ~/Bureau $ ./diviseurs.pl 10054965
    Liste des diviseurs de 10054965 :
    10054965
    3351655
    2010993
    670331
    44295
    14765
    8859
    3405
    2953
    1135
    681
    227
    15
    5
    3
    1
    

30.2. Fichiers, tables de hachage et expressions régulières

  1. Écrire un programme qui :

    • Place dans un tableau la liste des fichiers et répertoires de votre home.

    • Ne conserve dans ce tableau que les executables (droit Unix 'x') (fonction Grep).

    • Trie ce tableau par ordre de taille de fichier (attention à ne pas confondre la taille du nom de fichier).

    • Place dans un second tableau la taille respective de chaque fichier du tableau (fonction map).

    
#!/usr/bin/perl -w
    
    # Récupération de tous les fichiers et répertoires du /home
    my @mesFichiers = glob('~/.* ~/*');
    # On ne garde que les exécutables
    @mesFichiers = grep {-x $_ } @mesFichiers;
    # Trie du tableau par taille de fichier
    @mesFichiers = sort { (-s $a) <=> (-s $b) } @mesFichiers;
    # On place la taille des fichiers dans un autre tableau
    my @mesTailles = map { -s $_ } @mesFichiers;
    # Plus simple que la version précédente :
    # my @mesTailles = map { (stat($_))[7] } @mesFichiers;
    
    # Pour i allant de 0 à la taille du tableau "mesTailles"
    foreach my $i (0..@mesTailles - 1) {
        print "$mesFichiers[$i] $mesTailles[$i]\n";
    }
    
    
vanvincq@CP2L ~/Bureau $ ./fichiers.pl
    /home/vanvincq/. 4096
    /home/vanvincq/.. 4096
    /home/vanvincq/.adobe 4096
    /home/vanvincq/.cache 4096
    /home/vanvincq/.codeblocks 4096
    /home/vanvincq/.config 4096
    /home/vanvincq/.dbus 4096
    /home/vanvincq/.emacs.d 4096
    /home/vanvincq/.evolution 4096
    /home/vanvincq/.filezilla 4096
    /home/vanvincq/.fontconfig 4096
    /home/vanvincq/.gconf 4096
    /home/vanvincq/.gconfd 4096
    /home/vanvincq/.gegl-0.0 4096
    /home/vanvincq/.gimp-2.6 4096
    /home/vanvincq/.gnome2 4096
    /home/vanvincq/.gnome2_private 4096
    /home/vanvincq/.gnupg 4096
    /home/vanvincq/.gstreamer-0.10 4096
    /home/vanvincq/.gvfs 4096
    /home/vanvincq/.icedove 4096
    /home/vanvincq/.icons 4096
    /home/vanvincq/.lftp 4096
    /home/vanvincq/.libreoffice 4096
    /home/vanvincq/.local 4096
    /home/vanvincq/.macromedia 4096
    /home/vanvincq/.mozilla 4096
    /home/vanvincq/.nautilus 4096
    /home/vanvincq/.netkit 4096
    /home/vanvincq/.nv 4096
    /home/vanvincq/.pki 4096
    /home/vanvincq/.pulse 4096
    /home/vanvincq/.ssh 4096
    /home/vanvincq/.texmf-var 4096
    /home/vanvincq/.themes 4096
    /home/vanvincq/.thumbnails 4096
    /home/vanvincq/.update-notifier 4096
    /home/vanvincq/.wireshark 4096
    /home/vanvincq/.xchat2 4096
    /home/vanvincq/.xxe5 4096
    /home/vanvincq/Bureau 4096
    /home/vanvincq/Documents 4096
    /home/vanvincq/Images 4096
    /home/vanvincq/Modèles 4096
    /home/vanvincq/Musique 4096
    /home/vanvincq/pt 4096
    /home/vanvincq/Public 4096
    /home/vanvincq/Téléchargements 4096
    /home/vanvincq/Vidéos 4096
    
  2. Écrivez un programme qui prend en arguments sur sa ligne de commande des noms de mois (janvier, décembre, ...) et qui affiche leur nombre de jours (28, 30, ...) à l'écran. Utilisez une table de hachage (gérer le cas d'un mois inexistant).

    
#!/usr/bin/perl -w
    
    my %mois = (
        "Janvier"   =>  31,
        "Février"   =>  28,
        "Mars"      =>  31,
        "Avril"     =>  30,
        "Mai"       =>  31,
        "Juin"      =>  30,
        "Juillet"   =>  31,
        "Août"      =>  31,
        "Septembre" =>  30,
        "Octobre"   =>  31,
        "Novembre"  =>  30,
        "Décembre"  =>  31
    );
    
    foreach(@ARGV) {
        print "$_\t:\t";
    
        if (exists($mois{$_})) {
            print "$mois{$_}\n";
        } else {
            print "inconnu\n";
        }
    }
    
    
vanvincq@CP2L ~/Bureau $ ./mois.pl Janvier Février Bière Juin Mars
    Janvier	:	31
    Février	:	28
    Bière	:	inconnu
    Juin	:	30
    Mars	:	31
    

    Dans une seconde variante, comment faire pour accepter tous les mois sans tenir compte de la casse ?

    Pour simplifier un peu la tâche, j'ai transformé les clefs intitiales en minuscules.

    
#!/usr/bin/perl -w
    
    my %mois = (
        "janvier"   =>  31,
        "février"   =>  28,
        "mars"      =>  31,
        "avril"     =>  30,
        "mai"       =>  31,
        "juin"      =>  30,
        "juillet"   =>  31,
        "août"      =>  31,
        "septembre" =>  30,
        "octobre"   =>  31,
        "novembre"  =>  30,
        "décembre"  =>  31
    );
    
    foreach(@ARGV) {
        my $key = $_; 
        $key =~ tr/A-Z/a-z/;
    
        print "$key\t:\t";
    
        if (exists($mois{$key})) {
            print "$mois{$key}\n";
        } else {
            print "inconnu\n";
        }
    }
    
    
vanvincq@CP2L ~/Bureau $ ./mois.pl Janvier JANVIER mArS
    janvier	:	31
    janvier	:	31
    mars	:	31
    
  3. Les lignes du fichier système passwd sont de format login:passwd:uid:gid:info:home:shell. Écrivez un programme qui créé, à partir du fichier /etc/passwd, une table de hachage associant la séquence mot de passe au login (le login sera la clef et le mot de passe sera la valeur). Utilisez la fonction split.

    Dans une seconde étape, vous afficherez les couples (login, passwd) mémorisés dans la table de hachage.

    Enfin dans une dernière étape, le programme supprimera de cette table tous les utilisateurs dont le login contient une ou plusieurs voyelles.

    
#!/usr/bin/perl -w
    
    my ($login, $passwd);
    my %hash = ();
    
    # Ouverture du fichier en mode lecture
    if (!open(FILE, "/etc/passwd")) {
        exit(1);
    }
    
    # Absorption du contenu
    my @contentFile = <FILE>;
    
    # Pour chaque ligne du fichier, récupérer le deux premiers champs
    foreach (@contentFile) {
        ($login, $passwd) = split (/:/, $_);
        $hash{ $login } = $passwd;
    }
    
    # Affichage du contenu de la liste
    while (my ($key, $value) = each(%hash)) {
        print "$key => $value\n"; 
    }
    
    # Suppression des utilisateurs ayant une ou plusieurs voyelles
    foreach $login (keys(%hash)) {
        if ($login =~ m/[aeiouy]/i) {
            delete ($hash{ $login });
        }
    }
    
    
    # Affichage du contenu de la liste
    print "\nContenu de la table après suppression :\n";
    while (my ($key, $value) = each(%hash)) {
        print "$key => $value\n";  
    }
    
    # Fermeture du fichier
    close(FILE);
    
    
vanvincq@CP2L ~/Bureau $ ./password.pl
    telnetd => x
    bin => x
    usbmux => x
    nobody => x
    lp => x
    Debian-gdm => x
    irc => x
    list => x
    messagebus => x
    sys => x
    Debian-exim => x
    gnats => x
    saned => x
    cl-builder => x
    avahi => x
    vanvincq => x
    proxy => x
    avahi-autoipd => x
    games => x
    libuuid => x
    hplip => x
    www-data => x
    proftpd => x
    ftp => x
    statd => x
    mail => x
    root => x
    daemon => x
    backup => x
    uucp => x
    sync => x
    man => x
    news => x
    sshd => x
    
    Contenu de la table après suppression :
    lp => x
    ftp => x
    sshd => x
    
  4. Le but de cet exercice est la manipulation des expressions régulières (ne pas utiliser split ici). Il vous est demandé d'écrire un programme manipulant le même fichier /etc/passwd et d'afficher les informations qui vous seront demandées.

    Ce programme comportera uniquement les instructions nécessaires à la lecture du fichier ainsi que l'expression régulière demandée et un simple appel à la fonction print. Une fois que vous avez répondu à une question, vous pouvez mettre en commentaire les lignes de codes la concernant et passer à la suivante.

    Il vous est demandé d'afficher :

    1. la ligne correspondante à l'utilisateur root;

      
#!/usr/bin/perl -w
      
      # Ouverture du fichier
      open (FILE, "/etc/passwd") || die();
      
      print "Ligne correspondante à l'utilisateur root :\t";
      while (<FILE>) {
          print "$_" if ( $_ =~ m/^root\b/);
      }
      
      # Fermeture du fichier
      close (FILE);
      
      
vanvincq@CP2L ~ $ ./expression.pl
      Ligne correspondante à l'utilisateur root :	root:x:0:0:root:/root:/bin/bash
      
    2. les lignes correspondantes aux utilisateurs qui n'ont pas /bin/sh comme shell (commencez par chercher ceux qui ont /bin/sh puis nier);

      
#!/usr/bin/perl -w
      
      # Ouverture du fichier
      open (FILE, "/etc/passwd") || die();
      
      print "Lignes correspondantes aux utilisateurs sans /bin/sh :\n";
      while (<FILE>) {
          print "$_" if ( $_ !~ m:/bin/sh$:);
      }
      
      # Fermeture du fichier
      close (FILE);
      
      
vanvincq@CP2L ~ $ ./expression.pl
      Lignes correspondantes aux utilisateurs sans /bin/sh :
      root:x:0:0:root:/root:/bin/bash
      sync:x:4:65534:sync:/bin:/bin/sync
      Debian-exim:x:101:103::/var/spool/exim4:/bin/false
      statd:x:102:65534::/var/lib/nfs:/bin/false
      messagebus:x:103:106::/var/run/dbus:/bin/false
      avahi:x:104:107:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
      usbmux:x:105:46:usbmux daemon,,,:/home/usbmux:/bin/false
      saned:x:106:115::/home/saned:/bin/false
      hplip:x:107:7:HPLIP system user,,,:/var/run/hplip:/bin/false
      Debian-gdm:x:108:116:Gnome Display Manager:/var/lib/gdm3:/bin/false
      avahi-autoipd:x:109:117:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false
      vanvincq:x:1000:1000:Vanvincq Clément,,,:/home/vanvincq:/bin/bash
      cl-builder:x:110:118::/usr/share/common-lisp/:/bin/false
      sshd:x:111:65534::/var/run/sshd:/usr/sbin/nologin
      proftpd:x:112:65534::/var/run/proftpd:/bin/false
      ftp:x:1010:1010:Accès ftp authentifié,,,,:/home/ftp:/bin/true
      telnetd:x:114:119::/nonexistent:/bin/false
      
    3. les lignes en remplaçant /home par /mnt/home;

      
#!/usr/bin/perl -w
      
      # Ouverture du fichier
      open (FILE, "/etc/passwd") || die();
      
      while (<FILE>) {
          $_ =~ s@/home@/mount/home@; # on peut utiliser tout autre séparateur au choix comme ':', etc.
          print "$_";
      }
      
      # Fermeture du fichier
      close (FILE);
      
      
vanvincq@CP2L ~ $ ./expression.pl
      root:x:0:0:root:/root:/bin/bash
      daemon:x:1:1:daemon:/usr/sbin:/bin/sh
      bin:x:2:2:bin:/bin:/bin/sh
      sys:x:3:3:sys:/dev:/bin/sh
      sync:x:4:65534:sync:/bin:/bin/sync
      games:x:5:60:games:/usr/games:/bin/sh
      man:x:6:12:man:/var/cache/man:/bin/sh
      lp:x:7:7:lp:/var/spool/lpd:/bin/sh
      mail:x:8:8:mail:/var/mail:/bin/sh
      news:x:9:9:news:/var/spool/news:/bin/sh
      uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
      proxy:x:13:13:proxy:/bin:/bin/sh
      www-data:x:33:33:www-data:/var/www:/bin/sh
      backup:x:34:34:backup:/var/backups:/bin/sh
      list:x:38:38:Mailing List Manager:/var/list:/bin/sh
      irc:x:39:39:ircd:/var/run/ircd:/bin/sh
      gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
      nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
      libuuid:x:100:101::/var/lib/libuuid:/bin/sh
      Debian-exim:x:101:103::/var/spool/exim4:/bin/false
      statd:x:102:65534::/var/lib/nfs:/bin/false
      messagebus:x:103:106::/var/run/dbus:/bin/false
      avahi:x:104:107:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
      usbmux:x:105:46:usbmux daemon,,,:/mount/home/usbmux:/bin/false
      saned:x:106:115::/mount/home/saned:/bin/false
      hplip:x:107:7:HPLIP system user,,,:/var/run/hplip:/bin/false
      Debian-gdm:x:108:116:Gnome Display Manager:/var/lib/gdm3:/bin/false
      avahi-autoipd:x:109:117:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false
      vanvincq:x:1000:1000:Vanvincq Clément,,,:/mount/home/vanvincq:/bin/bash
      cl-builder:x:110:118::/usr/share/common-lisp/:/bin/false
      sshd:x:111:65534::/var/run/sshd:/usr/sbin/nologin
      proftpd:x:112:65534::/var/run/proftpd:/bin/false
      ftp:x:1010:1010:Accès ftp authentifié,,,,:/mount/home/ftp:/bin/true
      telnetd:x:114:119::/nonexistent:/bin/false
      
    4. les lignes en supprimant la séquence de mot de passe;

      
#!/usr/bin/perl -w
      
      # Ouverture du fichier
      open (FILE, "/etc/passwd") || die();
      
      while (<FILE>) {
          $_ =~ s/:[^:]+?:/:/;
          print "$_";
      }
      
      # Fermeture du fichier
      close (FILE);
      
      
cvanvinc@pinson ~/Bureau $ ./exercice.pl
      root:0:0:root:/root:/bin/bash
      daemon:1:1:daemon:/usr/sbin:/bin/sh
      bin:2:2:bin:/bin:/bin/sh
      sys:3:3:sys:/dev:/bin/sh
      sync:4:65534:sync:/bin:/bin/sync
      games:5:60:games:/usr/games:/bin/sh
      man:6:12:man:/var/cache/man:/bin/sh
      lp:7:7:lp:/var/spool/lpd:/bin/sh
      mail:8:8:mail:/var/mail:/bin/sh
      news:9:9:news:/var/spool/news:/bin/sh
      uucp:10:10:uucp:/var/spool/uucp:/bin/sh
      proxy:13:13:proxy:/bin:/bin/sh
      www-data:33:33:www-data:/var/www:/bin/sh
      backup:34:34:backup:/var/backups:/bin/sh
      list:38:38:Mailing List Manager:/var/list:/bin/sh
      irc:39:39:ircd:/var/run/ircd:/bin/sh
      gnats:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
      nobody:65534:65534:nobody:/nonexistent:/bin/sh
      libuuid:100:101::/var/lib/libuuid:/bin/sh
      messagebus:101:103::/var/run/dbus:/bin/false
      Debian-exim:102:104::/var/spool/exim4:/bin/false
      statd:103:65534::/var/lib/nfs:/bin/false
      avahi:104:107:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
      usbmux:105:46:usbmux daemon,,,:/home/usbmux:/bin/false
      Debian-gdm:106:114:Gnome Display Manager:/var/lib/gdm3:/bin/false
      saned:107:116::/home/saned:/bin/false
      hplip:108:7:HPLIP system user,,,:/var/run/hplip:/bin/false
      info:1000:1000:info,,,:/home/info:/bin/bash
      haldaemon:109:117:Hardware abstraction layer,,,:/var/run/hald:/bin/false
      cl-builder:110:119::/usr/share/common-lisp/:/bin/false
      mysql:111:120:MySQL Server,,,:/var/lib/mysql:/bin/false
      gdm:112:121:Gnome Display Manager:/var/lib/gdm:/bin/false
      sshd:113:65534::/var/run/sshd:/usr/sbin/nologin
      nath:1001:1001:,,,:/home/nath:/bin/bash
      
    5. les lignes en échangeant login et passwd.

      
#!/usr/bin/perl -w
      
      # Ouverture du fichier
      open (FILE, "/etc/passwd") || die();
      
      # Les parenthèses permettent d'inclure le regroupement mémorisant.
      while (<FILE>) {
          $_ =~ s/^([^:]+):([^:]+):/$2:$1:/;
          print "$_";
      }
      
      # Fermeture du fichier
      close (FILE);
      
      
cvanvinc@pinson ~/Bureau $ ./exercice.pl
      x:root:0:0:root:/root:/bin/bash
      x:daemon:1:1:daemon:/usr/sbin:/bin/sh
      x:bin:2:2:bin:/bin:/bin/sh
      x:sys:3:3:sys:/dev:/bin/sh
      x:sync:4:65534:sync:/bin:/bin/sync
      x:games:5:60:games:/usr/games:/bin/sh
      x:man:6:12:man:/var/cache/man:/bin/sh
      x:lp:7:7:lp:/var/spool/lpd:/bin/sh
      x:mail:8:8:mail:/var/mail:/bin/sh
      x:news:9:9:news:/var/spool/news:/bin/sh
      x:uucp:10:10:uucp:/var/spool/uucp:/bin/sh
      x:proxy:13:13:proxy:/bin:/bin/sh
      x:www-data:33:33:www-data:/var/www:/bin/sh
      x:backup:34:34:backup:/var/backups:/bin/sh
      x:list:38:38:Mailing List Manager:/var/list:/bin/sh
      x:irc:39:39:ircd:/var/run/ircd:/bin/sh
      x:gnats:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
      x:nobody:65534:65534:nobody:/nonexistent:/bin/sh
      x:libuuid:100:101::/var/lib/libuuid:/bin/sh
      x:Debian-exim:101:103::/var/spool/exim4:/bin/false
      x:statd:102:65534::/var/lib/nfs:/bin/false
      x:messagebus:103:106::/var/run/dbus:/bin/false
      x:avahi:104:107:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
      x:usbmux:105:46:usbmux daemon,,,:/home/usbmux:/bin/false
      x:saned:106:115::/home/saned:/bin/false
      x:hplip:107:7:HPLIP system user,,,:/var/run/hplip:/bin/false
      x:Debian-gdm:108:116:Gnome Display Manager:/var/lib/gdm3:/bin/false
      x:avahi-autoipd:109:117:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false
      x:vanvincq:1000:1000:Vanvincq Clément,,,:/home/vanvincq:/bin/bash
      x:cl-builder:110:118::/usr/share/common-lisp/:/bin/false
      x:sshd:111:65534::/var/run/sshd:/usr/sbin/nologin
      x:proftpd:112:65534::/var/run/proftpd:/bin/false
      x:ftp:1010:1010:Accès ftp authentifié,,,,:/home/ftp:/bin/true
      x:telnetd:114:119::/nonexistent:/bin/false
      
      

30.3. Référence et application à l'administration système

  1. Construisez une référence vers une table de hachage dont les clefs seraient le login d'une personne et la valeur une référence vers une table de hachage comportant les clefs suivantes :

    • uid: son numéro d'utilisateur,

    • home: son répertoire personnel,

    • gid: une référence vers un tableau comportant ses numéros de groupe.

    Prennez par exemple :

    • root, d'uid 0, son répertoire personnel est /root et ses groupes sont 0, 2 et 10.

    • paul, d'uid 500, son répertoire personnel est /home/paul et ses groupes sont 200, 4 et 10.

    Faites un schéma de cette structure en mémoire.

    Figure 5. Schématisation de l'idée

    Affichez d'abord cette structure de données à l'aide du module Data::Dumper.

    
#!/usr/bin/perl -w
    use strict;
    use Data::Dumper;
    
    my $reference = {
        "root" => {
            "uid"  =>  0,
            "home" =>  "/root",
            "gid"  =>  [
                0, 2, 10]},
        "paul" => {
            "uid"   => 500,
            "home"  => "/home/paul",
            "gid"   => [
                200, 4, 10]}
    };
    
    print Dumper($reference);
    
    
cvanvinc@pinson ~/Bureau $ ./reference.pl
    $VAR1 = {
              'root' => {
                          'uid' => 0,
                          'home' => '/root',
                          'gid' => [
                                     0,
                                     2,
                                     10
                                   ]
                        },
              'paul' => {
                          'uid' => 500,
                          'home' => '/home/paul',
                          'gid' => [
                                     200,
                                     4,
                                     10
                                   ]
                        }
            };
    

    Écrivez ensuite le code Perl permettant d'afficher toute la structure sans utiliser ce module (le format d'affichage est libre): n'utilisez pas l'opérateur ref, mais utilisez le fait que vous connaissez les trois clefs des tables de hachages (uid, home et gid) ainsi que les types de leurs valeurs respectives.

    Dans un premier temps, affichez la liste des numéros de groupe avec foreach.

    
#!/usr/bin/perl -w
    use strict;
    use Data::Dumper;
    
    my $reference = {
        "root" => {
            "uid"  =>  0,
            "home" =>  "/root",
            "gid"  =>  [
                0, 2, 10]},
        "paul" => {
            "uid"   => 500,
            "home"  => "/home/paul",
            "gid"   => [
                200, 4, 10]}
    };
    
    print "Affichage libre sans Dumper:\n";
    foreach my $login ( keys %{$reference} ) {
        print "$login\n";
    
        print "\t$reference->{$login}->{'uid'}\n";
        print "\t$reference->{$login}->{'home'}\n";
    
        foreach my $value ( @{$reference->{$login}->{'gid'}} ) {
            print "\t\t$value\n";
        }
    }
    
    
vanvincq@CP2L ~/Bureau $ ./reference.pl
    Affichage libre sans Dumper:
    root
    	0
    	/root
    		0
    		2
    		10
    paul
    	500
    	/home/paul
    		200
    		4
    		10
    

    Dans un second temps avec join.

    
#!/usr/bin/perl -w
    use strict;
    use Data::Dumper;
    
    my $reference = {
        "root" => {
            "uid"  =>  0,
            "home" =>  "/root",
            "gid"  =>  [
                0, 2, 10]},
        "paul" => {
            "uid"   => 500,
            "home"  => "/home/paul",
            "gid"   => [
                200, 4, 10]}
    };
    
    print "Affichage libre sans Dumper:\n";
    foreach my $login ( keys %{$reference} ) {
        print "$login\n";
    
        print "\t$reference->{$login}->{'uid'}\n";
        print "\t$reference->{$login}->{'home'}\n";
    	print "\t".join (' ', @{$reference->{$login}->{'gid'}})."\n";
    }
    
    
vanvincq@CP2L ~/Bureau $ ./reference.pl
    Affichage libre sans Dumper:
    root
    	0
    	/root
    	0 2 10
    paul
    	500
    	/home/paul
    	200 4 10
    

    Affichez finalement chaque liste de numéros de groupe par ordre numérique.

    
#!/usr/bin/perl -w
    use strict;
    use Data::Dumper;
    
    my $reference = {
        "root" => {
            "uid"  =>  0,
            "home" =>  "/root",
            "gid"  =>  [
                0, 2, 10]},
        "paul" => {
            "uid"   => 500,
            "home"  => "/home/paul",
            "gid"   => [
                200, 4, 10]}
    };
    
    print "Affichage libre sans Dumper:\n";
    foreach my $login ( keys %{$reference} ) {
        print "$login\n";
    
        print "\t$reference->{$login}->{'uid'}\n";
        print "\t$reference->{$login}->{'home'}\n";
    	print "\t".join(' ', sort( {$a <=> $b} @{$reference->{$login}->{'gid'}} ))."\n";
    }
    
    
vanvincq@CP2L ~/Bureau $ ./reference.pl
    Affichage libre sans Dumper:
    root
    	0
    	/root
    	0 2 10
    paul
    	500
    	/home/paul
    	4 10 200
    
  2. Le but de cet exercice est la manipulation du fichier passwd. Ce fichier est composé de sept champs séparés par le caractère ':'. L'ordre des champs et le suivant :

    • login : le nom du compte de l'utilisateur,

    • passwd : la séquence chiffrée du mot de passe (ou * dans certains cas),

    • uid : le numéro de l'utilisateur (0 pour root),

    • gid : le numéro de groupe de l'utilisateur (attention : il n'y a ici qu'un seul numéro, ne pas confondre avec ce qui vous a été dit à l'exercice précédent),

    • info : des informations sur l'utilisateur (nom, etc.),

    • home : le répertoire personnel de l'utilisateur,

    • shell : le chemin de l'exécutable de son shell préféré.

    Voici les étapes du projet :

    1. Quelle structure pourrait-on utiliser pour stocker en mémoire toutes ces données et les manipuler facilement ?

      La solution la plus simple serait de créer un tableau contenant les références des différents utilisateurs vers une table de hachage dont les clefs seraient 'login', 'uid', etc.

    2. On se propose de stocker ces informations dans un tableau qui aura pour valeur des référence vers des tables de hachage dont les clefs seront login, uid, gid, info, passwd, home et shell. Faites un schéma de cette structure de données.

      Figure 6. Schématisation de la structure de données

    3. Écrivez une fonction parse qui prend en paramètre le nom du fichier à analyser. Elle renvoie une référence vers ce tableau et elle s'utilisera de la sorte : my $ref = parse('/etc/passwd');

      
#!/usr/bin/perl -w
      use strict;
      
      #** 
      # @function public parse ($filename)
      # @brief Ouvre un fichier et récupère son contenu (selon la structure /etc/passwd).
      # @params $filename required le nom du fichier à parser.
      # @retval \@array la référence vers le tableau.
      #*    
      sub parse {
      	my ($filename) = @_;
      	my @array = ();
      	my ($login, $passwd, $uid, $gid, $info ,$home, $shell);
      
      	open (FILE, $filename)
      		or die("open: $!");
      
      	while (<FILE>) {
      		($login, $passwd, $uid, $gid, $info ,$home, $shell) = split (/:/, $_);
      		push (@array, {
      			'login'		=>	$login,
      			'passwd'	=>	$passwd,
      			'uid'		=>	$uid,
      			'gid'		=>	$gid,
      			'info'		=>	$info,
      			'home'		=>	$home,
      			'shell'		=>	$shell}
      		);
      	}
      	close (FILE);
      	return \@array;
      }
      
      my $ref = parse('/etc/passwd');
      
    4. Écrivez une fonction dump qui effectue l'affichage de toutes ces informations. Chaque utilisateur sera affiché sur une seule ligne au même format que les lignes du fichier /etc/passwd et les utilisateurs seront affichés dans l'ordre de leur présence dans le fichier (pas de tri à faire, on respecte l'ordre du tableau).

      
#!/usr/bin/perl -w
      use strict;
      
      #** 
      # @function public parse ($filename)
      # @brief Ouvre un fichier et récupère son contenu (selon la structure /etc/passwd).
      # @params $filename required le nom du fichier à parser.
      # @retval \@array la référence vers le tableau.
      #*    
      sub parse {
      	my ($filename) = @_;
      	my @array = ();
      	my ($login, $passwd, $uid, $gid, $info ,$home, $shell);
      
      	open (FILE, $filename)
      		or die("open: $!");
      
      	while (<FILE>) {
      		($login, $passwd, $uid, $gid, $info ,$home, $shell) = split (/:/, $_);
      		push (@array, {
      			'login'		=>	$login,
      			'passwd'	=>	$passwd,
      			'uid'		=>	$uid,
      			'gid'		=>	$gid,
      			'info'		=>	$info,
      			'home'		=>	$home,
      			'shell'		=>	$shell}
      		);
      	}
      	close (FILE);
      	return \@array;
      }
      
      #** 
      # @function public dump ($reference)
      # @brief Affiche le contenu de la structure
      # @params $reference required la référence vers la structure
      #*  
      sub dump {
      	my ($reference) = @_;
      
      	foreach (@$reference) {
      		foreach my $value (values(%$_)) {
      			# Suppression des caractères de fin de ligne avec chomp
      			# plus simple que $value =~ s/\n//;
      			chomp $value;
      			print "$value:";
      		}
      		print "\n";
      	}
      }
      
      # Les '::' évitent l'ambiguité lors de l'appel de la fonction.
      ::dump(parse('/etc/passwd'));
      
      
vanvincq@CP2L ~/Bureau $ ./ultime.pl
      root:x:0:/bin/bash:/root:0:root:
      daemon:x:1:/bin/sh:/usr/sbin:1:daemon:
      bin:x:2:/bin/sh:/bin:2:bin:
      sys:x:3:/bin/sh:/dev:3:sys:
      sync:x:4:/bin/sync:/bin:65534:sync:
      games:x:5:/bin/sh:/usr/games:60:games:
      man:x:6:/bin/sh:/var/cache/man:12:man:
      lp:x:7:/bin/sh:/var/spool/lpd:7:lp:
      mail:x:8:/bin/sh:/var/mail:8:mail:
      news:x:9:/bin/sh:/var/spool/news:9:news:
      uucp:x:10:/bin/sh:/var/spool/uucp:10:uucp:
      proxy:x:13:/bin/sh:/bin:13:proxy:
      www-data:x:33:/bin/sh:/var/www:33:www-data:
      backup:x:34:/bin/sh:/var/backups:34:backup:
      Mailing List Manager:x:38:/bin/sh:/var/list:38:list:
      ircd:x:39:/bin/sh:/var/run/ircd:39:irc:
      Gnats Bug-Reporting System (admin):x:41:/bin/sh:/var/lib/gnats:41:gnats:
      nobody:x:65534:/bin/sh:/nonexistent:65534:nobody:
      :x:100:/bin/sh:/var/lib/libuuid:101:libuuid:
      :x:101:/bin/false:/var/spool/exim4:103:Debian-exim:
      :x:102:/bin/false:/var/lib/nfs:65534:statd:
      :x:103:/bin/false:/var/run/dbus:106:messagebus:
      Avahi mDNS daemon,,,:x:104:/bin/false:/var/run/avahi-daemon:107:avahi:
      usbmux daemon,,,:x:105:/bin/false:/home/usbmux:46:usbmux:
      :x:106:/bin/false:/home/saned:115:saned:
      HPLIP system user,,,:x:107:/bin/false:/var/run/hplip:7:hplip:
      Gnome Display Manager:x:108:/bin/false:/var/lib/gdm3:116:Debian-gdm:
      Avahi autoip daemon,,,:x:109:/bin/false:/var/lib/avahi-autoipd:117:avahi-autoipd:
      Vanvincq Clément,,,:x:1000:/bin/bash:/home/vanvincq:1000:vanvincq:
      :x:110:/bin/false:/usr/share/common-lisp/:118:cl-builder:
      :x:111:/usr/sbin/nologin:/var/run/sshd:65534:sshd:
      :x:112:/bin/false:/var/run/proftpd:65534:proftpd:
      Accès ftp authentifié,,,,:x:1010:/bin/true:/home/ftp:1010:ftp:
      :x:114:/bin/false:/nonexistent:119:telnetd:
      
    5. Dans le programme principal après l'appel à la fonction parse, modifiez vos données des manières suivantes :

      1. ne gardez que les comptes dont les uid sont des nombres pairs (% et grep),

        
#!/usr/bin/perl -w
        use strict;
        
        #** 
        # @function public parse ($filename)
        # @brief Ouvre un fichier et récupère son contenu (selon la structure /etc/passwd).
        # @params $filename required le nom du fichier à parser.
        # @retval \@array la référence vers le tableau.
        #*    
        sub parse {
        	my ($filename) = @_;
        	my @array = ();
        	my ($login, $passwd, $uid, $gid, $info ,$home, $shell);
        
        	open (FILE, $filename)
        		or die("open: $!");
        
        	while (<FILE>) {
        		($login, $passwd, $uid, $gid, $info ,$home, $shell) = split (/:/, $_);
        		push (@array, {
        			'login'		=>	$login,
        			'passwd'	=>	$passwd,
        			'uid'		=>	$uid,
        			'gid'		=>	$gid,
        			'info'		=>	$info,
        			'home'		=>	$home,
        			'shell'		=>	$shell}
        		);
        	}
        	close (FILE);
        	return \@array;
        }
        
        #** 
        # @function public dump ($reference)
        # @brief Affiche le contenu de la structure
        # @params $reference required la référence vers la structure
        #*  
        sub dump {
        	my ($reference) = @_;
        
        	foreach (@$reference) {
        		foreach my $value (values(%$_)) {
        			# Suppression des caractères de fin de ligne avec chomp
        			# plus simple que $value =~ s/\n//;
        			chomp $value;
        			print "$value:";
        		}
        		print "\n";
        	}
        }
        
        sub peersOnly {
        	my ($reference) = @_;
        
        	# On retourne directement une référence anonyme
        	return [ grep { !($_->{'uid'} % 2) } @$reference ];
        }
        
        my $ref = parse('/etc/passwd');
        $ref = peersOnly($ref);
        
        # Les '::' évitent l'ambiguité lors de l'appel de la fonction.
        ::dump($ref);
        
        
vanvincq@CP2L ~/Bureau $ ./ultime.pl
        root:x:0:/bin/bash:/root:0:root:
        bin:x:2:/bin/sh:/bin:2:bin:
        sync:x:4:/bin/sync:/bin:65534:sync:
        man:x:6:/bin/sh:/var/cache/man:12:man:
        mail:x:8:/bin/sh:/var/mail:8:mail:
        uucp:x:10:/bin/sh:/var/spool/uucp:10:uucp:
        backup:x:34:/bin/sh:/var/backups:34:backup:
        Mailing List Manager:x:38:/bin/sh:/var/list:38:list:
        nobody:x:65534:/bin/sh:/nonexistent:65534:nobody:
        :x:100:/bin/sh:/var/lib/libuuid:101:libuuid:
        :x:102:/bin/false:/var/lib/nfs:65534:statd:
        Avahi mDNS daemon,,,:x:104:/bin/false:/var/run/avahi-daemon:107:avahi:
        :x:106:/bin/false:/home/saned:115:saned:
        Gnome Display Manager:x:108:/bin/false:/var/lib/gdm3:116:Debian-gdm:
        Vanvincq Clément,,,:x:1000:/bin/bash:/home/vanvincq:1000:vanvincq:
        :x:110:/bin/false:/usr/share/common-lisp/:118:cl-builder:
        :x:112:/bin/false:/var/run/proftpd:65534:proftpd:
        Accès ftp authentifié,,,,:x:1010:/bin/true:/home/ftp:1010:ftp:
        :x:114:/bin/false:/nonexistent:119:telnetd:
        
      2. ajoutez dix à tous les gid (map),

        
#!/usr/bin/perl -w
        use strict;
        
        #** 
        # @function public parse ($filename)
        # @brief Ouvre un fichier et récupère son contenu (selon la structure /etc/passwd).
        # @params $filename required le nom du fichier à parser.
        # @retval \@array la référence vers le tableau.
        #*    
        sub parse {
        	my ($filename) = @_;
        	my @array = ();
        	my ($login, $passwd, $uid, $gid, $info ,$home, $shell);
        
        	open (FILE, $filename)
        		or die("open: $!");
        
        	while (<FILE>) {
        		($login, $passwd, $uid, $gid, $info ,$home, $shell) = split (/:/, $_);
        		push (@array, {
        			'login'		=>	$login,
        			'passwd'	=>	$passwd,
        			'uid'		=>	$uid,
        			'gid'		=>	$gid,
        			'info'		=>	$info,
        			'home'		=>	$home,
        			'shell'		=>	$shell}
        		);
        	}
        	close (FILE);
        	return \@array;
        }
        
        #** 
        # @function public dump ($reference)
        # @brief Affiche le contenu de la structure
        # @params $reference required la référence vers la structure
        #*  
        sub dump {
        	my ($reference) = @_;
        
        	foreach (@$reference) {
        		foreach my $value (values(%$_)) {
        			# Suppression des caractères de fin de ligne avec chomp
        			# plus simple que $value =~ s/\n//;
        			chomp $value;
        			print "$value:";
        		}
        		print "\n";
        	}
        }
        
        sub peersOnly {
        	my ($reference) = @_;
        
        	# On retourne directement une référence anonyme
        	return [ grep { !($_->{'uid'} % 2) } @$reference ];
        }
        
        sub addTenToGID {
        	my ($reference) = @_;
        
            foreach (@$reference) {
                $_->{'gid'} += 10;
            }
        }
        
        my $ref = parse('/etc/passwd');
        # Récupération de la nouvelle référence
        $ref = peersOnly($ref);
        # Modification directe
        addTenToGID($ref);
        
        # Les '::' évitent l'ambiguité lors de l'appel de la fonction.
        ::dump($ref);
        
        
        
cvanvinc@pinson ~/Bureau $ ./ultime.pl
        root:x:0:/bin/bash:/root:10:root:
        bin:x:2:/bin/sh:/bin:12:bin:
        sync:x:4:/bin/sync:/bin:65544:sync:
        man:x:6:/bin/sh:/var/cache/man:22:man:
        mail:x:8:/bin/sh:/var/mail:18:mail:
        uucp:x:10:/bin/sh:/var/spool/uucp:20:uucp:
        backup:x:34:/bin/sh:/var/backups:44:backup:
        Mailing List Manager:x:38:/bin/sh:/var/list:48:list:
        nobody:x:65534:/bin/sh:/nonexistent:65544:nobody:
        :x:100:/bin/sh:/var/lib/libuuid:111:libuuid:
        :x:102:/bin/false:/var/spool/exim4:114:Debian-exim:
        Avahi mDNS daemon,,,:x:104:/bin/false:/var/run/avahi-daemon:117:avahi:
        Gnome Display Manager:x:106:/bin/false:/var/lib/gdm3:124:Debian-gdm:
        HPLIP system user,,,:x:108:/bin/false:/var/run/hplip:17:hplip:
        info,,,:x:1000:/bin/bash:/home/info:1010:info:
        :x:110:/bin/false:/usr/share/common-lisp/:129:cl-builder:
        Gnome Display Manager:x:112:/bin/false:/var/lib/gdm:131:gdm:
        
      3. triez le tableau par ordre alphabétique du login (sort).

        
#!/usr/bin/perl -w
        use strict;
        
        #** 
        # @function public parse ($filename)
        # @brief Ouvre un fichier et récupère son contenu (selon la structure /etc/passwd).
        # @params $filename required le nom du fichier à parser.
        # @retval \@array la référence vers le tableau.
        #*    
        sub parse {
        	my ($filename) = @_;
        	my @array = ();
        	my ($login, $passwd, $uid, $gid, $info ,$home, $shell);
        
        	open (FILE, $filename)
        		or die("open: $!");
        
        	while (<FILE>) {
        		($login, $passwd, $uid, $gid, $info ,$home, $shell) = split (/:/, $_);
        		push (@array, {
        			'login'		=>	$login,
        			'passwd'	=>	$passwd,
        			'uid'		=>	$uid,
        			'gid'		=>	$gid,
        			'info'		=>	$info,
        			'home'		=>	$home,
        			'shell'		=>	$shell}
        		);
        	}
        	close (FILE);
        	return \@array;
        }
        
        #** 
        # @function public dump ($reference)
        # @brief Affiche le contenu de la structure
        # @params $reference required la référence vers la structure
        #*  
        sub dump {
        	my ($reference) = @_;
        
        	foreach (@$reference) {
        		foreach my $value (values(%$_)) {
        			# Suppression des caractères de fin de ligne avec chomp
        			# plus simple que $value =~ s/\n//;
        			chomp $value;
        			print "$value:";
        		}
        		print "\n";
        	}
        }
        
        sub peersOnly {
        	my ($reference) = @_;
        
        	# On retourne directement une référence anonyme
        	return [ grep { !($_->{'uid'} % 2) } @$reference ];
        }
        
        sub addTenToGID {
        	my ($reference) = @_;
        
            foreach (@$reference) {
                $_->{'gid'} += 10;
            }
        }
        
        sub sortByLogin {
            my ($reference) = @_;
        
            # Tri lexical explicite
            return [ sort { $a->{'login'} cmp $b->{'login'} } @$reference ];
        }
        
        my $ref = parse('/etc/passwd');
        # Récupération de la nouvelle référence
        $ref = peersOnly($ref);
        # Modification directe
        addTenToGID($ref);
        # Récupération de la nouvelle référence
        $ref = sortByLogin($ref);
        
        # Les '::' évitent l'ambiguité lors de l'appel de la fonction.
        ::dump($ref);                            
        
        
cvanvinc@pinson ~/Bureau $ ./ultime.pl
        :x:102:/bin/false:/var/spool/exim4:114:Debian-exim:
        Gnome Display Manager:x:106:/bin/false:/var/lib/gdm3:124:Debian-gdm:
        Avahi mDNS daemon,,,:x:104:/bin/false:/var/run/avahi-daemon:117:avahi:
        backup:x:34:/bin/sh:/var/backups:44:backup:
        bin:x:2:/bin/sh:/bin:12:bin:
        :x:110:/bin/false:/usr/share/common-lisp/:129:cl-builder:
        Gnome Display Manager:x:112:/bin/false:/var/lib/gdm:131:gdm:
        HPLIP system user,,,:x:108:/bin/false:/var/run/hplip:17:hplip:
        info,,,:x:1000:/bin/bash:/home/info:1010:info:
        :x:100:/bin/sh:/var/lib/libuuid:111:libuuid:
        Mailing List Manager:x:38:/bin/sh:/var/list:48:list:
        mail:x:8:/bin/sh:/var/mail:18:mail:
        man:x:6:/bin/sh:/var/cache/man:22:man:
        nobody:x:65534:/bin/sh:/nonexistent:65544:nobody:
        root:x:0:/bin/bash:/root:10:root:
        sync:x:4:/bin/sync:/bin:65544:sync:
        uucp:x:10:/bin/sh:/var/spool/uucp:20:uucp:
        

      Utilisez votre fonction dump pour vérifier.

    Petit détail : lors de l'appel de la fonction dump, l'ordre des clefs de la table de hachage n'est pas respectée.