|
UlfZibis
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
Hallo, Ich habe folgende Verzeichnisstruktur: Ablage
Album1
123.jpg
01-titel1.flac
02-titel2.flac
Interpret
Album2
123.jpg
01 titel1.flac
02 titel2.flac
Album3
123.png
01 titel1.mp3
02 titel2.mp3
Album4 # leer
Mit einer for-Schleife möchte ich die jeweils erste *.flac-Datei bearbeiten. Der Einfachheit halber nehme ich in folgendem Code ls -l als Platzhalter für eine kompliziertere Operation. Die Zeile mit echo dient nur dem Debugging. :
find Ablage -mindepth 1 -type d -exec bash -c \
'for dir; do \
ls -ld "$dir"; \
for file in `ls -1 "$dir"`; do \
echo "$file"; \
if [[ "$dir/$file" = *.flac ]]; then \
ls -l "$dir/$file"; \
break; \
fi; \
done; \
done' \
-- {} + Ergebnis: drwxrwxr-x 1 ich ich 0 Aug 19 00:07 Ablage/Album1
01-titel1.flac
-rw-rw-r-- 1 ich ich 0 Aug 19 00:06 Ablage/Album1/01-titel1.flac
drwxrwxr-x 1 ich ich 0 Aug 18 23:55 Ablage/Album4
drwxrwxr-x 1 ich ich 0 Aug 18 23:55 Ablage/Interpret
Album2
Album3
drwxrwxr-x 1 ich ich 0 Aug 19 00:02 Ablage/Interpret/Album2
01
titel1.flac
ls: Zugriff auf 'Ablage/Interpret/Album2/titel1.flac' nicht möglich: Datei oder Verzeichnis nicht gefunden
drwxrwxr-x 1 ich ich 0 Aug 19 00:03 Ablage/Interpret/Album3
01
titel1.mp3
03
titel2.mp3
123.png
Wie man sieht werden Dateien mit Leerzeichen in 2 aufgespalten. Wie kann ich das verhindern? Wenn ich for file in "$dir"; verwende, erhalte ich nur das Verzeichnis selbst. Wenn ich for file in "$dir"/?*; verwende, bekomme ich eine Fehlermeldung mit dem leeren Verzeichnis.
|
|
micneu
Anmeldungsdatum: 19. Januar 2021
Beiträge: 845
|
Wenn du Bash schon länger machst sollte bekannt sein das du die variable IFS entsprechend anpassen musst, einfach mal nach suchen in deiner favorisierten Suchmaschine
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
micneu schrieb: Wenn du Bash schon länger machst sollte bekannt sein das du die variable IFS entsprechend anpassen musst, einfach mal nach suchen in deiner favorisierten Suchmaschine
Nicht wirklich, ich habe eher Erfahrung mit "richtigen" Programmiersprachen. Um das gruselige Bash drücke ich mich, so gut ich kann. Ich kann mich aber dunkel erinnern, dass da mal was mit IFS war. Oje, das wird ja immer komplizierter mit der for-Schleife. Gibt's da nicht noch eine andere Lösung, ohne dass ich IFS verbiegen muss? Mit (hier dann verschachteltem) find gibt es eine einfache Lösung: find Ablage -mindepth 1 -type d -exec bash -c \
'for dir; do \
ls -ld "$dir"; \
find $dir/01*.flac -exec ls -l \{\} \; 2> /dev/null ; \
done' \
-- {} +
Das ist laut Wiki-Team aber ein Missbrauch von find. Genau deshalb suche ich eine funktionierende und vor allem unkomplizierte Lösung ohne find für die innere Schleife. Die soll es laut kB ja geben.
|
|
micneu
Anmeldungsdatum: 19. Januar 2021
Beiträge: 845
|
Warum fragst du nicht mal die KI, ich habe das mal für dich gemacht
#!/bin/bash
# Basisverzeichnis
base_dir="Ablage"
# Variable zur Speicherung des zuletzt bearbeiteten Verzeichnisses
current_dir=""
# Rekursive Suche nach *.flac Dateien, null-terminiert für sichere Handhabung von Leerzeichen
find "$base_dir" -type f -name '*.flac' -print0 | \
while IFS= read -r -d '' file; do
dir=$(dirname "$file")
# Prüfen, ob ein neues Verzeichnis erreicht wurde
if [[ "$current_dir" != "$dir" ]]; then
current_dir="$dir"
echo "Erste flac Datei in $dir:"
# Hier Beispiel-Befehl, anstelle von ls -l kann deine komplexe Operation stehen
ls -l "$file"
fi
done
|
|
shiro
Supporter
Anmeldungsdatum: 20. Juli 2020
Beiträge: 1303
|
Wenn du nur die erste .flac Datei verarbeiten willst, warum machst du dann nicht folgendes?
$ # Erzeuge Spielwiese
$ mkdir -p Ablage/{Album{1,4},Interpret} Ablage/Interpret/Album{2..3}
$ touch Ablage/Album1/{123.jpg,01-titel1.flac,02-titel2.flac}
$ touch Ablage/Interpret/Album{2,3}/{123.jpg,01\ titel1.flac,02\ titel2.flac}
$
$ # Bearbeite (echo) erste .flac Datei in den unterliegenden Ordnern
$ find ./ -name "*.flac" | sort | while read f; do if [ "$d" != "${f%/*}" ]; then echo $f; d="${f%/*}"; fi; done
./Ablage/Album1/01-titel1.flac
./Ablage/Interpret/Album2/01 titel1.flac
./Ablage/Interpret/Album3/01 titel1.flac
$
$ # Listing der Spielwiese
$ find ./ | sort
./
./Ablage
./Ablage/Album1
./Ablage/Album1/01-titel1.flac
./Ablage/Album1/02-titel2.flac
./Ablage/Album1/123.jpg
./Ablage/Album4
./Ablage/Interpret
./Ablage/Interpret/Album2
./Ablage/Interpret/Album2/01 titel1.flac
./Ablage/Interpret/Album2/02 titel2.flac
./Ablage/Interpret/Album2/123.jpg
./Ablage/Interpret/Album3
./Ablage/Interpret/Album3/01 titel1.flac
./Ablage/Interpret/Album3/02 titel2.flac
./Ablage/Interpret/Album3/123.jpg
$
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13242
|
micneu schrieb: Wenn du Bash schon länger machst sollte bekannt sein das du die variable IFS entsprechend anpassen musst, einfach mal nach suchen in deiner favorisierten Suchmaschine
Nicht unbedingt. Man sollte auf jeden Fall auch nicht die Ausgabe von ls nutzen, um Listen von Dateien zu bearbeiten (siehe Links auf meiner Nutzerseite). Ich würde vermutlich so etwas machen (ungetestet): 1
2
3
4
5
6
7
8
9
10
11
12
13 | unset dir
for flac in Ablage/*.flac Ablage/*/*.flac Ablage/*/*/*.flac
if [ -f "$flac" ]; then
d="$(dirname "$flac")"
if [ "$dir" != "$d" ]; then
# first one!
dir="$d"
echo "Processing: $flac ..."
fi
fi
done
|
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4735
|
Noch eine Variante:
| while IFS= read -r -d $'\0' directory; do
for file in "$directory"/*.flac; do
[[ -f $file ]] && echo "$file" # Do the processing here.
break
done
done < <(find Ablage -mindepth 1 -type d -print0)
|
|
|
kB
Supporter, Wikiteam
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 9837
|
UlfZibis schrieb: […] Verzeichnisstruktur […]
Mit einer for-Schleife möchte ich […]
Da das oberste Verzeichnis weitere Ordner enthält, die dann rekursiv auch durchsucht werden müssen, lässt sich die Aufgabe alleine mit einer Schleife nicht lösen. Man kann sie einfach lösen mit find ganz ohne Shell-Konstrukte, da find für das rekursive Durchsuchen von Verzeichnisbäumen erfunden wurde:
find . -type d -execdir find {} -maxdepth 1 -type f -name '*.flac' -print -quit \; Diese Lösung hat allerdings den Makel, dass das -print von find manche Sonderzeichen in Dateinamen besonders behandelt (z.B. wird ein ungepaartes ' durch ein ? ersetzt), und leider sind solche kranken Dateinamen bei Musiktiteln anzutreffen. Man kann sie auch lösen ganz ohne find nur mit der Shell; das macht man am besten durch Definition einer rekursiven Funktion (also eine Funktion, die sich selbst aufruft). Das ist allerdings weder etwas für die Schnelle noch etwas für Anfänger und daher verzichte ich hier auf weitere Erläuterungen dieses Weges. Mit anderen Lösungen, die sowohl find als auch Shell-Schnipsel verwenden, hast Du vermutlich selber mehr Erfahrungen.
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4735
|
@kb find garantiert keine Sortierung der Ergebnisse. Da hatte ich den Ausgangsbeitrag anders verstanden. Solche ”kranken” Dateinamen sind nicht wirklich selten, weil das Apostroph in Titeln/Namen halt nicht selten ist:
| $ locate "'" | wc -l
7672
$ locate "'" | egrep -iv '\.(mp3|ogg|flac|wav|mid|sid)$' | wc -l
4939
|
Wie man sieht, ist das auch nicht auf Musikdateien beschränkt. Da sind bei mir auch ein Haufen PDFs mit Zeitschriften (64'er) oder Handbüchern „… (Programmer|User)'s (Manual|Guide)“ für alle mögliche Hard- und Software dabei.
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
shiro schrieb: ... warum machst du dann nicht folgendes?
Wenn ich dafür eine Antwort gehabt hätte, hätte ich den Thread nicht gestartet. 💡 $ # Erzeuge Spielwiese
Nicht ganz meine Vorgabe, denn manche Alben enthalten auch nur MP3-Dateien, hier im Beispiel Album3. $ find ./ -name "*.flac" | sort | while read f; do if [ "$d" != "${f%/*}" ]; then echo $f; d="${f%/*}"; fi; done
Sehr eleganter knapper Einzeiler, ganz nach meinem Geschmack, danke. 👍 rklm schrieb: | for flac in Ablage/*.flac Ablage/*/*.flac Ablage/*/*/*.flac
|
Auch sehr schick, der Trick. Marc_BlackJack_Rintsch schrieb: | while IFS= read -r -d $'\0' directory; do
for file in "$directory"/*.flac; do
[[ -f $file ]] && echo "$file" # Do the processing here.
break
done
done < <(find Ablage -mindepth 1 -type d -print0)
|
Auch kurz und knapp, sehr fein. 👍 Der Fairness halber müsste da aber noch die Zeile ls -ld "$directory"; rein. Warum manipulierst Du hier IFS zu ""? Dein Code funktioniert auch da-ohne. Der Code funktioniert auch ohne -print0 wenn man -d $'\n' nimmt. Folgende Variante ohne for-Schleifen dürfte aber dennoch die kürzeste sein: find Ablage -mindepth 1 -type d | while read dir; \
do find $dir/01*.flac -print -exec ls -l {} \; 2> /dev/null ; done
Deshalb finde ich es "schade", dass meine Beispiele hier (Zeile 497-504) vom Wiki-Team diskreditiert wurden, denn find ... -exec sh -c `...` \; ist IMHO die kürzeste for-Schleife für beliebige Bash-Kommandos, die man sich vorstellen kann.
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
kB schrieb: Da das oberste Verzeichnis weitere Ordner enthält, die dann rekursiv auch durchsucht werden müssen, lässt sich die Aufgabe alleine mit einer Schleife nicht lösen.
"Quod erat demonstrandum !" Das sagt der härteste Verfechter von (sinngemäß): "Man soll find nicht für Schleifen missbrauchen, da for-Schleifen klarer, unkomplizierter, einfacher und kürzer sind." find . -type d -execdir find {} -maxdepth 1 -type f -name '*.flac' -print -quit \;
Wo kann ich da die eingangs erwähnte "kompliziertere Operation" unterbringen? ... liefert folgendes Ergebnis: ./Album1/01-titel1.flac
./Album2/01 titel1.flac ich benötige aber: Ablage/Album1/01-titel1.flac
Ablage/Interpret/Album2/01 titel1.flac
Diese Lösung hat allerdings den Makel, dass das -print von find manche Sonderzeichen in Dateinamen besonders behandelt (z.B. wird ein ungepaartes ' durch ein ? ersetzt), und leider sind solche kranken Dateinamen bei Musiktiteln anzutreffen.
Danke für den wichtigen Hinweis. Man kann sie auch lösen ganz ohne find nur mit der Shell; das macht man am besten durch Definition einer rekursiven Funktion (also eine Funktion, die sich selbst aufruft). Das ist allerdings weder etwas für die Schnelle noch etwas für Anfänger ...
Sieh an, also von wegen "klarer, unkomplizierter, einfacher und kürzer". 👿
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4735
|
@UlfZibis Stimmt, das IFS ist nicht nötig. Aber -print0 weglassen macht natürlich den Unterschied, dass dann keine Zeilenumbrüche in Verzeichnisnamen vorkommen dürfen. Ich gehöre zu den Leuten die nicht Dateinamen krank finden, die ganz normale erlaubte Zeichen enthalten, sondern Programme die mit so etwas einfachem nicht klar kommen. 😈 Ist es denn eigentlich egal welche *.flac-Datei aus dem jeweiligen Verzeichnis genommen wird? Nur mal so ein Beispiellauf: | $ find tmp/Ablage/ -name '*.flac'
tmp/Ablage/Interpret/Album2/01 titel1.flac
tmp/Ablage/Interpret/Album2/02 titel2.flac
tmp/Ablage/Album1/02-titel2.flac
tmp/Ablage/Album1/01-titel1.flac
|
Man achte auf die Reihenfolge der Dateien. Einige der Lösungen hier würden 02-titel2.flac statt 01-titel1.flac aus Ablage/Album1/ verarbeiten! Die Reihenfolge hängt davon ab in welcher Reihenfolge der Dateisystemtreiber die Namen liefert. Da wird bei find nichts sortiert wie bei Shell globbing.
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
Marc_BlackJack_Rintsch schrieb: Aber -print0 weglassen macht natürlich den Unterschied, dass dann keine Zeilenumbrüche in Verzeichnisnamen vorkommen dürfen.
Ja klar, ich bin mal optimistisch, dass sowas in meinen Dateien nicht vorkommt.
Ist es denn eigentlich egal welche *.flac-Datei aus dem jeweiligen Verzeichnis genommen wird?
Ja das ist im Prinzip egal. Ich will halt von jedem Album exeplarisch eine Datei auf ihre Aussteuerung hin überprüfen, um abzuschätzen, ob ich beim beabsichtigten umwandeln in MP3 eine Lautstärke-Korrektur vornehmen sollte. So sieht dann mein aktueller "komplizierterer" Befehl aus: find Ablage -mindepth 1 -type d | while read dir; \
do find "$dir"/01*.flac -print -exec sh -c '\
ffmpeg -hide_banner -i "{}" -af volumedetect -f null /dev/null 2>&1 >/dev/null | \
grep "Parsed_volumedetect_0.*_volume:"\
' \; 2> /dev/null ; done Ergebnis: Ablage/Udo Lindenberg - Ball Pompös (1974 Remastered)/01. Jonny Controlletti.flac
[Parsed_volumedetect_0 @ 0x609a09b8e200] mean_volume: -12.4 dB
[Parsed_volumedetect_0 @ 0x609a09b8e200] max_volume: -0.8 dB
Ablage/Yello - One Second (1987 Remaster 2005)/01 - La Habanera.flac
[Parsed_volumedetect_0 @ 0x653cc5122d00] mean_volume: -14.8 dB
[Parsed_volumedetect_0 @ 0x653cc5122d00] max_volume: -0.2 dB
.....
|
|
kB
Supporter, Wikiteam
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 9837
|
UlfZibis schrieb: kB schrieb: Da das oberste Verzeichnis weitere Ordner enthält, die dann rekursiv auch durchsucht werden müssen, lässt sich die Aufgabe alleine mit einer Schleife nicht lösen.
"Quod erat demonstrandum !" Das sagt der härteste Verfechter von (sinngemäß): "Man soll find nicht für Schleifen missbrauchen, da for-Schleifen klarer, unkomplizierter, einfacher und kürzer sind."
Hor bitte auf mit diesen unfairen Angriffen, unnötigen Spitzen und aus dem Kontext heraus gerissenen Pseudozitaten! Ich habe nie behauptet, dass sich jede Aufgabe mit einer einfachen For-Schleife lösen lässt, noch dass der sinnvolle Gebrauch von find generell Missbrauch wäre. Allerdings sehe ich es als Missbrauch, wenn man für Aufgaben, welche die spezifischen Fähigkeiten von find – insbesondere das rekursive Durchsuchen von Verzeichnisbäumen – nicht benötigen, dennoch find verwendet, bloß weil man eine simple Schleife vermeiden will. Leider verstellst Du Dich als zu doof, um diesen Punkt zu begreifen.
find . -type d -execdir find {} -maxdepth 1 -type f -name '*.flac' -print -quit \;
Wo kann ich da die eingangs erwähnte "kompliziertere Operation" unterbringen? ... liefert folgendes Ergebnis: […]
Eine Möglichkeit wäre: find . -type d -execdir find {} -maxdepth 1 -type f -name '*.flac' -print -quit \; | while read ; do echo ${REPLY#./} ; done […] Sieh an, also von wegen "klarer, unkomplizierter, einfacher und kürzer".
Nochmal: Hör auf mit diesem unfairen Gebaren! (s.o.)
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4735
|
Die Aussage zur rekursiven Funktion finde ich übertrieben. So eine Funktion ist nicht wirklich kompliziert und auch recht schnell geschrieben. 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | Recurse () {
local directory file
for directory in "$1"/*; do
if [[ -d $directory ]]; then
for file in "$directory"/*.flac; do
[[ -f $file ]] && echo "processing $file"
break
done
Recurse "$directory"
fi
done
}
Recurse Ablage
|
|