Frage zur Bash und Mustererkennung
-
Wenn ich in einem Verzeichnis 2 Dateien habe, z.B.:
Test_A12.txt
TEST_A12.txtUnd nun alle Dateien ausgeben möchte, die zwar mit einem Großbuchstaben anfangen, aber dann von Kleinbuchstaben gefolgt werden, was muß ich dann in der Konsole eingeben.
Dieser Befehl, wie ich mir ursprünglich dachte, funktioniert leider nicht, da die Bash das [A-Z] so interpretiert, daß auch mehrere Zeichen mit Großbuchstaben folgen können, aber wie gebe ich nun an, daß nur ein einzelnen Zeichen aus Großbuchstaben bestehen soll?
ls [A-Z][a-z]**
-
Klammer setzen?
-
bash: Syntaxfehler beim unerwarteten Wort `('
-
Das ist eine der Stellen, an denen bash das principle of least surprise massiv verletzt. Schau dir diese bash-session an:
$ ls test Test TEst TEST $ ls [A-Z]* test Test TEst TEST $ export LC_COLLATE=C $ ls [A-Z]* TEST TEst TestDas Pattern [A-Z] wird abhängig von LC_COLLATE interpretiert. Standardmäßig wird LC_COLLATE bei dir vermutlich leer sein, womit das auf deutsch oder englisch defaultet, je nachdem auf welche Sprache du dein System gestellt hast. Dann ist [A-C] aber nicht äquivalent zu [ABC], sondern zu [AbBcC], weil das die Wörterbuch-Reihenfolge ist.
Dieses Verhalten ist sehr überraschend, aber zugunsten von Bash muss ich dagegenhalten: Es ist präzise dokumentiert, sogar mit Beispielen: http://www.gnu.org/software/bash/manual/bashref.html#Pattern-Matching
Pattern matching in bash ist aber allgemein sehr primitiv. Die Forderung "das erste Zeichen ein Großbuchstabe, danach nur noch Kleinbuchstaben" kannst du damit nicht umsetzen (ohne die shell-option extglob, siehe unten). Im Zweifelsfall geht das aber alles mit find und der Kommandozeilenoption -regex.
Runde Klammern () sind in bash übrigens sowas wie Schlüsselwörter, die haben praktisch immer eine Spezialbedeutung. Mit pattern matching haben die aber (normalerweise) nichts zu tun. Die runden Klammern braucht man z.B., um Funktionen zu deklarieren, subshells zu öffnen und ähnliches, was mit Prozessen und Funktionen zu tun hat. Bei neuen bash-Versionen kannst du im Prinzip die shelloption "extglob" setzen, dann hast du erweitertes globbing zur Verfügung. Ob das aber so portabel ist, ist zweifelhaft. Ich würde wahrscheinlich eher find mit -regex nehmen, da ist auch die Fehlerbehandlung falls keine Dateien gefunden wurden einfacher (kein redirect von stderr nötig).
Achso, andererseits ist natürlich die Frage, was du mit dem "ls" vorhast. Wenn du über die Dateien iterieren möchtest, dann kann sich die "extglob"-Option auf jeden Fall lohnen:
$ for file in [A-Z]*([a-z]); do echo "$file"; done Test $Und noch ein edit: Mit ls * gibst du nicht alle Dateinamen aus. Geh mal in dein home-Verzeichnis und mach ls *, dann siehst du, was ich meine. Wenn du Dateinamen ausgeben möchtest, kannst du zum Beispiel eine for-schleife wie die von oben nehmen, echo * (statt ls
oder find. Von echo * würde ich aber abraten, das ist nicht sonderlich robust.
-
Ich würde von solchem Bashgerätsel Abstand halten. Habe hier gerade das erste mal von LC_COLLATE gehört aber noch nie vermisst.
Geht erstmal viel einfacher: ls|grep '[A-Z][a-z]*'
Wenn nicht interaktiv, dann lieber, wie schon genannt, find -regex.Christoph schrieb:
Achso, andererseits ist natürlich die Frage, was du mit dem "ls" vorhast. Wenn du über die Dateien iterieren möchtest, dann kann sich die "extglob"-Option auf jeden Fall lohnen:
$ for file in [A-Z]*([a-z]); do echo "$file"; donestatt for-schleife ist meistens xargs dann auch bequemer. Obiges erweitert dann:
ls|grep '[A-Z][a-z]*'|xargs rm -vi
(gilt aber auch nur interaktiv. Am besten sonst find -print0 | xargs -0)
-
DrGreenthumb schrieb:
ls|grep '[A-Z][a-z]*'|xargs rm -vi
(gilt aber auch nur interaktiv. Am besten sonst find -print0 | xargs -0)
Ich wollt's gerade sagen.

ls gefolgt von xargs kann zu ganz hässlichen Problemen führen, weil schon ganz simple Sachen wie Dateien mit Leerzeichen im Namen nicht richtig behandelt werden.find -print0 | xargs -0 kann man übrigens ganz oft ersetzen durch find -execdir. Falls man die Dateien nur löschen möchte, gibt es auch find -delete, das dürfte noch effizienter sein, weil zum Löschen kein Prozess gestartet werden muss.