staging.inyokaproject.org

for-Schleife für Dateien in Verzeichnis - ich verzweifle

Status: Ungelöst | Ubuntu-Version: Ubuntu 24.04 (Noble Numbat)
Antworten |

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

Avatar von 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

Avatar von 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 Team-Icon

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 Team-Icon

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 Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4735

Noch eine Variante:

1
2
3
4
5
6
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 Team-Icon

Supporter, Wikiteam
Avatar von kB

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 Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

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:

1
2
3
4
$ 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:

1
for flac in Ablage/*.flac Ablage/*/*.flac Ablage/*/*/*.flac

Auch sehr schick, der Trick.

Marc_BlackJack_Rintsch schrieb:

1
2
3
4
5
6
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 \; 
  1. Wo kann ich da die eingangs erwähnte "kompliziertere Operation" unterbringen?

  2. ... 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 Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

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:

1
2
3
4
5
$ 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 Team-Icon

Supporter, Wikiteam
Avatar von kB

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 \; 
  1. Wo kann ich da die eingangs erwähnte "kompliziertere Operation" unterbringen?

  2. ... 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 Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

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
Antworten |