Seite 1 von 1

Doppelte Zeilen in Textdateien finden

Verfasst: Montag 12. Juli 2004, 14:11
von gerold
Hallo!

Mit diesem Python-Skript können doppelte Zeilen in Textdateien gefunden werden. Zusätzlich kann man als Parameter einen Filterstring (Regular Expression) übergeben.

ToDo´s:
- Mehrere Filterstrings ermöglichen (erledigt)
- Das Verhalten des Programmes mit Parametern steuern lassen
(-h --help = Hilfe anzeigen; -v --version = Version anzeigen; -i --ignore-case = Beim Filter Groß-/Kleinschreibung ignorieren; usw.) (erledigt)
- Die Ausgabe besser formatieren (auch mit Parametern steuerbar) (erledigt)
- Fehler abfangen (erledigt)
- Algorithmus optimieren

Code: Alles auswählen

<<siehe Version 0.3>>
mfg
Gerold
:-)

Version 0.2

Verfasst: Donnerstag 22. Juli 2004, 13:01
von gerold
Die Ausgabe wird jetzt sortiert dargestellt.

Code: Alles auswählen

<<siehe Version 0.3>>

Version 0.3

Verfasst: Freitag 23. Juli 2004, 21:04
von gerold
Hi!

8) Es wurde einiges geändert.

- Mehrere Filterstrings möglich
- Das Verhalten des Programmes lässt sich jetzt mit Parametern steuern
- Die Ausgabe kann jetzt anders formatiert dargestellt werden
- Mögliche Fehler beim Öffnen der Dateien werden abfangen

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
"""
******************************************************************************
* Description:  Findet mehrfach vorkommende Zeilen in einer Textdatei
*               und gibt diese in der Konsole zurück.
*               Die zu durchsuchenden Zeilen können mit Regular Expressions
*               gefiltert werden.
* Filename:     multilinesearch(.py)
* Version:      0.3
* Created:      2004-07-12 by Gerold Penz - gerold.penz(at)aon.at
* Modified:     2004-07-23 by Gerold Penz
* License:      LGPL: http://www.opensource.org/licenses/lgpl-license.php
* Requirements: Python 2.3:  http://www.python.org
******************************************************************************
"""

VERSION = "0.3"


#----------------------------------------------------------------------
def parse_options():
    """
    Wertet die Argumente aus und gibt diese zurück
    """

    from optparse import OptionParser
    from sys import exit

    #Usage-Text angeben und Parser-Objekt erzeugen
    usage = "%prog [options] FILE [File] ...\n\n" + \
            "    finds multiple existing lines in textfiles\n" + \
            "    by Gerold Penz 2004 -- gerold.penz@aon.at"
    version = "%prog " + VERSION
    parser = OptionParser(usage = usage, version = version)

    #Optionen hinzufuegen
    parser.add_option("-e", "--regexp-include",
        dest = "filter_list_include",
        action = "append",
        metavar = "PATTERN",
        help="show only filtered lines; "
             "PATTERN must be a regular expression")

    parser.add_option("-n", "--regexp-exclude",
        dest = "filter_list_exclude",
        action = "append",
        metavar = "PATTERN",
        help="ignore filtered lines; "
             "PATTERN must be a regular expression")

    parser.add_option("-i", "--ignore-case",
        action = "store_false",
        default = True,
        dest = "case_sensitive",
        help = "ignore case distinctions in the PATTERN")

    parser.add_option("-m", "--max-lines",
        dest = "max_lines",
        type = "int",
        metavar = "NUM",
        help = "stop reading a file after NUM lines")

    parser.add_option("-d", "--debug",
        dest = "debug",
        action = "store_true",
        default = False,
        help = "show debug messages")

    parser.add_option("--no-linenumbers",
        dest = "show_linenumbers",
        action = "store_false",
        default = True,
        help = "show no linenumbers")

    parser.add_option("--not-among-each-other",
        dest = "among_each_other",
        action = "store_false",
        default = True,
        help = "don't show lines exactly among each other")

    #Optionen parsen
    (options, args) = parser.parse_args()

    #Optionen und Argumente anzeigen
    if options.debug:
        print "Options:"
        print options
        print "-"*70
        print "Arguments:"
        for item in args:
            print item
        print "-"*70

    #Kein Argument angegeben --> Hilfe anzeigen und EXIT
    if len(args) == 0:
        parser.print_help()
        exit(0)

    #Rueckgabe
    return (options, args)


#----------------------------------------------------------------------
def find_double_rows(
    filename,
    filter_list_exclude = None,
    filter_list_include = [".+"],
    case_sensitive = True,
    max_lines = None):
    """
    Durchsucht die angegebenen Datei nach doppelten Zeilen.
    filename = Dateiname und Pfad zur zu durchsuchenden Datei
    filter_list_exclude = ein RE-Ausdruck zum Filtern der Zeilen
    filter_list_include = ein RE-Ausdruck zum Filtern der Zeilen
    case_sensitive = True, wenn bei den Filtern auf 
                     Gross-/Kleinschreibung geachtet werden soll
    max_lines = Anzahl der maximal zu durchsuchenden Zeilen einer Datei
    """

    import re
    import sets
    from sys import exit

    try:
        file = open(filename, "ru")
    except IOError, (errno, strerror):
        print strerror
        print "Can't open the file \"%s\"" % filename
        exit(0)
    except:
        raise

    rows = {}
    set = sets.Set()

    linenumber = 0
    for line in file:
        linenumber += 1

        #Abbruch, wenn max_lines ueberschritten wird
        if max_lines:
            if linenumber > max_lines:
                break

        #Zeilenumbruch abschneiden
        line = line[:-1]

        #Standardfilter hinzufügen
        if not filter_list_include:
            filter_list_include = [".+"]
        else:
            filter_list_include.append(".+")

        add_line = True

        #Jeden Exclude-Filter durchlaufen
        if filter_list_exclude:
            for filter in filter_list_exclude:
                if case_sensitive:
                    if re.match(filter, line):
                        add_line = False
                        break
                else:
                    if re.match(filter, line, re.IGNORECASE):
                        add_line = False
                        break

        #Jeden Include-Filter durchlaufen
        if add_line:
            for filter in filter_list_include:
                if case_sensitive:
                    if not re.match(filter, line):
                        add_line = False
                        break
                else:
                    if not re.match(filter, line, re.IGNORECASE):
                        add_line = False
                        break

        #Wenn die Zeile nicht ausgefiltert wurde --> verwenden
        if add_line:
            if line in set:
                rows[linenumber] = line
            else:
                set.add(line)

    file.close()

    return rows, linenumber


#----------------------------------------------------------------------
def main():

    #Optionen und Argumente parsen
    (options, args) = parse_options()

    #jede Datei durchlaufen
    for file in args:
        #Falls mehrere Dateien durchlaufen werden --> Linie
        if len(args) > 1:
            print
            print "File:", file
            print "-"*70

        #Doppelte Zeilen finden
        rows, lines = find_double_rows(
            file,
            options.filter_list_exclude,
            options.filter_list_include,
            options.case_sensitive,
            options.max_lines)

        #Sortieren
        keys = list(rows)
        keys.sort()

        #Anzeigen
        if options.show_linenumbers:
            if options.among_each_other:
                printarg = "%-" + str(len(str(lines))) + "s %s"
                for key in keys:
                    print printarg % (str(key) + ":", rows[key])
            else:
                for key in keys:
                    print "%s: %s" % (key, rows[key])
        else:
            for key in keys:
                print "%s" % rows[key]


#----------------------------------------------------------------------
if __name__ == "__main__":
    main()

mfg
Gerold
:-)

Verfasst: Freitag 23. Juli 2004, 22:25
von Dookie
Hi gerold,

feine Arbeit!

Nur eine Kleinigkeit: Setze die Imports an den Anfang des Scripts, gleich nach den Einleitenden Docstring. Dann sieht jeder gleich was für Module Dein Script zusätzlich braucht.


Gruß

Dookie

Verfasst: Samstag 24. Juli 2004, 07:39
von gerold
Dookie hat geschrieben:Nur eine Kleinigkeit: Setze die Imports an den Anfang des Scripts, gleich nach den Einleitenden Docstring. Dann sieht jeder gleich was für Module Dein Script zusätzlich braucht.
Hi Dookie,
hi all!

Darüber habe ich auch schon mal nachgedacht.
Hier meine Überlegung:
Was ist, wenn jemand nur die Funktion find_double_rows von multilinesearch.py importiert. Wenn ich jetzt meine Imports an den Anfang der Datei stelle, dann würde ich dem, der nur eine Funktion importieren will, Imports aufzwingen die er eigentlich gar nicht braucht.
Oder ist diese Mühe umsonst, und es werden sowiso immer alle Imports importiert?
Ich könnte mir als Alternative vorstellen, alle Imports als Kommentar an den Anfang der Datei zu stellen...
Gibt es zu diesem Thema eine Empfehlung?

lg
Gerold
:-)

Verfasst: Samstag 24. Juli 2004, 11:35
von Dookie
Also sys kannst immer importieren, kein script kommt ohne sys aus, wird ja sowieso intern importiert um die Argumente der Kommandozeile zu bekommen.
Den Import von optparser könntest in der Funktion die ihn braucht lassen. Gleiches gilt für Sets.
Alles Andere, also sys und re würd ich nach oben auserhalb der Funktionen verlegen.
Statt from sys import exit würd ich import sys bevorzugen und dann sys.exit() so aufrufen.


Gruß

Dookie

Verfasst: Sonntag 25. Juli 2004, 10:49
von Milan
gerold hat geschrieben:Oder ist diese Mühe umsonst, und es werden sowiso immer alle Imports importiert?
Ich glaube, genau das ist der Fall. Soweit ich weiß wird immer das ganze Modul initialisiert (also einmal ausgeführt) und dann werden die Objekte zur Verfügung gestellt. Von daher habe ich immer ganz oben eine meist längere Importliste stehen, die auch alle Fall-abhängigen-imports abdeckt.

Ich bin mehr ein Anhänger der Form alles am Anfang, damit das Programm hinterher schön flutscht, denn soviel RAM brauchen die Module nicht und während der Laufzeit in Funktionen zu importieren erscheint mir unsauber :wink: .

Verfasst: Sonntag 25. Juli 2004, 12:57
von Dookie
Hi nochmal,

ich halte es so, daß ich, wenn ich nur eine Funktion, aus einem speziellen Modul, in einer einzigen Ausnahmebehandlung brauche, dann wird diese Funktion dort importiert. Auch im Bereich nach

Code: Alles auswählen

if __name__ == "__main__":
    ...
kommen eventuell noch Imports die für die Verwendung eines Moduls als Script nötig sind, aber als importiertes Modul nicht gebraucht werden.


Gruß

Dookie

Verfasst: Sonntag 25. Juli 2004, 17:21
von gerold
Hallo!

Ich hoffe, dass ich es verständlich formulieren kann. :?

Meine Tests haben folgendes ergeben:
Wenn eine Import-Anweisung, nicht eingerückt (also in der Hirarchie ganz oben), in einem Modul steht, egal ob oben oder unten im Skript, dann wird diese beim Laden des Moduls ausgeführt. Da ein Modul wiederum mehrere Abhängigkeiten haben kann, werden diese ebenfalls geladen wenn in diesen Modulen Import-Anweisungen stehen die nicht durch Klassendefinitionen oder auf eine andere Art vom restlichen Code abgegrenzt werden. Das zieht bei größeren Programmen einen Rattenschwanz nach sich, den ich bei meinen Tests spürbar bemerkte.
Ganz besonders ist mir dieses Verhalten unter Windows in Verbindung mit GTK aufgefallen. Da konnte man schon mal eine bis zwei Sekunden warten, auch wenn GTK von den importierten Funktionen nicht benötigt wurde.
Für mich bedeutet das, dass es besser ist, wenn Module dort importiert werden, wo sie gebraucht werden. Das hat noch einen Vorteil --> ich sehe die Funktionen der importierten Module nur in dem Bereich, in dem ich sie auch brauche. Bei größeren Projekten könnten dadurch Fehler vermieden werden.
Die Module werden anscheinend nur bei der ersten Import-Anweisung in den Speicher geladen. Zusätzliche, später oder in anderen Funktionen auftretende, Import-Anweisungen greifen nicht mehr auf die Festplatte zu. Das zu importierende Modul wird nicht noch einmal initialisiert, es wird nur der Zugriff auf die Funktionen des importierten Moduls frei gegeben.
Dabei konnte ich keinen merkbaren Unterschied zwischen den Anweisungen from MODULE import FUNCTION und import MODULE feststellen. Allerdings habe ich dieses Verhalten nicht gestoppt.

Vieleicht weiß noch jemand etwas wissenswertes zu diesem Thema.
Ich lasse mich gerne beraten. :-)

Ich möchte in meinen kleinen Programmen die gleiche Struktur wie in den großen Programmen verwenden. Auch wenn es bei kleinen Programmen kein Problem ist, die Module, die in mehreren Funktionen verwendet werden, am Anfang zu importieren und die Module, die nur vereinzelt in Funktionen verwendet werden, in diesen selbst zu importieren, ist es bei großen Projekten unmöglich. Deshalb dachte ich mir, wenn es kein Problem macht ein Modul mehrmals zu importieren, dass ich die Module dort importiere wo sie gebraucht werden.
Um trotzdem eine Übersicht über alle benötigten Module zu haben, könnte ich mir vorstellen, für jedes zu importierende Modul einen Kommentar an den Anfang des Programms zu schreiben.

Gibt es protokollierte Geschwindigkeitstests? Was wird von den Programmierern von Python empfohlen?
Ich konnte mich in der kurzen Zeit, in der ich auch in Python programmiere, noch nicht genauer mit den Internas von Python beschäftigen.

lg
Gerold
:-)

Verfasst: Sonntag 25. Juli 2004, 17:52
von Dookie
Hi gerold.

stimmt, der Import eines Moduls kann einen Rattenschwanz anderer Imports nach sich ziehen. Und Module werden immer nur beim ersten Import intialisiert.
Der Unterschied zwischen import Modul und from Modul import Funktion ist darin zu sehen, was in den Namespace des importierenden Moduls (__dict__) aufgenommen wird. Im ersten Fall ists ein Verweis auf das Modul, und im zweiten ein Verweis auf die Funktion. Bei kurzen Modulnamen oder aussageschwachen Funktionsnamen bevorzuge ich das Modul zu importieren und die verwendete Funktion qualifiziert, also mit vorangestelltem Modulnamen zu verwenden.
Eine GUI würde ich immer als separates Modul implementieren, besonders wenn ich ein Tool mache, daß auch ein Kommandozeileninterface hat. Wobei bei Verwendung der Kommandozeilenversion das Modul für die GUI gar nicht geladen wird.

hier http://www.python.org/peps/pep-0008.html gibts einen Guideline für Pythoncode.


Gruß

Dookie

Verfasst: Sonntag 25. Juli 2004, 21:17
von RainBowBender
Dookie hat geschrieben:stimmt, der Import eines Moduls kann einen Rattenschwanz anderer Imports nach sich ziehen. Und Module werden immer nur beim ersten Import intialisiert.
Der Unterschied zwischen import Modul und from Modul import Funktion ist darin zu sehen, was in den Namespace des importierenden Moduls (__dict__) aufgenommen wird. Im ersten Fall ists ein Verweis auf das Modul, und im zweiten ein Verweis auf die Funktion. Bei kurzen Modulnamen oder aussageschwachen Funktionsnamen bevorzuge ich das Modul zu importieren und die verwendete Funktion qualifiziert, also mit vorangestelltem Modulnamen zu verwenden.
Eine GUI würde ich immer als separates Modul implementieren, besonders wenn ich ein Tool mache, daß auch ein Kommandozeileninterface hat. Wobei bei Verwendung der Kommandozeilenversion das Modul für die GUI gar nicht geladen wird.
Es gibt auch

Code: Alles auswählen

import Modul as Name
, dass ist praktisch wenn man Namespaceverschmutzung vermeiden will, aber nicht dauernd EinGanzLANGERverrueckterMODULname voranstellen will.

Code: Alles auswählen

from Modul import *
halte ich für schlechten Stil,

Code: Alles auswählen

from Modul import Funktion
brauche ich kaum.