Le but du TP est de récupérer des informations contenues dans un fichier dhcpd.conf
. Ce fichier ressemble à ça :
# # DHCP Server Configuration file. # see /usr/share/doc/dhcp*/dhcpd.conf.sample # ddns-update-style ad-hoc; not authoritative; min-lease-time 43200; max-lease-time 43200; subnet 172.19.45.0 netmask 255.255.255.0 { deny unknown-clients ; range 172.19.45.245 172.19.45.254; option subnet-mask 255.255.0.0; option domain-name-servers 172.19.45.18,172.19.45.20; option routers 172.19.0.254; } host coquil-gw1 { hardware ethernet 00:1d:09:0a:33:db; fixed-address coquil-gw1; } host fpl { hardware ethernet 00:26:b9:5d:32:32; fixed-address fpl; } host roundcube { hardware ethernet 00:16:36:09:2d:b9; fixed-address roundcube; } host deux { hardware ethernet 00:14:38:63:6E:F3; fixed-address deux; } host visiteur1 { hardware ethernet 00:00:aa:be:93:5d; } host visiteur2 { hardware ethernet 00:60:B0:D5:3B:05; }
Voici·un·véritable·fichier·dhcpd.conf
Nous allons écrire un script qui parcours un fichier dhcpd.conf
, et construit une structure de données contenant pour chaque host une liste d'attributs choisis, associés à leur valeur.
Ci-dessous un exemple de la structure de données que l'on pourrait construire :
'fpl' => { 'ip' => 'fpl', 'mac' => '00:26:b9:5d:32:32' }, 'roundcube' => { 'ip' => 'roundcube', 'mac' => '00:16:36:09:2d:b9' }, 'visiteur2' => { 'mac' => '00:60:B0:D5:3B:05' }
Comme le formatage du fichier dhcpd.conf
est très libre par rapport aux sauts de lignes, on va reformater le fichier à notre façon.
Pour cela, on va fusionner toutes les lignes en une grande string, que l'on pourra ensuite parser.
Il s'agit donc ici :
host {…}
de cette string,1: #!/bin/bash 2: 3: # Les structures de données sont très limités en bash. Ici 4: # nous utiliserons plutot un fichier intermédiaire qu'une liste 5: # comme cela a été en perl, ruby et python. 6: 7: # Nom du fichier tampon 8: FILETMP=/tmp/dhcpd-tmp.$$ 9: 10: # Reformattage du fichier dhcpd.conf passé sur stdin : 11: # - grep -v pour enlever les lignes vides 12: # - sed pour retirer les commentaires 13: # - tr -d pour enlever les sauts de lignes (doit être fait en dernier) 14: grep -v '^$' < /dev/stdin | sed -e 's/#.*//g' | tr -d '\n' > $FILETMP 15: 16: cat $FILETMP 17: rm $FILETMP
1: use FileHandle ; 2: use Data::Dumper ; 3: 4: use strict ; 5: 6: # mettre le contenu du fichier dans une string 7: my $lines ; 8: while (<>) 9: { 10: chomp ; # supprime le \n final 11: s/#.*// ; # supprime les commentaires 12: next unless /\S/ ; # si la ligne est vide, au suivant ! 13: 14: $lines .= $_ ; # $_ est la ligne courant 15: } 16: 17: # découper la string pour en extraire les portions host {} 18: my @lines = $lines=~/(host\s.*?})/g ; 19: 20: print Dumper \@lines ;
1: #!/usr/bin/python 2: import fileinput 3: import re 4: 5: # on concatène toutes les lignes du fichier en supprimant les commentaires et les fins de ligne 6: lines = '' 7: for line in fileinput.input(): 8: lines = lines + re.sub('#.*','',line.rstrip()) 9: 10: # on extrait toutes les occurences de "host ...}" 11: hosts = re.findall('host\s[^}]*}',lines) 12: 13: # et on affiche la liste obtenue 14: print hosts
require 'pp' lines = File.readlines('dhcpd.conf') # lis le fichier ligne par ligne stream = lines.collect! do |line| # remplace chaque ligne line.chomp! # supprime le \n final (line.match(/#/).nil?) ? line : nil; # remplace les commentaire par nil end.compact.join('') # supprime les nil et refait une string hosts = stream.scan(/host\s*[^}]+\}/) # identifie les blocs "host" pp hosts # affiche la liste
Parser la liste précédente pour en extraire le nom du host et la valeurs des attributs hardware ethernet
et fixed-address
.
Pour chaque host, créer un petit hash dont les clés sont ”hardware
ethernet
” et ”fixed-address
”, et dont les valeurs sont les valeurs de ces attributs.
1: use FileHandle ; 2: use Data::Dumper ; 3: 4: use strict ; 5: 6: my $lines ; 7: while (<>) 8: { 9: chomp ; 10: s/#.*// ; 11: next unless /\S/ ; 12: 13: $lines .= $_ ; 14: } 15: 16: my @lines = $lines=~/(host\s.*?})/g ; 17: 18: for my $l (@lines) 19: { 20: my $item ; # pointeur de hash pour les attributs du host courant 21: 22: my ($host,$rest) = $l=~/host\s*(\S+)\s*\{\s*(.*?)\s*\}/ ; 23: 24: if ($rest=~/hardware\s+ethernet\s+\S+\s*;/) 25: { 26: ($item->{mac}) = $rest=~/hardware\s+ethernet\s+(\S+)\s*;/ ; # on extrait 27: $rest =~ s/hardware\s+ethernet\s+\S+\s*;// ; # on efface 28: } 29: 30: if ($rest=~/fixed-address\s+\S+\s*;/) 31: { 32: ($item->{ip}) = $rest=~/fixed-address\s+(\S+)\s*;/ ; # on extrait 33: $rest =~ s/fixed-address\s+\S+\s*;// ; # on efface 34: } 35: 36: print Dumper $item ; 37: }
1: #!/usr/bin/python 2: import fileinput 3: import re 4: 5: lines = '' 6: for line in fileinput.input(): 7: lines = lines + re.sub('#.*','',line.rstrip()) 8: 9: hosts = re.findall('host\s[^}]*}',lines) 10: 11: # on prépare une liste de regexps 12: regexps=[ 13: re.compile('(fixed-address)\s+(\S+)'), 14: re.compile('(hardware\sethernet)\s+(\S+)') 15: ] 16: 17: # on boucle sur les hosts 18: for host in hosts: 19: # un dictionnaire vide **par host** qu'on va remplir 20: attributes={} 21: m=re.match('host\s+(\S+)\s*{\s*(.*?)\s*}',host) 22: if m: 23: # on récupère d'une part le hostname, d'autre part ses attributs 24: hostname,context=m.group(1),m.group(2) 25: # les attributs sont séparés par des ; 26: # on boucle sur les champs 27: for field in re.split(';\s*',context): 28: # pour chaque regexp, on teste si le champ matche 29: for pattern in regexps: 30: n=pattern.search(field) 31: if n: 32: # si c'est le cas, n.group(1) contient l'étiquette (par ex: fixed-address) 33: # n.group(2) contient la valeur (par ex: 00:16:36:09:2d:b9) 34: attributes[n.group(1)]=n.group(2) 35: # on affiche l'attribut 36: # on est revenu au niveau de la première boucle for 37: print attributes
regexp = /host\s*(\S+)\s*\{\s*(.*?)\s*\}/ pp hosts.collect do |host| # remplace le tableau de string attributes = {} # par un tableau de hash host.match(regexp) # extrait les attributs hostname,context=$1,$2 # fields = context.split(';') # sépare les attributs sur ';' fields.each do |field| # [ '(hardware\s+ethernet)\s+(\S+)', # identifie les attributs '(fixed-address)\s+(\S+)' # ].each do |pattern| attributes[$1] = $2 unless field.chomp.match(pattern).nil? end end attributes end
Pour mémoriser l'ensemble des infos nous intéressant, on va créer un grand hash dont les clés sont les noms de host, et dont les les valeurs sont les petits hashs précédents. En fin de programme, faire un dump de ce grand hash.
1: use FileHandle ; 2: use Data::Dumper ; 3: 4: use strict ; 5: 6: my $lines ; 7: while (<>) 8: { 9: chomp ; 10: s/#.*// ; 11: next unless /\S/ ; 12: 13: $lines .= $_ ; 14: } 15: 16: my @lines = $lines=~/(host\s.*?})/g ; 17: my $info ; # pointera vers le hash pour stocker les attributs de tous les hosts 18: for my $l (@lines) 19: { 20: my $item ; 21: 22: my ($host,$rest) = $l=~/host\s*(\S+)\s*\{\s*(.*?)\s*\}/ ; 23: 24: if ($rest=~/hardware\s+ethernet\s+\S+\s*;/) 25: { 26: ($item->{mac}) = $rest=~/hardware\s+ethernet\s+(\S+)\s*;/ ; 27: $rest =~ s/hardware\s+ethernet\s+\S+\s*;// ; 28: } 29: 30: if ($rest=~/fixed-address\s+\S+\s*;/) 31: { 32: ($item->{ip}) = $rest=~/fixed-address\s+(\S+)\s*;/ ; 33: $rest =~ s/fixed-address\s+\S+\s*;// ; 34: } 35: 36: $info->{$host} = $item ; # nouvel host 37: } 38: 39: print Dumper $info ;
1: #!/usr/bin/python 2: import fileinput 3: import re 4: import pprint 5: 6: lines = '' 7: for line in fileinput.input(): 8: lines = lines + re.sub('#.*','',line.rstrip()) 9: 10: hosts = re.findall('host\s[^}]*}',lines) 11: # un dictionnaire vide qui contiendra les valeurs pour chaque host 12: hosts_hash = {} 13: regexps=[ 14: re.compile('(fixed-address)\s+(\S+)'), 15: re.compile('(hardware\sethernet)\s+(\S+)') 16: ] 17: for host in hosts: 18: attributes={} 19: m=re.match('host\s+(\S+)\s*{\s*(.*?)\s*}',host) 20: if m: 21: hostname,context=m.group(1),m.group(2) 22: for field in re.split(';\s*',context): 23: for pattern in regexps: 24: n=pattern.search(field) 25: if n: 26: attributes[n.group(1)]=n.group(2) 27: # on stocke les dictionnaire attributes dans le dictionnaire hosts_hash sous la clé hostname 28: hosts_hash[hostname] = attributes 29: pprint.pprint(hosts_hash)
hosts_array = hosts.collect do |host| attributes = {} host.match(regexp) hostname,context=$1,$2 fields = context.split(';') fields.each do |field| [ '(hardware\s+ethernet)\s+(\S+)', '(fixed-address)\s+(\S+)' ].each do |pattern| attributes[$1] = $2 unless field.chomp.match(pattern).nil? end end [ hostname, attributes ] end pp Hash[*hosts_array.flatten] # la notation *hosts_array eclate le tableau d'objet # en autant de paramètres à passer à la méthode de classe Hash[]. pp Hash[hosts_array] # mais cela marche aussi
Pour rendre le programme plus extensible, on va définir dans une structure de données, les attributs à récupérer, et pour chacun d'eux, l'expression régulière permettant de l'extraire. Par la suite, il suffira donc d'ajouter un nouvel élément dans ce hash pour prendre en compte de nouveaux attributs.
Créer un hash dont les clés sont les noms des attributs à récupérer, et dont les valeurs sont les expressions régulières permettant de les récupérer. Transformer le programme afin d'extraire les attributs spécifiés dans la structure précédente.
Sortir le grand hash à l'écran, lisiblement, sans dump.
1: use FileHandle ; 2: use Data::Dumper ; 3: 4: use strict ; 5: 6: # ce qu'il faut extraire, et comment le faire 7: my %attributes = 8: ( 9: mac => qr/hardware\s+ethernet\s+(\S+?)\s*;/ 10: , ip => qr/fixed-address\s+(\S+?)\s*;/ 11: ) ; 12: 13: my $lines ; 14: while (<>) 15: { 16: chomp ; 17: s/#.*// ; 18: next unless /\S/ ; 19: 20: $lines .= $_ ; 21: } 22: 23: my @lines = $lines=~/(host\s.*?})/g ; 24: my $info ; 25: for my $l (@lines) 26: { 27: my $item ; 28: 29: my ($host,$rest) = $l=~/host\s*(\S+)\s*\{\s*(.*?)\s*\}/ ; 30: 31: # recherche des attributs 32: my ($key,$re) ; 33: while (($key,$re)=each %attributes) 34: { 35: if ($rest=~$re) 36: { 37: ($item->{$key}) = $rest=~/$re/ ; 38: $rest =~ s/$re// ; 39: } 40: } 41: 42: $info->{$host} = $item ; # nouvel host 43: } 44: 45: # parcours du hash 46: while (my($host,$attr)=each %$info) 47: { 48: print "\n$host:\n" ; 49: for my $attr_name (keys %$attr) 50: { 51: print "\t$attr_name: ",$attr->{$attr_name},"\n" ; 52: } 53: }
1: #!/usr/bin/python 2: import fileinput 3: import re 4: 5: # un dictionnaire qui contient les regexp que nous allons utiliser 6: attributes_match={ 7: 'mac':'(hardware\sethernet)\s+(\S+)', 8: 'ip':'(fixed-address)\s+(\S+)' 9: } 10: 11: lines = '' 12: for line in fileinput.input(): 13: lines = lines + re.sub('#.*','',line.rstrip()) 14: 15: hosts = re.findall('host\s[^}]*}',lines) 16: hosts_hash = {} 17: 18: for host in hosts: 19: attributes={} 20: m=re.match('host\s+(\S+)\s*{\s*(.*?)\s*}',host) 21: if m: 22: hostname,context=m.group(1),m.group(2) 23: for field in re.split(';\s*',context): 24: # iteritems permet de boucler sur clé,valeur 25: for key,pattern in attributes_match.iteritems(): 26: n=re.search(pattern,field) 27: if n: 28: # ici key peut valoir 'mac' et n.group(2) peut valoir '00:16:36:09:2d:b9' 29: attributes[key] = n.group(2) 30: hosts_hash[hostname] = attributes 31: 32: # iteritems permet de boucler sur clé,valeur 33: for hostname,attributes in hosts_hash.iteritems(): 34: print "%s:"%hostname 35: for name,value in attributes.iteritems(): 36: print "\t%s: %s"%(name,value)
attributes_match = { 'mac' => '(hardware\s+ethernet)\s+(\S+)', 'ip' => '(fixed-address)\s+(\S+)' } hosts_array = hosts.collect do |host| attributes = {} host.match(regexp) hostname,context=$1,$2 fields = context.split(';') fields.each do |field| attributes_match.each do |key,pattern| attributes[key] = $2 unless field.chomp.match(pattern).nil? end end [ hostname, attributes ] end hosts_hash = Hash[*hosts_array.flatten] hosts_hash.each do |hostname,attributes| puts "#{hostname}:" attributes.each do |name,value| puts "\t#{name}: #{value}" end end
$Id: dhcpd.conf.txt 683 2012-05-28 19:57:45Z jaclin $