Je voudrais utiliser Python pour analyser /var/log/monthly.out sur OS X pour exporter les totaux de comptabilité utilisateur. Le fichier journal ressemble à ceci:

Mon Feb  1 09:12:41 GMT 2016

Rotating fax log files:

Doing login accounting:
    total      688.31
    example   401.12
    _mbsetupuser   287.10
    root         0.05
    admin     0.04

-- End of monthly output --

Tue Feb 16 14:27:21 GMT 2016

Rotating fax log files:

Doing login accounting:
    total        0.00

-- End of monthly output --

Thu Mar  3 09:37:31 GMT 2016

Rotating fax log files:

Doing login accounting:
    total      377.92
    example   377.92

-- End of monthly output --

J'ai pu extraire les paires nom d'utilisateur / totaux avec cette expression régulière:

\t(\w*)\W*(\d*\.\d{2})

En Python:

>>> import re
>>> re.findall(r'\t(\w*)\W*(\d*\.\d{2})', open('/var/log/monthly.out', 'r').read())
[('total', '688.31'), ('example', '401.12'), ('_mbsetupuser', '287.10'), ('root', '0.05'), ('admin', '0.04'), ('total', '0.00'), ('total', '377.92'), ('example', '377.92')]

Mais je ne peux pas comprendre comment extraire la ligne de date de telle manière qu'elle soit attachée aux paires nom d'utilisateur / totaux pour ce mois.

2
SillyWilly 7 mars 2016 à 19:38

4 réponses

Meilleure réponse

Utilisez str.split().

import re

re_user_amount = r'\s+(\w+)\s+(\d*\.\d{2})'
re_date = r'\w{3}\s+\w{3}\s+\d+\s+\d\d:\d\d:\d\d \w+ \d{4}'

with open('/var/log/monthly.out', 'r') as f:
    content = f.read()
    sections = content.split('-- End of monthly output --')

    for section in sections:
        date = re.findall(re_date, section)
        matches = re.findall(re_user_amount, section)

        print(date, matches)

Si vous souhaitez transformer la chaîne de date en une date / heure réelle, consultez Conversion de chaîne en date-heure.

2
Community 23 mai 2017 à 10:28

Vous voudrez peut-être essayer l'expression régulière suivante, qui n'est pas si élégante cependant:

import re

string = """
Mon Feb  1 09:12:41 GMT 2016

Rotating fax log files:

Doing login accounting:
    total      688.31
    example   401.12
    _mbsetupuser   287.10
    root         0.05
    admin     0.04

-- End of monthly output --

Tue Feb 16 14:27:21 GMT 2016

Rotating fax log files:

Doing login accounting:
    total        0.00

-- End of monthly output --

Thu Mar  3 09:37:31 GMT 2016

Rotating fax log files:

Doing login accounting:
    total      377.92
    example   377.92

-- End of monthly output --
"""
pattern = '(\w+\s+\w+\s+[\d:\s]+[A-Z]{3}\s+\d{4})[\s\S]+?((?:\w+)\s+(?:[0-9.]+))\s+(?:((?:\w+)\s*(?:[0-9.]+)))?\s+(?:((?:\w+)\s*(?:[0-9.]+)))?\s*(?:((?:\w+)\s+(?:[0-9.]+)))?\s*(?:((?:\w+)\s*(?:[0-9.]+)))?'
print re.findall(pattern, string)

Production:

[('Mon Feb  1 09:12:41 GMT 2016', 'total      688.31', 'example   401.12', '_mbsetupuser   287.10', 'root         0.05', 'admin     0.04'), 
('Tue Feb 16 14:27:21 GMT 2016', 'total        0.00', '', '', '', ''), 
('Thu Mar  3 09:37:31 GMT 2016', 'total      377.92', 'example   377.92', '', '', '')]

REGEX DEMO.

0
Quinn 7 mars 2016 à 19:39

Voici quelque chose de plus court:

with open("/var/log/monthly.out") as f:
    months = map(str.strip, f.read().split("-- End of monthly output --"))
    for sec in filter(None, y):
        date = sec.splitlines()[0]
        accs = re.findall("\n\s+(\w+)\s+([\d\.]+)", sec)
        print(date, accs)

Cela divise le contenu du fichier en mois, extrait la date de chaque mois et recherche tous les comptes de chaque mois.

0
Zach Gates 7 mars 2016 à 17:30

Eh bien, il y a rarement un remède magique pour tout basé sur l'expression régulière. Les regex sont un excellent outil pour une simple analyse de chaînes, mais elles ne doivent pas remplacer une bonne vieille programmation!

Donc, si vous regardez vos données, vous remarquerez qu'elles commencent toujours par une date et se terminent par le -- End of monthly output -- ligne. Donc, une bonne façon de gérer ce serait de diviser vos données par chaque sortie mensuelle.

Commençons par vos données:

>>> s = """\
... Mon Feb  1 09:12:41 GMT 2016
... 
... Rotating fax log files:
... 
... Doing login accounting:
...     total      688.31
...     example   401.12
...     _mbsetupuser   287.10
...     root         0.05
...     admin     0.04
... 
... -- End of monthly output --
... 
... Tue Feb 16 14:27:21 GMT 2016
... 
... Rotating fax log files:
... 
... Doing login accounting:
...     total        0.00
... 
... -- End of monthly output --
... 
... Thu Mar  3 09:37:31 GMT 2016
... 
... Rotating fax log files:
... 
... Doing login accounting:
...     total      377.92
...     example   377.92
... 
... -- End of monthly output --"""

Et séparons-le en fonction de la ligne de fin de mois:

>>> reports = s.split('-- End of monthly output --')
>>> reports
['Mon Feb  1 09:12:41 GMT 2016\n\nRotating fax log files:\n\nDoing login accounting:\n    total      688.31\n    example   401.12\n    _mbsetupuser   287.10\n    root         0.05\n    admin     0.04\n\n', '\n\nTue Feb 16 14:27:21 GMT 2016\n\nRotating fax log files:\n\nDoing login accounting:\n    total        0.00\n\n', '\n\nThu Mar  3 09:37:31 GMT 2016\n\nRotating fax log files:\n\nDoing login accounting:\n    total      377.92\n    example   377.92\n\n', '']

Ensuite, vous pouvez séparer les données comptables du reste du journal:

>>> report = reports[0]
>>> head, tail = report.split('Doing login accounting:')

Extrayons maintenant la ligne de date:

>>> date_line = head.strip().split('\n')[0]

Et remplissez un dict avec ces paires nom d'utilisateur / totaux:

>>> accounting = dict(zip(tail.split()[::2], tail.split()[1::2]))

L'astuce consiste à utiliser zip() pour créer des paires à partir d'itérateurs sur tail. La gauche" le côté de la paire étant un itérateur commençant à l'index 0, itérant tous les 2 éléments, le ~ droit ~ le côté de la paire étant un itérateur commençant à l'index 1, itérant tous les 2 éléments. Ce qui rend:

{'admin': '0.04', 'root': '0.05', 'total': '688.31', '_mbsetupuser': '287.10', 'example': '401.12'}

Alors maintenant que c'est fait, vous pouvez le faire dans une boucle for:

import datetime

def parse_monthly_log(log_path='/var/log/monthly.out'):
    with open(log_path, 'r') as log:
        reports = log.read().strip('\n ').split('-- End of monthly output --')
        for report in filter(lambda it: it, reports):
            head, tail = report.split('Doing login accounting:')
            date_line = head.strip().split('\n')[0]
            accounting = dict(zip(tail.split()[::2], tail.split()[1::2]))
            yield {
                'date': datetime.datetime.strptime(date_line.replace('  ', ' 0'), '%a %b %d %H:%M:%S %Z %Y'),
                'accounting': accounting
            }

>>> import pprint
>>> pprint.pprint(list(parse_monthly_log()), indent=2)
[ { 'accounting': { '_mbsetupuser': '287.10',
                    'admin': '0.04',
                    'example': '401.12',
                    'root': '0.05',
                    'total': '688.31'},
    'date': datetime.datetime(2016, 2, 1, 9, 12, 41)},
{ 'accounting': { 'total': '0.00'},
    'date': datetime.datetime(2016, 2, 16, 14, 27, 21)},
{ 'accounting': { 'example': '377.92', 'total': '377.92'},
    'date': datetime.datetime(2016, 3, 3, 9, 37, 31)}]

Et là, vous allez avec une solution pythonique sans une seule expression régulière.

NB: J'ai dû faire un petit tour avec le datetime, car le journal contient un numéro de jour rempli d'espace et non nul (comme prévu strptime), j'ai utilisé la chaîne .replace() pour changer un double espace en un 0 dans la chaîne de date

Remarque: les filter() et split() utilisés dans la boucle for report… sont utilisés pour supprimer les rapports vides de début et de fin, selon la façon dont le fichier journal démarre ou se termine.

2
zmo 7 mars 2016 à 17:31