Le but du TP est d'y voir un peu plus clair dans les logs d'un serveur de mails, en regroupant pour chaque mail les informations le concernant.
Les logs ressemblent à ça :
Jul 25 04:02:09 tonton postfix/local[5432]: 52BD11744C: to=<mailmaster@tonton.univ-angers.fr>, orig_to=<root>, relay=local, delay=6.8, delays=3.5/0/0/3.3, dsn=2.0.0, status=sent (delivered to command: /usr/bin/procmail) Jul 25 04:02:09 tonton postfix/qmgr[2125]: 52BD11744C: removed Jul 25 04:03:05 tonton postfix/smtpd[5426]: disconnect from netsrv.math[172.19.45.20] Jul 25 04:03:05 tonton postfix/qmgr[2125]: 479521744B: from=<root@netsrv.math>, size=4418700, nrcpt=1 (queue active) Jul 25 04:03:05 tonton spamc[6085]: skipped message, greater than max message size (512000 bytes) Jul 25 04:03:05 tonton postfix/local[5427]: 479521744B: to=<mailmaster@smtp.math>, relay=local, delay=60, delays=59/0/0/0.36, dsn=2.0.0, status=sent (delivered to command: /usr/bin/procmail)
Dans ce type de log, chaque opération appliquée à un mail est consignée sur une ligne propre, comportant en particulier le message-ID du mail, sous forme d'une chaîne de 10 caractères hexadécimaux (ici 52BD11744C
et 479521744B
).
Voici un véritable fichier maillog
Nous allons écrire un script qui parcours un tel fichier de log, et construit une structure de données contenant pour chaque mail (identifié par son Message-ID) la liste des lignes (débarrassées éventuellement des informations superflues) le concernant.
Pour un mail donné, on fera attention à conserver l'ordre chronologique des lignes le concernant. Ci-dessous un exemple de la structure de données que l'on pourrait construire :
'E4A9F1744B' => [ ' client=math.univ-angers.fr[193.49.146.25]', ' message-id=<4C51974F.9090401@certa.ssi.gouv.fr>', ' from=<corresp_ssi-owner@services.cnrs.fr>, size=13207, nrcpt=1', ' to=<jaclin@math.univ-angers.fr>, status=sent', ' removed' ], '916561744B' => [ ' client=math.univ-angers.fr[193.49.146.25]', ' message-id=<OP-M80-S55-1280040877@rem02.com>', ' from=<bounce@rem02.com>, size=6585, nrcpt=1', ' to=<jea@tonton.univ-angers.fr>, status=sent', ' removed' ]
Ecrire un filtre qui stocke dans une liste chaque ligne lues sur stdin
. Le programme envoie ensuite le contenu de cette liste sur stdout
. On ne s'intéressera qu'aux lignes contenant un message-ID, les autres seront simplement écartées.
push
, module Data::Dumper
1: #!/bin/bash 2: 3: # Pas de liste en bash => recours au tableau + index 4: typeset -a LST 5: typeset -i LSTSIZE=0 6: 7: # Fichier intermédiaire pour limiter le nombre d'appel 8: # à grep au sein d'une boucle 9: # $$ est le PID du script => unicité du fichier tampon 10: BUFFILE=/tmp/maillog-tmp.$$ 11: 12: # Ne garder que les lignes avec un message_ID 13: # grep -E => expressions régulières étendues 14: grep -E ' [0-9A-F]{10}:' < /dev/stdin > $BUFFILE 15: 16: # Remplissage du tableau LST 17: while read LINE 18: do 19: LST[$((LSTSIZE++))]="$LINE" 20: done < $BUFFILE 21: 22: # Affichage des lignes 23: for (( i=0 ; i<$LSTSIZE ; i++)) 24: do 25: echo "${LST[$i]}" 26: done 27: 28: rm $BUFFILE
1: use strict ; # rend obligatoire les déclarations 2: 3: my @list ; # une liste 4: while(<>) 5: { 6: next unless /\s[0-9A-F]{10}:/ ; # s'il n'y a pas de message_ID, au suivant ! 7: push(@list,$_) ; 8: } 9: 10: for (@list) { print ; } # équivalent à : for my $line (@list) { print $line ; }
1: import fileinput 2: import re 3: 4: list = [] 5: p = re.compile('\s[0-9A-F]{10}:') 6: for line in fileinput.input(): 7: if p.search(line): 8: list.append(line) 9: for item in list: 10: print item,
1: list = [] # un tableau ie une liste 2: $stdin.each_line do |line| # pour toutes les lignes de $stdin 3: list.push line unless line.match(/\s[0-9A-F]{10}:/).nil? # on garde la ligne sauf si il n'y a pas de message_ID 4: end 5: list.each do |item| puts item end
Avant de stocker une ligne dans la liste, supprimer les informations inintéressantes, comme par exemple le nom du host ou le PID du process.
1: #!/bin/bash 2: 3: # Pas de liste en bash => recours au tableau + index 4: typeset -a LST 5: typeset -i LSTSIZE=0 6: 7: # Fichier intermédiaire pour limiter le nombre d'appel 8: # à grep au sein d'une boucle 9: # $$ est le PID du script => unicité du fichier tampon 10: BUFFILE=/tmp/maillog-tmp.$$ 11: 12: # Ne garder que les lignes avec un message_ID 13: # grep -E => expressions régulières étendues 14: # sed est utilisé pour retirer le PID et le nom du host 15: grep -E ' [0-9A-F]{10}:' < /dev/stdin | sed 's/^\(\w\+ [0-9]\+ [0-9]\+:[0-9]\+:[0-9]\+\) \w\+ \(.*\)\[[0-9]*\]/\1 \2/g' > $BUFFILE 16: 17: # Remplissage du tableau LST 18: while read LINE 19: do 20: LST[$((LSTSIZE++))]="$LINE" 21: done < $BUFFILE 22: 23: # Affichage des lignes 24: for (( i=0 ; i<$LSTSIZE ; i++)) 25: do 26: echo "${LST[$i]}" 27: done 28: 29: rm $BUFFILE
1: # On utilise ici le module Data::Dumper pour le fun 2: 3: use Data::Dumper ; # pour dump de variables 4: use strict ; 5: 6: my @list ; 7: while(<>) 8: { 9: next unless /\s[0-9A-F]{10}:/ ; 10: s/^(\w+ \d+ \d+:\d+:\d+) \w+ (.*?)\[\d+\]/$1 $2/ ; # suppression du host et du PID 11: push(@list,$_) ; 12: } 13: print Dumper \@list ; # issu de data::Dumper
Explication de la ligne #10 : ce qui s'identifie avec le contenu des ()
situées dans la première partie du substitute se récupére dans les variables $1
, $2
, $3
, etc.
1: import fileinput 2: import re 3: 4: list = [] 5: p = re.compile('\s[0-9A-F]{10}:') 6: for line in fileinput.input(): 7: if p.search(line): 8: list.append(re.sub('^(\w+ \d+ \d+:\d+:\d+) \w+ (.*?)\[\d+\]',r'\1 \2',line)) 9: for item in list: 10: print item,
1: require 'pp' # PrettyPrinting 2: 3: list = [] 4: $stdin.each_line do |line| 5: begin # suppression du host et du PID 6: list.push "#{$1} #{$2}" unless line.match(/^(\w+ \d+ +\d+:\d+:\d+) \w+ (.*?)\[\d+\]/).nil? 7: end unless line.match(/\s[0-9A-F]{10}:/).nil? 8: end 9: pp list
Utiliser maintenant un hash pour conserver les lignes intéressantes. La clé du hash sera le message-ID. Pour une clé donnée, la valeur du hash sera la concaténation des lignes relatives à ce message-ID.
Enlever le message-ID des lignes stockées. Faire un dump du hash en fin de programme.
.
1: #!/bin/bash 2: 3: # # Les tableaux associatifs ne peuvent pas être utilisés pour simuler 4: # # une table de hachage. En effet on arrive vite à un probleme de 5: # # capacité (au niveau de l'index) avec l'exemple ci-dessous : 6: 7: # typeset -a HSH 8: # HSH['B31E017463']="Valeur n°1" 9: # HSH['B31E017463']="Valeur n°2" 10: # HSH['B73DF1745B']="Valeur n°3" 11: # HSH['B73DF1745B']="Valeur n°4" 12: # HSH['4FE0D17466']="Valeur n°5" 13: 14: # # L'erreur est sans appel : 15: # #./mail3.sh: line 12: 4FE0D17466: value too great for base (error token is "4FE0D17466") 16: # # 17: 18: # Fichier intermédiaire pour limiter le nombre d'appel 19: # à grep au sein d'une boucle 20: # $$ est le PID du script => unicité du fichier tampon 21: BUFFILE=/tmp/maillog-tmp.$$ 22: 23: # Construction du fichier intermédiaire : 24: # - grep sélectionne les lignes avec un message_id 25: # - sed replace le message_id en debut de ligne, retire le PID et 26: # le nom du host 27: # - comme toutes les lignes commencent par un message_id, sort 28: # permet de les rassembler 29: grep -E ' [0-9A-F]{10}:' /dev/stdin | sed 's/^\(\w\+ \+[0-9]\+ [0-9]\+:[0-9]\+:[0-9]\+\) \w\+ \(.*\)\[[0-9]*\]: \([0-9A-F]\{10\}\):\(.*\)/\3\1 \2\4/g' | sort > $BUFFILE 30: 31: # Affichage de toutes les lignes relatives à un message_id 32: MSID="" 33: while read LINE 34: do 35: K=${LINE:0:10} # 10 premiers caractères => message ID 36: if [ ! "$K" = "$MSID" ]; then 37: # Première fois que l'on rencontre ce message_id => nouveau bloc 38: MSID=$K 39: echo "Message with ID $MSID:" 40: fi 41: echo " ${LINE:10}" # Le reste de la ligne avec le message de log 42: done < $BUFFILE 43: 44: rm $BUFFILE
1: use Data::Dumper ; 2: use strict ; 3: 4: my %h ; # un hash 5: while(<>) 6: { 7: next unless /\s[0-9A-F]{10}:/ ; 8: s/^(\w+ \d+ +\d+:\d+:\d+) \w+ (.*?)\[\d+\]/$1 $2/ ; 9: my($begin,$id,$end) = /^(.*) ([0-9A-F]{10})(: .*)/ ; # on coupe en 3 10: $h{$id} .= "\n".$begin.$end ; 11: } 12: print Dumper \%h ;
Explication de la ligne #9 : ce qui s'identifie avec le contenu des () situées dans la RE se récupére dans la liste à gauche de l'affectation.
1: import fileinput 2: import re 3: 4: h = {} 5: p = re.compile('\s[0-9A-F]{10}:') 6: reg = re.compile('^(?P<before>.*): (?P<message_id>[0-9A-F]{10}):(?P<after> .*)') 7: for line in fileinput.input(): 8: if p.search(line): 9: line_inter=re.sub('^(\w+ \d+ +\d+:\d+:\d+) \w+ (.*?)\[\d+\]',r'\1 \2',line) 10: m=reg.match(line_inter) 11: if m: 12: if m.group('message_id') in h.keys(): 13: h[m.group('message_id')]=h[m.group('message_id')]+m.group('before')+m.group('after')+'\n' 14: else: 15: h[m.group('message_id')]=m.group('before')+m.group('after')+'\n' 16: 17: for key in h.keys(): 18: print key+" => " 19: print h[key]
1: %w(rubygems oniguruma pp).each do |lib| require lib end 2: # oniguruma est la lib RE par défaut sous ruby 1.9 3: # supporte différentes syntaxes de regexp, permet le "negative matching" et le "named group matching" 4: 5: list = {} 6: regexp = Oniguruma::ORegexp.new('^(?<before>.*): (?<message_id>[0-9A-F]{10}): (?<interresting_part>.*)') 7: $stdin.each_line do |line| 8: m = regexp.match(line) 9: list[m[:message_id]] = "#{list[m[:message_id]]}\n#{m[:before]}#{m[:interresting_part]}" unless m.nil? 10: end 11: pp list
Conserver le hash, mais cette fois, pour une clé donnée, la valeur du hash sera la liste des lignes relatives à ce message-ID.
Faire un dump du hash en fin de programme.
1: use Data::Dumper ; 2: use strict ; 3: 4: my %h ; 5: while(<>) 6: { 7: next unless /\s[0-9A-F]{10}:/ ; 8: s/^(\w+ \d+ +\d+:\d+:\d+) \w+ (.*?)\[\d+\]/$1 $2/ ; 9: my($begin,$id,$end) = /^(.*) ([0-9A-F]{10})(: .*)/ ; 10: push(@{$h{$id}},$begin.$end) ; 11: } 12: print Dumper \%h ;
Explication de la ligne #10 :
$h{$id}
: on s'intéresse à l'élément de hash de clé $id
@{$h{$id}}
: on dit que la valeur de cet élément est une listepush(@{$h{$id}},$begin.$end)
: on pousse $begin.$end
en tete de liste1: import fileinput 2: import re 3: import pprint 4: 5: h = {} 6: p = re.compile('\s[0-9A-F]{10}:') 7: reg = re.compile('^(?P<before>.*): (?P<message_id>[0-9A-F]{10}):(?P<after> .*)') 8: for line in fileinput.input(): 9: if p.search(line): 10: line_inter=re.sub('^(\w+ \d+ +\d+:\d+:\d+) \w+ (.*?)\[\d+\]',r'\1 \2',line) 11: m=reg.match(line_inter) 12: if m: 13: if m.group('message_id') in h.keys(): 14: h[m.group('message_id')].append(m.group('before')+m.group('after')) 15: else: 16: h[m.group('message_id')]=[m.group('before')+m.group('after')] 17: pprint.pprint(h)
1: %w(rubygems oniguruma pp).each do |lib| require lib end 2: # oniguruma est la lib RE par défaut sous ruby 1.9 3: # supporte différentes syntaxes de regexp, permet le "negative matching" et le "named group matching" 4: 5: list = {} 6: regexp = Oniguruma::ORegexp.new('^(?<before>.*): (?<message_id>[0-9A-F]{10}): (?<interresting_part>.*)') 7: $stdin.each_line do |line| 8: m = regexp.match(line) 9: begin 10: array = list[m[:message_id]] || [] # on traite le cas ou aucune entrée n'existe 11: list[m[:message_id]] = array << m[:before]+" "+m[:interresting_part] # l'opérateur '+' marche sur les strings 12: end unless m.nil? # ce sont des objets comme les autres 13: end 14: pp list
Transformer le programme précédent pour qu'il récupère sur la ligne de commande le nom du fichier de sortie (option -o
).
Faire une sortie propre du hash (plus propre qu'un dump).
Exemple d'utilisation :
$ ./myprog -o summary.log maillog.1 maillog.2
Ecrire un message d'erreur si l'option -o est omise.
Getopt::Long
1: #!/bin/bash 2: 3: # Fonction pour afficher l'utilisation du script 4: function usage() { 5: cat <<EOF 6: $0 -o <output_file> <maillog_file> [ <maillog_file...] 7: EOF 8: } 9: 10: # Vérification de la ligne de commande 11: OUTFILE= 12: FILES= 13: while getopts "o:" OPT 14: do 15: case $OPT in 16: o) 17: OUTFILE=$OPTARG 18: ;; 19: ?) 20: echo "Option $OPT inconnue" > /dev/stderr 21: usage 22: exit 1 23: esac 24: done 25: shift $((OPTIND - 1)) # Elimine de $* les arguments traités par getopt 26: if [ -z "$OUTFILE" ]; then 27: echo "L'option -o est obligatoire" > /dev/stderr 28: usage 29: exit 1 30: fi 31: if [ -z "$*" ]; then 32: echo "Vous devez spécifiez au moins un fichier à analyser" 33: usage 34: exit 1 35: fi 36: 37: # Fichier intermédiaire pour limiter le nombre d'appel 38: # à grep au sein d'une boucle 39: # $$ est le PID du script => unicité du fichier tampon 40: BUFFILE=/tmp/maillog-tmp.$$ 41: 42: # Construction du fichier intermédiaire : 43: # - grep sélectionne les lignes avec un message_id dans les N fichiers d'entrée 44: # - sed replace le message_id en debut de ligne, retire le PID et 45: # le nom du host 46: # - comme toutes les lignes commencent par un message_id, sort 47: # permet de les rassembler 48: grep -h -E ' [0-9A-F]{10}:' $* | sed 's/^\(\w\+ \+[0-9]\+ [0-9]\+:[0-9]\+:[0-9]\+\) \w\+ \(.*\)\[[0-9]*\]: \([0-9A-F]\{10\}\):\(.*\)/\3\1 \2\4/g' | sort > $BUFFILE 49: 50: # Affichage de toutes les lignes relatives à un message_id 51: MSID="" 52: while read LINE 53: do 54: K=${LINE:0:10} # 10 premiers caractères => message ID 55: if [ ! "$K" = "$MSID" ]; then 56: # Première fois que l'on rencontre ce message_id => nouveau bloc 57: [ -z "$MSID" ] || echo "" 58: MSID=$K 59: echo "$MSID:" 60: fi 61: echo -e "\t${LINE:10}" # Le reste de la ligne avec le message de log 62: done < $BUFFILE > $OUTFILE 63: 64: rm $BUFFILE
1: use FileHandle ; 2: use Getopt::Long ; # pour récupérer les arguments de ligne de commande 3: use strict ; 4: 5: #------------- 6: sub usage # ceci est une définition de fonction 7: { 8: my ($msg) = @_ ; 9: 10: print << "EOD" ; 11: $msg 12: usage: $0 -o output_file files 13: 14: EOD 15: exit(1) ; 16: } 17: 18: #------------- 19: my ($verbose,$fout) ; 20: 21: GetOptions( 22: "o=s" => \$fout 23: ) or usage("syntax error") ; 24: usage("-o arg is missing") unless $fout=~/\S/ ; 25: 26: my $h ; # un pointeur de hash, pour changer 27: 28: # parsing et stockage en ram 29: while(<>) 30: { 31: next unless /\s[0-9A-F]{10}:/ ; 32: s/^(\w+ \d+ +\d+:\d+:\d+) \w+ (.*?)\[\d+\]/$1 $2/ ; 33: my($begin,$id,$end) = /^(.*) ([0-9A-F]{10})(: .*)/ ; 34: push(@{$h->{$id}},$begin.$end) ; # noter la flèche ! 35: } 36: 37: # écriture dans fichier 38: my $fh = new FileHandle(">$fout") or die $! ; 39: for my $key (keys %$h) # pour toutes les clés 40: { 41: $fh->print("\n$key:\n") ; 42: for my $i (@{$h->{$key}}) # pour toute la liste 43: { 44: $fh->print("\t$i\n") ; 45: } 46: } 47: $fh->close() ;
Explication
«EOD
print “$msg\nusage: $0 -o output_file files\n” ;
EOD
) peut être n'importe quelle chaîneGetopt::Long
permet d'extraire les options de la ligne de commande, et d'affecter des variables ou déclencher des fonctions automatiquement en fonction des options trouvées. Voir man Getopt::Long
ou sur www.cpan.org.FileHandle
permet de gérer les fichiers de données de façon objet. Voir man FileHandle
ou sur www.cpan.org.1: #!/usr/bin/python 2: import fileinput 3: import re 4: import getopt 5: import sys 6: 7: filename=None 8: try: 9: opt,args = getopt.getopt(sys.argv[1:], "o:", ["output="]) 10: except getopt.GetoptError,err: 11: print str(err) 12: sys.exit(2) 13: 14: for option in opt: 15: if option[0]=='-o' or option[0]=="--output": 16: filename=option[1] 17: 18: sys.argv[1:]=args 19: if filename==None: 20: print "l'option -o est obligatoire" 21: sys.exit(2) 22: 23: h = {} 24: p = re.compile('\s[0-9A-F]{10}:') 25: reg = re.compile('^(?P<before>.*): (?P<message_id>[0-9A-F]{10}):(?P<after> .*)') 26: for line in fileinput.input(): 27: if p.search(line): 28: line_inter=re.sub('^(\w+ \d+ +\d+:\d+:\d+) \w+ (.*?)\[\d+\]',r'\1 \2',line) 29: m=reg.match(line_inter) 30: if m: 31: if m.group('message_id') in h.keys(): 32: h[m.group('message_id')].append(m.group('before')+m.group('after')) 33: else: 34: h[m.group('message_id')]=[m.group('before')+m.group('after')] 35: 36: 37: fh=open(filename,'w') 38: for key in h.keys(): 39: fh.write('\n%s:\n'%key) 40: for elt in h[key]: 41: fh.write('\t%s\n'%elt) 42: fh.close()
1: #!/usr/bin/env ruby 2: 3: # 4: # 5: # == Synopsis 6: # 7: # Débroussailler des log d'un serveur de mails 8: # Version qui gère les argument de la ligne de commande 9: # 10: # == Usage 11: # 12: # ./myprog --output-file output.txt 13: # 14: # 15: 16: %w(rubygems oniguruma pp getoptlong rdoc/usage).each do |lib| require lib end 17: 18: Options = Struct.new( :output_file, :verbose ) 19: @options = Options.new( nil, false ) 20: 21: @regexp = Oniguruma::ORegexp.new('^(?<before>.*): (?<message_id>[0-9A-F]{10}): (?<interresting_part>.*)') 22: 23: # 24: # parse arguments on command line 25: # 26: def parse_command_line 27: if ARGV.length == 0 28: RDoc::usage 29: exit 1 30: end 31: 32: opts = GetoptLong.new( 33: [ '--output-file','-o', GetoptLong::REQUIRED_ARGUMENT ], 34: ) 35: 36: begin 37: opts.each{|opt,arg| 38: case opt 39: when '--output-file' 40: @options.output_file = arg 41: end 42: } 43: rescue Exception 44: exit 2 45: end 46: end 47: def parse_line(line) 48: m = @regexp.match(line) 49: begin 50: array = @h[m[:message_id]] || [] 51: @h[m[:message_id]] = array << m[:before]+" "+m[:interresting_part] 52: end unless m.nil? 53: end 54: def read_from_stdin_and_write_to_file 55: @h = {} 56: $stdin.each_line do |line| parse_line(line) end 57: File.open( "./#{@options.output_file}", File::WRONLY|File::TRUNC|File::CREAT, 0600) do |output| 58: PP.pp(@h,output) 59: end # auto-close output file 60: return @h 61: end 62: def main 63: parse_command_line 64: read_from_stdin_and_write_to_file 65: end 66: 67: main 68: 69: __END__
Ajouter une option -v
qui affiche sur stdout
le nombre de mails traités, et une option - -help qui affiche la syntaxe d'utilisation de la commande.
1: #!/bin/bash 2: 3: # Fonction pour afficher l'utilisation du script 4: function usage() { 5: cat <<EOF 6: $0 [-v] -o <output_file> <maillog_file> [ <maillog_file...] 7: $0 -h 8: EOF 9: } 10: 11: # Vérification de la ligne de commande 12: OUTFILE= 13: FILES= 14: SHOWMAILCOUNT=0 15: while getopts "vo:" OPT 16: do 17: case $OPT in 18: h) 19: usage 20: exit 0 21: ;; 22: o) 23: OUTFILE=$OPTARG 24: ;; 25: v) 26: SHOWMAILCOUNT=1 27: ;; 28: ?) 29: echo "Option $OPT inconnue" > /dev/stderr 30: usage 31: exit 1 32: esac 33: done 34: shift $((OPTIND - 1)) # Elimine de $* les arguments traités par getopt 35: if [ -z "$OUTFILE" ]; then 36: echo "L'option -o est obligatoire" > /dev/stderr 37: usage 38: exit 1 39: fi 40: if [ -z "$*" ]; then 41: echo "Vous devez spécifiez au moins un fichier à analyser" 42: usage 43: exit 1 44: fi 45: 46: # Fichier intermédiaire pour limiter le nombre d'appel 47: # à grep au sein d'une boucle 48: # $$ est le PID du script => unicité du fichier tampon 49: BUFFILE=/tmp/maillog-tmp.$$ 50: 51: # Construction du fichier intermédiaire : 52: # - grep sélectionne les lignes avec un message_id dans les N fichiers d'entrée 53: # - sed replace le message_id en debut de ligne, retire le PID et 54: # le nom du host 55: # - comme toutes les lignes commencent par un message_id, sort 56: # permet de les rassembler 57: grep -h -E ' [0-9A-F]{10}:' $* | sed 's/^\(\w\+ \+[0-9]\+ [0-9]\+:[0-9]\+:[0-9]\+\) \w\+ \(.*\)\[[0-9]*\]: \([0-9A-F]\{10\}\):\(.*\)/\3\1 \2\4/g' | sort > $BUFFILE 58: 59: # Affichage de toutes les lignes relatives à un message_id et comptage 60: # du nombre de mail traités 61: MSID="" 62: COUNT=0 63: while read LINE 64: do 65: K=${LINE:0:10} # 10 premiers caractères => message ID 66: if [ ! "$K" = "$MSID" ]; then 67: # Première fois que l'on rencontre ce message_id => nouveau bloc 68: [ -z "$MSID" ] || echo "" 69: MSID=$K 70: echo "$MSID:" 71: ((COUNT++)) 72: fi 73: echo -e "\t${LINE:10}" # Le reste de la ligne avec le message de log 74: done < $BUFFILE > $OUTFILE 75: 76: # Si -v, affichage du nombre de mails traités 77: if [ $SHOWMAILCOUNT -eq 1 ] ; then 78: echo "$COUNT mails traités" 79: fi 80: 81: rm $BUFFILE
1: use FileHandle ; 2: use Getopt::Long ; 3: use strict ; 4: 5: #------------- 6: sub usage 7: { 8: my ($msg) = @_ ; 9: 10: print << "EOD" ; 11: $msg 12: usage: $0 [-v] -o output_file files 13: $0 --help 14: EOD 15: exit(1) ; 16: } 17: 18: #------------- 19: my ($verbose,$fout) ; 20: 21: GetOptions( 22: "h|help" => \&usage # envoie vers une fonction 23: , "v|verbose+" => \$verbose 24: , "o=s" => \$fout 25: ) or usage("syntax error") ; 26: usage("-o arg is missing") unless $fout=~/\S/ ; 27: 28: my $h ; 29: my $nb = 0 ; 30: 31: while(<>) 32: { 33: next unless /\s[0-9A-F]{10}:/ ; 34: s/^(\w+ \d+ +\d+:\d+:\d+) \w+ (.*?)\[\d+\]/$1 $2/ ; 35: my($begin,$id,$end) = /^(.*) ([0-9A-F]{10})(: .*)/ ; 36: $nb++ unless exists $h->{$id} ; 37: push(@{$h->{$id}},$begin.$end) ; 38: } 39: 40: my $fh = new FileHandle(">$fout") or die $! ; 41: for my $key (keys %$h) 42: { 43: $fh->print("\n$key:\n") ; 44: for my $i (@{$h->{$key}}) 45: { 46: $fh->print("\t$i\n") ; 47: } 48: } 49: $fh->close() ; 50: print "$nb mails traités\n" if $verbose ;
1: #!/usr/bin/python 2: '''Usage : ./myprog [-h] [-v] -o output_file files''' 3: import fileinput 4: import re 5: import getopt 6: import sys 7: 8: 9: filename=None 10: verbose=None 11: try: 12: opt,args = getopt.getopt(sys.argv[1:], "o:hv", ["output=","help","verbose"]) 13: except getopt.GetoptError,err: 14: print str(err) 15: sys.exit(2) 16: 17: for option in opt: 18: if option[0]=='-o' or option[0]=="--output": 19: filename=option[1] 20: if option[0]=='-h' or option[0]=="--help": 21: print __doc__ 22: sys.exit(1) 23: if option[0]=='-v' or option[0]=="--verbose": 24: verbose=True 25: 26: sys.argv[1:]=args # on nettoie la ligne de commande pour fileinput 27: if filename==None: 28: print "l'option -o est obligatoire" 29: sys.exit(2) 30: 31: h = {} 32: p = re.compile('\s[0-9A-F]{10}:') 33: reg = re.compile('^(?P<before>.*): (?P<message_id>[0-9A-F]{10}):(?P<after> .*)') 34: for line in fileinput.input(): 35: if p.search(line): 36: line_inter=re.sub('^(\w+ \d+ +\d+:\d+:\d+) \w+ (.*?)\[\d+\]',r'\1 \2',line) 37: m=reg.match(line_inter) 38: if m: 39: if m.group('message_id') in h.keys(): 40: h[m.group('message_id')].append(m.group('before')+m.group('after')) 41: else: 42: h[m.group('message_id')]=[m.group('before')+m.group('after')] 43: 44: 45: fh=open(filename,'w') 46: for key in h.keys(): 47: fh.write('\n%s:\n'%key) 48: for elt in h[key]: 49: fh.write('\t%s\n'%elt) 50: fh.close() 51: 52: if verbose: 53: print "%s mails traites"%len(h.keys())
1: #!/usr/bin/env ruby 2: 3: # 4: # 5: # == Synopsis 6: # 7: # Débroussailler des log d'un serveur de mails 8: # Version qui gère les argument de la ligne de commande 9: # 10: # == Usage 11: # 12: # ./myprog --help 13: # ./myprog --output-file output.txt 14: # ./myprog ---verbose --output-file output.txt 15: # 16: 17: %w(rubygems oniguruma pp getoptlong rdoc/usage).each do |lib| require lib end 18: 19: Options = Struct.new( :output_file, :verbose ) 20: @options = Options.new( nil, false ) 21: 22: @regexp = Oniguruma::ORegexp.new('^(?<before>.*): (?<message_id>[0-9A-F]{10}): (?<interresting_part>.*)') 23: 24: # 25: # parse arguments on command line 26: # 27: def parse_command_line 28: if ARGV.length == 0 29: RDoc::usage 30: exit 1 31: end 32: 33: opts = GetoptLong.new( 34: [ '--help', '-h', GetoptLong::NO_ARGUMENT ], 35: [ '--output-file','-o', GetoptLong::REQUIRED_ARGUMENT ], 36: [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ] 37: ) 38: 39: begin 40: opts.each{|opt,arg| 41: case opt 42: when '--help' 43: RDoc::usage 44: when '--output-file' 45: @options.output_file = arg 46: when '--verbose' 47: @options.verbose = true 48: end 49: } 50: rescue GetoptLong::MissingArgument 51: exit 2 52: end 53: end 54: def parse_line(line) 55: m = @regexp.match(line) 56: begin 57: array = @h[m[:message_id]] || [] 58: @h[m[:message_id]] = array << m[:before]+" "+m[:interresting_part] 59: end unless m.nil? 60: end 61: def read_from_stdin_and_write_to_file 62: @h = {} 63: $stdin.each_line do |line| parse_line(line) end 64: File.open( "./#{@options.output_file}", File::WRONLY|File::TRUNC|File::CREAT, 0600) do |output| 65: PP.pp(@h,output) 66: end # auto-close output file 67: return @h 68: end 69: def main 70: parse_command_line 71: h = read_from_stdin_and_write_to_file 72: puts h.size if @options.verbose 73: end 74: 75: main 76: 77: __END__
$Id: maillog.txt 682 2012-05-28 17:22:12Z jaclin $