Platzhalter in Konfigurationsdatei ersetzen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
api
User
Beiträge: 181
Registriert: Donnerstag 7. August 2008, 21:23

Hallo zusammen,

ich habe eine gegebene Konfigurationsdatei, die wie folgt ausssieht:

Code: Alles auswählen

# Parameter
  P  %S1%  s134
  P  %A%   app
  P  %M%   /mar
# Destination
  D   2   %S1%,s71  testam      /%A%%M%/tmp    TEST
  D   3   s78       testam      %M%/tmp        TEST
Nun würde ich diese Datei gerne einlesen. Die Zeile, die mit einem Feld beginnt, in dem "D" steht (hier also die letzten Zeilen), sollen die Platzhalter, die mit dem Kennzeichen "P" beginnen, ersetzen. Es soll dann also folgende Liste erzeugt werden:

Code: Alles auswählen

['D', '2', 's134,s71', 'testam', '/app/mar/tmp', 'TEST'],
['D', '3', s78, 'testam', '/mar/tmp', 'TEST']
Ich verwende Python 2.6

Wie kann man das lösen?

CU,
API
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Geh die Datei 2x Zeile für Zeile durch.
Beim ersten Mal schaust du nach allen Zeilen die mit P beginnen und baust dir ein dict aus Parametername: wert.

Dann machst du einen zweiten Duchlauf und ersetzt, wenn die Zeile mit "D" beginnt, den Platzhalter durch den Wert und schon hast du was du brauchst.

str.split() und str.replace() werden dir gute Dienste leisten.
api
User
Beiträge: 181
Registriert: Donnerstag 7. August 2008, 21:23

@sparrow: Danke schonmal für den Vorschlag.

Allerdings weiss ich noch nicht, wie ich das umsetzen soll. Da ich ja nicht weiss, welcher Platzhalter wann vorkommt. Also ich habe jetzt die Platzhalter in ein Dictionary eingelesen:

Code: Alles auswählen

>>> par_dic
{'%M%': '/mar', '%A%': 'app', '%S1%': 's134'}
Die Zeilen, die mit "D" beginnen, habe ich in eine Liste eingelesen:

Code: Alles auswählen

>>> d_list
[['D', '2', '%S1,s71', 'testam', '/%A%%M%/tmp', 'TEST'], ['D', '3', 's78', 'testam', '%M%/tmp', 'TEST']]
Wenn ich nun dadurch iteriere, wie mache ich das mit dem Ersetzen?

Code: Alles auswählen

[value.replace(par_dic[??]) for value in d_list]
So läufts nicht - ist klar. Aber wie muss ich das anstellen?
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Also mir wäre das zu kompliziert, deshalb habe ich ja gesagt du sollst die Datei 2x durch gehen.
Damit erschlägst du auch das Problem, dass vielleicht nach D-Zeilen noch P-Zeilen auftauchen.

Beim ersten Mal das dict aufbauen und beim zweiten Mal ersetzen.
Dann kannst du das tun bevor du die neuen "D"-Zeilen in Listen umwandelst und das Austauschen vorher vornehmen.

Dann brauchst du nur noch schauen ob der key in der Zeile ist und dann entsprechend duch den Wert ersetzen.
Zuletzt geändert von sparrow am Donnerstag 3. Februar 2011, 13:07, insgesamt 1-mal geändert.
BlackJack

@api: Man könnte mit einem regulären Ausdruck nach den "Variablennamen" suchen und ersetzen. Als Argument zum Ersetzen kann man bei `re.sub()` auch eine Funktion angeben, die das `Match`-Objekt bekommt, und die Ersetzung liefern muss.

Wenn alle Parameter definiert werden, bevor sie eingesetzt werden, kommt man auch mit einem Durchlauf bei der Datei aus:

Code: Alles auswählen

import re
from functools import partial


def iter_destinations(lines):
    parameters = {'': '%'}      # '%%' will be replaced by '%'.
    
    substitute = partial(re.compile(r'%(.*?)%').sub,
                         lambda match: parameters[match.group(1)])
    
    def process_comment(args):
        pass
    
    def process_param(args):
        name, value = args.split()
        parameters[name[1:-1]] = value
    
    def process_destination(args):
        return substitute(args).split()
    
    dispatch = {
        '#': process_comment,
        'P': process_param,
        'D': process_destination
    }
    
    for line in lines:
        command, args = line.split(None, 1)
        result = dispatch[command](args)
        if command == 'D':
            yield result


def main():
    with open('test.txt') as lines:
        print list(iter_destinations(lines))


if __name__ == "__main__":
    main()
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Ohne reguläre Ausdrücke und mit Zwischenspeichern der Zeilen:

Code: Alles auswählen

def do_so(data):

    # find parameter(1st cycle)
    parameter = {} # will contain a paramter: value dict
    lines = [] # hold the lines for the second cycle
    for line in data:
        splits = line.split()
        lines.append(line)
        if splits[0] == "P":
            parameter[splits[1]] = splits[2]

    # replace (2nd cycle)
    d_list = [] # an entry for each D-line
    for line in lines:
        splits = line.split()
        if splits[0] == "D":
            for key in parameter:
                if key in line:
                    line = line.replace(key, parameter[key])
            d_list.append(line.split())

    return d_list


def main():
    with open("test.txt", "r") as data:
        print do_so(data)

if __name__ == "__main__":
    main()
api
User
Beiträge: 181
Registriert: Donnerstag 7. August 2008, 21:23

@BlackJack: Eine interessante Alternative :)

Mit meinem Python 2.6 funktioniert das allerdings nicht (functools kennt er nicht & "with open ..." mag er auch nicht)

Ich könnte allerdings auch Python 3.1 nehmen - da musste ich nur noch das print abändern. Aber ansonsten funzt es.

Danke dir schonmal...
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Ich hätte es so hier gemacht, allerdings ist BlackJacks Script wesentlich schöner, und warscheinlich auch um einiges effizienter.

Code: Alles auswählen

#!/usr/bin/env python

def replace(dest, parameters):
    for key, value in parameters.iteritems():
        for num in xrange(len(dest)):
            dest[num] = dest[num].replace(key, value)
    return dest

def read(fileobject):
    parameters, destinations = {}, []
    for line in fileobject:
        line = line.strip().split()
        if line[0] == "P": parameters[line[1]] = line[2]
        elif line[0] == "D": destinations.append(line)
    return parameters, destinations
    

if __name__ == "__main__":
    with open("test.cnf") as handle:
        parameters, destinations = read(handle)
            
    fin = (replace(dest, parameters) for dest in destinations)
    print(list(fin))
@api
Dann hast du nicht Python 2.6, denn dort existiert das with statement. Prüff die Version noch mal, warscheinlich hast du 2.5 oder niedriger. Hier kann dir mit einem __future__ import geholfen sein.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
BlackJack

@api: `functools` gibt es seit Python 2.5 in der Standardbibliothek und die ``with``-Anweisung kann man dort mit ``from __future__ import with_statement`` aktivieren. Ich habe das Programm mit 2.6 geschrieben und getestet.
api
User
Beiträge: 181
Registriert: Donnerstag 7. August 2008, 21:23

@ALLE: Oh sorry. (Wer lesen kann, ist klar im Vorteil) Ich habe die Python-Version 2.4.6 :oops:
api
User
Beiträge: 181
Registriert: Donnerstag 7. August 2008, 21:23

Nun ja, wie schon oben erwähnt, könnte ich ja auch Python 3.1 verwenden.

Alles in allem, finde ich BlackJacks Script am elegantesten.

Aber ich danke euch allen für die konstruktiven Vorschläge... :D
Antworten