Wildcard-Problem unter Windows

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.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Für simple Ersetzungen reicht ja im Grunde das schon aus:

Code: Alles auswählen

from glob import iglob
from sys import stdin

def replace(lines):
    for line in lines:
        pattern = line.rstrip()
        for path in iglob(pattern):
            yield path

def main():
    for path in replace(stdin):
        print(path)

if __name__ == '__main__':
    main()
Der zu ersetzende Inhalt kann hier z.B. über eine Pipe in die Standardeingabe des kleinen Programms geleitet werden. Ausgegeben werden dann die entsprechenden Ersetzungen.
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

Und der Fehler liegt wo genau?
https://docs.python.org/2/library/re.html#re.MULTILINE:

When specified, the pattern character '^' matches at the beginning of the string and at the beginning of each line (immediately following each newline); and the pattern character '$' matches at the end of the string and at the end of each line (immediately preceding each newline). By default, '^' matches only at the beginning of the string, and '$' only at the end of the string and immediately before the newline (if any) at the end of the string.

Ich finde die Erklärung verwirrend. Mir ist unklar, was String in diesem Kontext bedeuten soll. Wenn ich davon ausgehe, dass "Beta Gamma Delta" ein String ist, der zwischen den Strings "Alpha" und "Zeta" liegt, spielt es doch keine Rolle, ob das Pattern "^Beta" oder "Beta" ist. Oder anders: Woher soll mein Pattern wissen, in welchem der vielen möglichen Strings in meinem Text ich suche. Ich weiß schlichtweg nicht, welchen Sinn das Caret haben soll, wenn es nicht um den Anfang von Zeilen, sondern um den Anfang von Strings geht.

Und weil ich die Erklärung verwirrend finde, habe ich es auf regex.com getestet: http://regexr.com/3gqjc.

Die Flag "global" entspricht "--count 0". ("--count 0" ist die Default.)
Die Flag "multiline" entspricht "--pattern-multiline".

Aber bei einem gegebenen Text:

Code: Alles auswählen

Test Test
Test Test
Test Test
Test Test
erfasst

Code: Alles auswählen

subst --count 0 --pattern-multiline -p "^Test Test\nTest" -r "Match Match\nMatch" "Text.txt"
bzw.

Code: Alles auswählen

subst --count 0 --pattern-multiline -p "^Test Test\r\nTest" -r "Match Match\r\nMatch" "Text.txt"
das Muster nur einmal -- im Gegensatz zu dem Ergebnis auf regexr.com.

-l bedeutet, dass der Text Zeile für Zeile in den Speicher eingelesen wird. Ohne die Option -l wird der gesamte Text in den Speicher eingelesen. Mit -l kann also "^Test Test\nTest" nicht erfasst werden, weil der dritte String "Test" schon in der nächsten Zeile liegt, die subst.py noch nicht eingelesen hat. -l ist nicht das Gegenteil von --pattern-multiline, wie man erwarten könnte. Der Punkt ist einfach, dass es keine Kombination von Optionen gibt, mit der sich jedes Auftreten von "^Test Test\r\nTest" bzw "^Test Test\nTest" erfassen lässt.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

In der Regex-Sprache ist das so, weil der Standard halt der komplette Text ist. Mit Multiline sagst du, dass du einzelne Zeilen betrachten willst. Bei dem von dir verwendeten Programm ist jedoch die zeilenweise Betrachtung der Standard und Multiline meint, dass alle Zeilen in einem Rutsch gesehen werden sollen (d.h. Caret meint das erste Zeichen vom kompletten Text, anstatt von der jeweiligen Zeile). Du musst hier halt genau umgekehrt denken.

Ich hab übrigens meinen Eigenbau noch etwas erweitert:

Code: Alles auswählen

#!/usr/bin/env python
from __future__ import print_function
from glob import has_magic, iglob
from sys import argv, stderr, stdin, stdout
from textwrap import dedent

def replace(lines):
    """
    Replace lines containing wildcards with their real paths.

    Input can be any iterable (list, file object, ...)
    """
    for line in lines:
        pattern = line.rstrip()
        if not has_magic(pattern):
            # No wildcard -> No change
            yield pattern
        else:
            for path in iglob(pattern):
                yield path

def print_replaced(lines, output_stream=stdout):
    """
    Print replacements for given lines if they contain wildcards.
    Otherwise, print the original line.
    """
    for path in replace(lines):
        print(path, file=output_stream)

def main():
    """
    Print replacements for given lines using shell-like globbing.

    An additional argument defines the input's filename.
    If omitted, STDIN is used.

    Invokation:
    pathrepl [filename]

    Examples:
    pathrepl pathfile.txt
    pathfile.txt > pathrepl
    echo *.py | pathrepl
    """
    if len(argv) == 1:
        print_replaced(stdin)
    elif len(argv) == 2:
        try:
            with open(argv[1]) as stream:
                print_replaced(stream)
        except IOError as err:
            print('I/O Error:', err, file=stderr)
    else:
        print(dedent(main.__doc__), file=stderr)

if __name__ == '__main__':
    main()
Bringt dir das was für dein Vorhaben? In dem Fall könnte ich natürlich noch die Behandlung von Carets mit aufnehmen...
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

Du musst hier halt genau umgekehrt denken.
Aber es gibt ja keine Kombination von Optionen (inkl. des Verzichts auf bestimmte Optionen), mit denen subst jedes Auftreten des Musters "^Test Test\nTest" im Text erfassen kann. Umgekehrt zu denken, nützt also nichts. Und das bedeutet nichts anders, als das subst derzeit Regex nicht abdeckt. Da reguläre Ausdrücke aber auch ohne solche Bugs sehr komplex sein können, ist es mit Bugs ausgesprochen schwer durch reine Überlegung herauszufinden, ob der User selbst oder aber das Programm einen Fehler gemacht hat. Der beste Weg ist dann ein Test mit einem anderen Ersetzungsprogramm, das in Bezug auf die Ersetzungen bugfrei ist. UltraEdit verhält sich übrigens genauso wie das Ersetzen-Programm, das auf regexr.com verwendet wird.

Edited: Ein wesentlicher Unterschied zwischen Ultraedit bzw. dem auf regexr.com verwendeten Programm und subst ist, dass der User nur entscheiden kann, ob er jedes oder nur das erste Auftreten des Musters erfassen will. Subst kann hingegen mit "--count n" auch maximal n Ersetzungen durchführen.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Anscheinend bist du nicht daran interessiert, dass statt Anpassungen als Alternative einfach neuer Code geschrieben wird. Dann bin ich hier wohl raus. Ich finde den Code des gezeigten Projekts kompliziert und werde mich da sicher nicht ohne Not einarbeiten. Nichts für ungut.
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

Ergänzung zu meinem vorherigen Beitrag:
Bringt dir das was für dein Vorhaben? In dem Fall könnte ich natürlich noch die Behandlung von Carets mit aufnehmen...
Das kann ich nicht einschätzen, weil ich schlichtweg kein Python kann. Im Grunde ist Subst ja nicht mein Vorhaben, sondern ein Projekt des Entwicklers von Subst. Ich will das Programm natürlich nutzen. Deswegen sollte es bugfrei sein. Aber welche Features in das Programm eingebaut werden sollen, ist nicht meine Entscheidung. Mein Engagement auf Github beschränkt sich bezüglich Subst darauf, Bugs zu melden und dem Entwickler im Rahmen meiner Möglichkeiten zu helfen, sie zu beseitigen, also z. B. möglichst schnell zu reagieren, wenn er eine Änderung an Subst vorgenommen hat und ihm zu schreiben, ob sie unter Windows funktioniert. Dabei versuche ich, ihm nicht Probleme mit Subst zu melden, die nur deshalb entstehen, weil ich z. B. nicht verstanden habe, wie reguläre Ausdrücke funktionieren. Ich überlege also dreimal, bevor ich dem Entwickler ein neues Problem melde, weil ich ihm nicht die Zeit stehlen will. Die Zeit, die er sich für Erklärungen nimmt, ist Zeit, die nicht mehr für die Beseitigung von Bugs zur Verfügung steht.

In Bezug auf mein Vorhaben, Subst für die Ersetzungen in dekompilierten Fonts einzusetzen: Ich kann es bereits jetzt nutzen. Noch brauche ich das Caret nicht in meinen Mustern. Und Wildcards brauche ich derzeit auch nicht in den Befehlen von Subst, weil ich die Befehle innerhalb einer For-Schleife meines Batch-Scripts abarbeiten lasse. Aber ich wünsche mir halt eine Ersetzungssoftware, die bugfrei ist, um nicht später in Probleme zu rennen, wenn ich mir bereits sehr viel Arbeit mit der Erstellung der Muster gemacht habe.
Anscheinend bist du nicht daran interessiert, dass statt Anpassungen als Alternative einfach neuer Code geschrieben wird.
Da hätte ich gar nichts gegen. Nur kann ich eben kein Python. Deswegen hatte ich ursprünglich auch nicht die Absicht, die Bugs hier in diesem Thread zu diskutieren. Ich bin schlichtweg nicht kompetent genug, um in Bezug auf den Code mehr zur Beseitigung der Bugs beizutragen, als ich bisher beigetragen habe. Es ging vielmehr darum, auf Subst aufmerksam zu machen, und zwar in der Hoffnung, dass Python-Programmierer, die das Skript ebenfalls für nützlich halten, in das Projekt einsteigen. Außer mir gab es nur eine andere Person, die dem Entwickler ein Problem gemeldet hat. Und ich vermute, das liegt entweder daran, dass fast niemand Subst kennt oder dass die meisten keine Notwendigkeit darin sehen, ein Skript wie Subst zu programmieren, weil es für sie bessere Alternativen gibt.
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

Der Bug mit dem Caret ist jetzt anscheinend gefixt. Und Wildcards funktionieren jetzt halbwegs. Allerdings lässt Subst noch Ordner als Dateien durchgehen, was in einer Fehlermeldung endet.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ein wenig Inspiration hat mein Code wohl schon gebracht, wenn ich seine Antworten so lese. Wie gesagt, bei mir war's in einem Projekt mit anderer Thematik. Da hat sich diese "magische" Unterscheidung zwischen Auflösung der Namen vs Anzeige der Verzeichnisinhalte richtig angefühlt. Habe hierfür aber auch kein Feedback erhalten, sodass ich nicht wirklich einschätzen kann, ob mein Vorgehen hilfreich für andere User ist...
kyou hat geschrieben:Allerdings lässt Subst noch Ordner als Dateien durchgehen, was in einer Fehlermeldung endet.
Das kann man nur anhand des Namens leider nicht erkennen. Möglicherweise schafft der Einbau von os.path.isdir(filename) (dann mit angepasstem Verhalten) hier Abhilfe.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Ich habe den Projekt-Code auch nur überflogen. Ich schließe mich an, dass dieser komplizierter ist, als er sein müsste. So habe ich mich dem Kernproblem, dem Ersetzen von Text gewidmet und folgende Funktion (mit type-hints) zusammengeschraubt:

Code: Alles auswählen

def subst(pattern:str, replacement:Union[str, None]=None) -> Callable[[str], str]:
    """Returns a function which substitues and replaces a given string
    according to the given pattern and replacement-rule. """
    def func(string:str) -> str:
        match = re.compile(pattern).match(string)
        return (replacement.format(*match.groups())
                if (match and replacement)
                else (string if match else None))
    return func
Das ganze kann man wie folgt anwenden:[codebox=pycon file=Unbenannt.txt]>>> s = subst('^do#(\\d+)#([xyz])$', '{1} = {0}')
>>> s('do#34#x')
'x = 34'
>>> s('do#34#z')
'z = 34'
>>> s('do#34#q')
>>> type(s('do#34#q'))
<class 'NoneType'>
[/code]

Die Ausrede "ich kann kein Python" solltest du in eine Aussage "ich kann noch kein Python" ändern. Aus den Beispielen hier konnte ich eine Menge lernen.

Die Kenntnisse, welche Du Dir hier aneignen solltest sind:
  1. Funktionen und Funktionen "höherer Ordnung"
  2. tuple-unpacking
  3. conditional-expression (if-else-Ausdruck)
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

snafu hat geschrieben:Ein wenig Inspiration hat mein Code wohl schon gebracht, wenn ich seine Antworten so lese.
Warum auch nicht? Der Entwickler scheint ein sehr netter und für Vorschläge empfänglicher Mensch zu sein. Es hat nur keinen Sinn, dass ich als eine Art Vermittler und Übersetzer zwischen euch und dem Entwickler des Skripts auftrete. (Github ist ja für Gemeinschaftsprojekte ausgelegt.) Dafür spreche ich weder gut genug Englisch, noch sind meine Python-Kenntnisse dafür ausreichend. Das geht meiner Ansicht nach aber schon aus meinem Erstbeitrag hervor. Ich bat nicht darum, mir zu helfen, das Skript zu verbessern, sondern euch zu entscheiden, ob ihr selbst eine Verwendung dafür habt. Und falls ja, ob ihr euch bei dem Projekt auf Github engagieren wollt oder nicht. Und wenn ihr merkt, dass das Skript zwar prinzipiell nützlich ist, aber der Code so kompliziert, dass es sich nicht lohnt, ihn zu verbessern, sondern ein Skript mit einer vergleichbaren Funktionalität von Grund auf neu zu programmieren, dann würde ich mich freuen, wenn ihr das im Rahmen eigener Github-Projekte tätet. Das Bessere ist ja des Guten Feind. Insofern habe ich gewiss nichts gegen Alternativen, ganz im Gegenteil.
Antworten