lukeflo
Anmeldungsdatum: 15. Oktober 2011
Beiträge: Zähle...
|
Hallo Zusammen, im Rahmen einer beruflichen Weiterbildung befasse ich mich zurzeit mit Skripten und Data Science. Ubuntu/Linux nutze ich schon seit langem, aber mit dem Verfassen eigener Befehle/Skripte habe ich mich bisher noch nicht intensiv beschäftigt. Das soll sich nun ändern bzw. ist schon im Gange. Um meine alltäglich Arbeit zu erleichtern (und als Aufgabe fürs Studium) versuche ich mich aktuell an einem Skript, welches alle Datei- und Verzeichnisnamen im aktuellen Verzeichnis dahingehend vereinheitlicht, dass es Leerzeichen durch Unterstriche und Groß- durch Kleinbuchstaben ersetzt: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | #!/bin/bash
files=(./*[A-ZÄÖÜ" "]*)
if [ -f "$files" ]; then
for Datei in ./*[A-ZÄÖÜ]*; do
mv "$Datei" "${Datei,,}"
done
for Datei in ./*[" "]*; do
mv "$Datei" "${Datei// /_}"
done
elif [ -d "$files" ]; then
for Datei in ./*[A-ZÄÖÜ]*; do
mv "$Datei" "${Datei,,}"
done
for Datei in ./*[" "]*; do
mv "$Datei" "${Datei// /_}"
done
else
echo "No file or directory found to rename"
fi
|
Das Skript tut, was es soll; Soweit alles gut. Wenn nun aber alle Datei- und Verzeichnisnamen im aktuellen Verzeichnis bereits den gewünschten Zielbedingungen entsprechen, erscheint nicht der unter else formulierte Text, sondern folgende Meldungen: mv: Aufruf von stat für './*[A-ZÄÖÜ]*' nicht möglich: Datei oder Verzeichnis nicht gefunden
mv: Aufruf von stat für './*[ ]*' nicht möglich: Datei oder Verzeichnis nicht gefunden Dies tut der Funktionsweise keinen Abbruch, ich finde es aber nicht schön. Ich nehme an, es könnte etwas mit der Definition und Verwendung der Variablen zu tun haben, bin da aber nicht sicher. Eventuell ist das Skript auch etwas zu kompliziert und lässt sich vereinfachen. Das gehört aber, denke ich, zum Lernprozess 😉 Danke schon einmal für Tipps und Hinweise Gruß
Florian
|
juribel
Anmeldungsdatum: 20. April 2014
Beiträge: 856
|
Moin, so ein Skript würde ich auf keinen Fall direkt auf die Dateien loslassen. Sinnvoller wäre es, mit einem Skript erst einmal die Dateilisten in eine Datei schreiben zu lassen (im einfachsten Fall mit ls -1). Schon als Dokumentation, was das Programm überhaupt alles so findet. Der Rest wäre normale Dateiverarbeitung, nämlich die Datei wieder einlesen und Zeile für Zeile unpassende Einträge verwerfen und für die passenden einen passenden mv-Befehl daraus machen. Auch diese Datei kann man gefahrlos kontrollieren und ggf. das Skript verbessern. Und am Ende dann dieses generierte Skript laufen lassen. So kannst du vorher genauestens kontrollieren, was das Programm machen wird. Das sieht zwar sehr umständich aus in dem Sinne, dass da viel mehr Code geschrieben werden muss und dass temporäre Dateien erzeugt werden usw. Bei mir hat sich eine solche Vorgehensweise jedenfalls in kleinen und auch in sehr grossen Projekten absolut bewährt. Und das Programm ist modular, erweiterbar, und Teile können leichtens ausgetauscht werden, da es zwischen den einzelnen Schritten klare Schnittstellen gibt. Und weil alles in Dateien geschrieben wird, ist es auch gut kontrollierbar, und der Lerneffekt ist auch eher da. Und wenn es nicht ein Shellskript sein muss, könntest du alles z. B. in perl erledigen, das ist für so etwas prädestiniert. Vielleicht hilft das. Viele freundliche Grüsse, juribel
|
Doc_Symbiosis
Anmeldungsdatum: 11. Oktober 2006
Beiträge: 4212
|
Und soll das unbedingt ein Skript sein? Denn eigentlich gibt es dafür den Befehl rename.
|
juribel
Anmeldungsdatum: 20. April 2014
Beiträge: 856
|
Guter Tipp, danke! Rename kannte ich noch nicht. Macht wahrscheinlich genau das Gewünschte. Aber dem TS ging es dabei auch darum, in die Programmierung von Skripten zu einzusteigen, und da wäre so etwas eine schöne Fingerübung. Viele freundliche Grüsse, juribel
|
lukeflo
(Themenstarter)
Anmeldungsdatum: 15. Oktober 2011
Beiträge: 54
|
Danke euch, juribel und Doc, erst einmal für die Antworten und Hinweise. @juribel: Bisher habe ich das ursprüngliche Skript auch nur in einem Testverzeichnis laufen lassen, damit nichts schief geht. Der Tipp mit der Dokumentation ist sehr interessant, aber womöglich etwas zu aufwendig in meiner aktuellen zeitlichen Lage. @Doc_Symbiosis: Rename kannte ich bisher auch nicht. Danke für den Tipp. Hatte halt versucht, es möglichst ohne tiefergehende Internetrecherchen hinzukriegen 😉. Und ja, es muss ein Bash-Skript sein; zumindest für die Aufgabe im Studium. Daher habe ich das Skript nun folgendermaßen abgeändert: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | #!/bin/bash
files=(./*[A-ZÄÖÜ" "]*)
if [ -f "$files" ]; then
rename 'y/A-Z/a-z/' *
rename 's/\s/_/g' *
rename 's/Ä|ä/ae/g' *
rename 's/Ö|ö/oe/g' *
rename 's/Ü|ü/ue/g' *
elif [ -d "$files" ]; then
rename 'y/A-Z/a-z/' *
rename 's/\s/_/g' *
rename 's/Ä|ä/ae/g' *
rename 's/Ö|ö/oe/g' *
rename 's/Ü|ü/ue/g' *
else
echo "No file or directory found to rename"
fi
|
Es funktioniert wirklich ganz wunderbar!! Auch mit der Ausgabe des else-echo-Befehls, falls keine passenden Dateien vorhanden sind.
Womöglich ließen sich die regulären Ausdrücke in der if-Verzweigung auch noch zusammenfassen o.Ä. Aber erst einmal ist es ganz wunderbar. Nun überlege ich, ob ich noch einige Ersetzungen als wählbare Option hinzunehme. Ich muss halt immer noch einen drauf setzen... Trotzdem wäre ich weiterhin an Lösungsvorschlägen interessiert, wie sich dies auch mit meinem ersten Ansatz, dem nutzen von for-Loops und mv-Befehlen, bewerkstelligen ließe. Ist aber jetzt wirklich nur noch interessegeleitet für den Lerneffekt. Gruß
Florian
|
Doc_Symbiosis
Anmeldungsdatum: 11. Oktober 2006
Beiträge: 4212
|
Hm, vielleicht könntest Du eine Option -n hinzunehmen, die Du einfach direkt an das rename weiterreichen kannst. Damit wird dann nur ein "dryrun" ausgeführt, also nur eine Simulation... Und die UNterscheidung nach Datei und Verzeichnis ist noch überflüssig, da Du bei beiden ja das gleiche ausführst. Da kannst Du auch einfach mit -e die Existenz checken, egal, ob Datei oder Verzeichnis. Und Du könntest noch prüfen, ob das Kommando rename wirklich verfügbar ist (obwohl es eigentlich auf den allermeisten Linux-Systemen vorhanden ist) , etwas so:
if ! command -v rename &> /dev/null; then
echo "FEHLER: Das Kommando rename gibt es nicht. Breche das Skript ab!";
exit 2;
fi
|
lukeflo
(Themenstarter)
Anmeldungsdatum: 15. Oktober 2011
Beiträge: 54
|
Moin, das mit der -n Option habe ich schon getestet. Das passt so wunderbar!
Das mit der Option -e ist natürlich deutlich einfacher, danke für den Tipp. Die ganzen Möglichkeiten muss man aber auch erst mal auf dem Schirm haben... ☺ Werde jetzt mal testen, wie sich noch optionale Ersetzungen durch das Erstellen von Optionen/Parametern Hinzufügen lassen. Das müsste ja über die Variablen $1 - $9 eingerichtet werden können. Gruß
Florian
|
lukeflo
(Themenstarter)
Anmeldungsdatum: 15. Oktober 2011
Beiträge: 54
|
Moin, so, nun bin ich doch noch auf eine Herausforderung gestoßen. Die Belegung der Variablen files über den Befehl files=(./*[A-ZÄÖÜ" "]*) funktioniert natürlich nicht, da dann immer nur ein Wert eingelesen wird. Brauche also ein Array.
Die Schleife
| for file in ./*[A-ZÄÖÜäöü" "]*;
do files+=("$file")
done
|
löst das ganz wunderbar. In meinem Testordner mit Dateinamen, die die Kriterien erfüllen, und welchen, die es nicht tun, werden nur die Namen derjenigen in die Variable gelesen, die passen (Namen sind nicht sehr einfallsreich 😉 ):
| $ ls
aepop_op.txt Apo ÄpÜ 'Ärsche und Gänse' aso äsö AsoP.txt äsop.txt eso eso_ose
$ for file in ./*[A-ZÄÖÜäöü" "]*; do files+=("$file"); done
$ echo "${files[@]}"
./Apo ./ÄpÜ ./Ärsche und Gänse ./äsö ./AsoP.txt ./äsop.txt
|
Nun wollte ich dies testweise in meinem Skript nutzen, indem ich eine if-Verzweigung mit exit-Anweisung erstellt habe, falls die Variable/Array leer ist. Im positiven Fall soll das Array einfach dargestellt werden:
1
2
3
4
5
6
7
8
9
10
11
12 | #!/bin/bash
for file in ./*[A-ZÄÖÜäöü" "]*;
do files+=("$file")
done
if [ -z "$files" ]; then
echo "No file or directory found to rename"
exit 0
elif [ -n "$files" ]; then
echo "${files[@]}"
fi
|
Nun klappt der positive Fall ganz wunderbar. Er gibt mir, wie oben schon gezeigt, die einzelnen Elemente des Arrays aus. Ist das Array aber leer, da nur Dateinamen existieren, die nicht mehr geändert werden sollen, die also nicht von der for-Schleife für die Erstellung des Arrays verarbeitet werden, erscheint nicht der mit echo angegebene Satz und das Skript wird abgebrochen. Vielmehr gibt er mir dann als Ausgabe die Listendefinition der Schleife wieder (anderer Testordner):
| $ ls
poly.txt seso.txt
$ testskript # Name des Skripts mit o.g. Inhalt
./*[A-ZÄÖÜäöü ]*
|
Ich nehme an, dass ich irgendwo einen Anfängerfehler o.Ä. gemacht habe. Habe schon versucht, die Optionen in der Testdefinition [ -z "$files" ] auszutauschen und bei exit verschiedene Werte anzugeben. Klappt aber nicht. Weiß hier jemand zufällig, wo mein Fehler liegt? An der Funktion des Skripts ändert es nichts. Im fertigen soll der echo "${files[@]}" Part natürlich durch die Rename-Befehle ersetzt werden. Es funktioniert auch so, aber ich möchte ja was lernen und dafür ein Skript mit schönen Zusatzoptionen usw. schreiben. Danke schon mal! Gruß
Florian
|
Doc_Symbiosis
Anmeldungsdatum: 11. Oktober 2006
Beiträge: 4212
|
Verwende zum Check, ob ein Array leer ist, am Besten dieses hier:
if [ ${#files[@]} -eq 0 ]; then
echo "No file or directory found to rename"
exit 0
elif
... Aber eigentlich brauchst Du ja auch gar nicht erst einen Array aufzubauen, sondern kannst direkt in der for-Schleife das umbenennen durchführen...
|
lukeflo
(Themenstarter)
Anmeldungsdatum: 15. Oktober 2011
Beiträge: 54
|
Moin Doc, vielen Dank. Werde das Zuhaus direkt mal testen (sitze im Büro am Windows-PC ☺ ). Klar, ich brauche das nicht für die reine Funktionalität des Skripts. Aber einerseits möchte ich halt auch was lernen, andererseits handelt es sich um eine Aufgabe für mein Aufbaustudium und ich würde gerne ein paar Extras einbauen. Z.B., wie oben schon mal geschrieben, möchte ich meinem Skript noch Optionen dazubauen. Ob das immer praktikabel ist, sei mal dahingestellt, aber es geht halt vor allem ums Lernen. Und gelernt habe ich dadurch tatsächlich schon einiges; auch dank eurer Hilfe. Gruß
Florian PS: Doc_Symbiosis schrieb:
if [ ${#files[@]} -eq 0 ]; then
echo "No file or directory found to rename"
exit 0
elif
...
Was hat das Hashtag vor dem Variablennamen hier für einen Sinn?
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 10978
|
Damit kommt man an die Zahl der Elemente im Array - vgl. https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Arrays: ${#name[subscript]} expands to the length of ${name[subscript]} . If subscript is @ or * , the expansion is the number of elements in the array.
|
lukeflo
(Themenstarter)
Anmeldungsdatum: 15. Oktober 2011
Beiträge: 54
|
Alles klar, danke. Das mach dann Sinn mit dem -eq 0. Also quasi Anzahl der Element im Array equals 0.
|
lukeflo
(Themenstarter)
Anmeldungsdatum: 15. Oktober 2011
Beiträge: 54
|
Moin, leider klappt es nicht. Bzw. gehe ich davon aus, dass die Bedingung, die Doc_Symbiosis angegeben hat zwar funktioniert, aber eben nur wenn das Array leer ist. Wenn ich aber die oben angegebene for-loop in einem Verzeichnis ausführe, das nur Dateien enthält, die schon ausschließlich aus Kleinbuchstaben ohne Umlaute und Leerzeichen bestehen, schreibt er mir anstatt nichts die Definition der Dateinamen ins Array:
$ ls
poly.txt seso.txt
$ for file in ./*[A-ZÄÖÜäöü" "]*; do files+=("$file"); done
$ echo "${files[@]}"
./*[A-ZÄÖÜäöü" "]
Damit ist das Array dann natürlich nicht leer und besagte Bedingung (-eq 0) greift nicht. Hat jemand eine Ahnung, warum er bei dem for-loop bei Verzeichnissen ohne fragliche Dateinamen dann die Definition in das Array schreibt und wie sich das umgehen lässt?
Bei Verzeichnissen, in denen Dateinamen mit Zeichen aus der Definiton vorkommen, schreibt er ja nur die Dateinamen in das Array und nicht zusätzlich noch die Definiton, s. dieser Post:
lukeflo schrieb: In meinem Testordner mit Dateinamen, die die Kriterien erfüllen, und welchen, die es nicht tun, werden nur die Namen derjenigen in die Variable gelesen, die passen (Namen sind nicht sehr einfallsreich 😉 ):
| $ ls
aepop_op.txt Apo ÄpÜ 'Ärsche und Gänse' aso äsö AsoP.txt äsop.txt eso eso_ose
$ for file in ./*[A-ZÄÖÜäöü" "]*; do files+=("$file"); done
$ echo "${files[@]}"
./Apo ./ÄpÜ ./Ärsche und Gänse ./äsö ./AsoP.txt ./äsop.txt
|
Da wäre ich schon aus Verständnisgründen für die Hilfe sehr dankbar. Ich konnte auch nach einigem Rumprobieren keine Erklärung finden. Gruß Florian
|
lukeflo
(Themenstarter)
Anmeldungsdatum: 15. Oktober 2011
Beiträge: 54
|
Hab es selber hinbekommen; und zwar ohne eine for-loop. Einfach mit den Befehlen mapfile und find. Letzterer dann mit den entsprechenden Suchkriterien:
| mapfile -t files < <( find -name "*[A-ZÄÖÜäöü' ']*" )
if [ ${#files[@]} -eq 0 ]; then
echo "No file or directory found to rename"
exit 0
elif [ -n "$files" ]; then
echo "${files[@]}"
fi
|
Funktioniert wunderbar. Auch mit dem exit usw. Danke für eure Hilfe. Ich halte diesen Thread mal offen, falls weitere Fragen auftauchen. Gruß
Florian
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 10978
|
lukeflo schrieb: Wenn ich aber die oben angegebene for-loop in einem Verzeichnis ausführe, das nur Dateien enthält, die schon ausschließlich aus Kleinbuchstaben ohne Umlaute und Leerzeichen bestehen, schreibt er mir anstatt nichts die Definition der Dateinamen ins Array:
$ ls
poly.txt seso.txt
$ for file in ./*[A-ZÄÖÜäöü" "]*; do files+=("$file"); done
$ echo "${files[@]}"
./*[A-ZÄÖÜäöü" "]
Damit ist das Array dann natürlich nicht leer und besagte Bedingung (-eq 0) greift nicht.
Hat jemand eine Ahnung, warum er bei dem for-loop bei Verzeichnissen ohne fragliche Dateinamen dann die Definition in das Array schreibt und wie sich das umgehen lässt?
Man kann die Bash-Option nullglob setzen, damit das nicht passiert: https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html nullglob If set, Bash allows filename patterns which match no files to expand to a null string, rather than themselves.
|