• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

[gelöst] bash-Script mit Doppelschleife

Christina

Moderator
Teammitglied
Hallo,

ich möchte ganz alte DAT-Band-Überspielungen (von meinem Papa analog über die Soundkarte gemacht) durch neue digitale Überspielungen (mit Solid State Recorder auf SD-Card) ersetzen.
Jetzt möchte ich aber nicht jede Flac-Datei von Hand neu mit ID3-Tags beschriften, sondern die Tags der alten Flac-Dateien übernehmen.

Verzeichnis alte Aufnahme z.B.:
Code:
cd Musik-Alben/Dolly\ Parton/Here\ You\ Come\ Again/
ls -1
01. Here You Come Again.flac
02. Baby Come Out Tonight.flac
03. It’s All Wrong, but It’s All Right.flac
04. Me and Little Andy.flac
05. Lovin’ You.flac
06. Cowgirl and the Dandy.flac
07. Two Doors Down.flac
08. God’s Coloring Book.flac
09. As Soon as I Touched Him.flac
10. Sweet Music Man.flac
Verzeichnis neue Aufnahme:
Code:
ls -1 /home/christina/Tascam/Dolly\ Parton/
T063_DAT-Band.flac
T064_DAT-Band.flac
T065_DAT-Band.flac
T066_DAT-Band.flac
T067_DAT-Band.flac
T068_DAT-Band.flac
T069_DAT-Band.flac
T070_DAT-Band.flac
T071_DAT-Band.flac
T072_DAT-Band.flac
1. Kommando (im alte-Aufnahme-Verzeichnis):
Code:
metaflac --export-tags-to=- 01.\ Here\ You\ Come\ Again.flac \
| metaflac --remove-all-tags --import-tags-from=- /home/christina/Tascam/Dolly\ Parton/T063_DAT-Band.flac
--export-tags-to={stdout} | --import-tags-from={stdin}

Jetzt fehlt mir nur ein bash-Script, das das automatisch für alle Dateien in den beiden getrennten Verzeichnissen übernimmt. Die neu getaggten "Txxx_DAT-Band.flac"-Dateien kann ich später für jedes Verzeichnis einfach mit Kid3-qt alle auf einmal umbenennen. (Filename: Format: %{track}. %{title})

Vom Prinzip mit einer Schleife, die alle Dateinamen in einem Verzeichnis einliest, wäre das ja so wie in diesem Thema:
bash-Script, um viele Dateien automatisch zu verlinken
Jetzt brauche ich aber zwei verschachtelte Schleifen, die jeweils ein Verzeichnis einlesen und die Dateinamen immer paarweise als Argument an die beiden metaflac-Befehle übergeben.
Knifflig, die bash.
Könnt ihr mir hier vielleicht weiterhelfen, bitte?
lg
Christina
 
OP
Christina

Christina

Moderator
Teammitglied
Gräfin Klara schrieb:
Ich denke, du solltest alle files beider directories in je ein array lesen
Ich weiß leider nicht, wie das in der bash geht. Diese Lösung würde mich schon interessieren, wenn der Programmieraufwand nicht zu groß ist.
In dem letzten Link-Script liegt das "Array" in {stdout}: "ls -1 *.$suffix | while read i"

:) Ich kann mir aber auch mit einer Schleife einzelne temp-Dateien aus den Metadaten exakt nach dem Schema aus dem letzten Link-Script erstellen lassen und mit einer zweiten Schleife die Ziel-Flac-Dateien mit den temp-Dateien taggen.
Code:
#!/bin/bash

echo -e "FLAC-Zielverzeichnis: \c"
read dirbasename
suffix="flac"

nr=1
ls -1 *.$suffix | while read i
do
    metaflac --export-tags-to="temp-$(printf "%02d" $nr)" "$i"
    echo "$i -> temp-$(printf "%02d" $nr)"
    let nr++
done
      
nr=1
ls -1 $dirbasename/*.$suffix | while read i
do   
    metaflac --remove-all-tags --import-tags-from="temp-$(printf "%02d" $nr)" "$i"
    echo "temp-$(printf "%02d" $nr) -> $i"
    let nr++
done

rm -f temp-??
 
Christina schrieb:
:) Ich kann mir aber auch mit einer Schleife einzelne temp-Dateien ...
Du kannst das lösen wie du willst

Christina schrieb:
Ich weiß leider nicht, wie das in der bash geht. Diese Lösung würde mich schon interessieren, wenn der Programmieraufwand nicht zu groß ist.
Ist ganz einfach
Code:
#!/bin/bash

fa=()   # filename array A
fb=()   # filename array B
i=0      # array index

# einlesen aller filenamen in 2 arrays
# wenn die filenamen auch den pfad halten sollen, dann schreibst du hier  -printf "%p/%f\n"
fa=($(find "/Musik-Alben/Dolly Parton/irgendwo" -maxdepth 1 -type f -name "*.flac" -printf "%f\n"))
fb=($(find "/home/christina/Dolly Parton blabla" -maxdepth 1 -type f -name "*.flac" -printf "%f\n"))

# erledigt

# nun lesen und ausgeben beider arrays mittels Indexierung
# in diese eine loop setzt du all dein metaflac Brimborium
for i in ${!fa[@]}; do
	printf "%.2d: %s = %s\n" "$i" "${fa[i]}" "${fb[i]}"
done
exit 0
Ausprobieren und Anpassen an deine Notwendigkeiten überlasse ich dir.
Max. 7 Zeilen code müssen reichen.

Gruß
Gräfin Klara
 
OP
Christina

Christina

Moderator
Teammitglied
Hi Gräfin Klara,
das klappt so leider noch nicht.

1) Mit find bekomme ich als Ausgabe die Reihenfolge wie im Verzeichnis gespeichert, aber nicht alphanumerisch sortiert.
Code:
echo $(find . -maxdepth 1 -type f -name "*.flac" -printf "%f\n")
01. Here You Come Again.flac 06. Cowgirl and the Dandy.flac 05. Lovin’ You.flac 08. God’s Coloring Book.flac 03. It’s All Wrong, but It’s All Right.flac 07. Two Doors Down.flac 09. As Soon as I Touched Him.flac 10. Sweet Music Man.flac 04. Me and Little Andy.flac 02. Baby Come Out Tonight.flac

Ich muss also ls verwenden.
2) Da die Dateinamen Leerstellen enthalten, bekomme ich bei den 10 Flac-Dateien 49 Strings im Array.
bash-Script:
(dirbasename=/home/christina/Tascam)
Code:
#!/bin/bash

echo -e "FLAC-Zielverzeichnis: \c"
read dirbasename
suffix="flac"

fa=()   # filename array A
fb=()   # filename array B
i=0     # array index
fa=($(ls -1 *.$suffix))
fb=($(ls -1 $dirbasename/*.$suffix))
for i in ${!fa[@]}; do
        printf "%.2d: %s = %s\n" "$i" "${fa[i]}" "${fb[i]}"
done
exit 0
00: 01. = /home/christina/Tascam/01.
01: Here = Here
02: You = You
03: Come = Come
04: Again.flac = Again.flac
05: 02. = /home/christina/Tascam/02.
06: Baby = Baby
07: Come = Come
08: Out = Out
09: Tonight.flac = Tonight.flac
10: 03. = /home/christina/Tascam/03.
11: It’s = It’s
12: All = All
13: Wrong, = Wrong,
14: but = but
15: It’s = It’s
16: All = All
17: Right.flac = Right.flac

49: Man.flac = Man.flac
 
Christina schrieb:
1) Mit find bekomme ich als Ausgabe die Reihenfolge wie im Verzeichnis gespeichert, aber nicht alphanumerisch sortiert.
Kein Problem
Code:
find . -maxdepth 1 -type f -name "*.flac" -printf "%f\n" | sort -f
bzw.
Code:
fa=($(find "/Musik-Alben/Dolly Parton/irgendwo" -maxdepth 1 -type f -name "*.flac" -printf "%f\n" | sort -f))

Dein 'echo' ist fehl am Platz



Christina schrieb:
2) Da die Dateinamen Leerstellen enthalten, bekomme ich bei den 10 Flac-Dateien 49 Strings im Array.
Setze im script irgendwo am Beginn:
Code:
IFS=$'\n'

Ausprobieren
 

abgdf

Guru
Christina schrieb:
Da die Dateinamen Leerstellen enthalten, bekomme ich bei den 10 Flac-Dateien 49 Strings im Array.
Ja, genau. Hatte ich Dir nicht deshalb schonmal gesagt, daß Leerstellen und Umlaute, bzw. Unicode-Zeichen in Dateinamen problematisch sind?
Christina schrieb:
Knifflig, die bash.
Ja, genau. Hatte ich Dir nicht deshalb schonmal gesagt, daß sowas wesentlich leichter in Perl oder Python zu machen ist? Weil es da vernünftige Arrays gibt, und auch die Leerzeichen keine Probleme machen?

Wenn Du das in einer der beiden Sprachen machen möchtest (könntest Dir eine aussuchen), könnte ich Dich da durchführen, wenn Du möchtest. Wäre sowieso 'ne gute Sache zu lernen.
 
abgdf schrieb:
Weil es da vernünftige Arrays gibt, und auch die Leerzeichen keine Probleme machen?
Gib mir Beispiele "vernünftiger", eindimensionaler Perl Arrays (ohne Hash), die einem Array in Bash überlegen wären.
Das wird dir nicht gelingen, weder in Performance noch Anwendung.
 
OP
Christina

Christina

Moderator
Teammitglied
Jetzt klappt's. Und die Lösung mit zwei Arrays ist kürzer als die mit temp-Dateien und zwei Schleifen.
Code:
#!/bin/bash

echo -e "FLAC-Zielverzeichnis: \c"
read pathname
suffix="flac"
IFS=$'\n'

fa=()   # filename array A
fb=()   # filename array B
i=0     # array index
fa=($(find . -maxdepth 1 -type f -name "*.$suffix" -printf "%f\n" | sort -f))
fb=($(find $pathname -maxdepth 1 -type f -name "*.$suffix" -printf "%p\n" | sort -f))
for i in ${!fa[@]}; do
  printf "%.2d: %s -> %s\n" "$i" "${fa[i]}" "${fb[i]}"
  metaflac --export-tags-to=- ${fa[i]} | metaflac --remove-all-tags --import-tags-from=- ${fb[i]}
done
exit 0
Super, Danke!
@abgdf Wo ist das Problem?
LG
Christina
 
OP
Christina

Christina

Moderator
Teammitglied
Was bedeutet das @ hier als Index im Array?
Code:
for i in ${!fa[@]}; do
Ist das ein explizit ungültiger Ausdruck in der bash wie der NULL-Pointer in C?
 

abgdf

Guru
Christina schrieb:
@abgdf Wo ist das Problem?
Ach, ich mag die bash halt nicht besonders (Leerzeichenproblematik in Arrays, fehlende oder existierende Whitespace-Zeichen können von Bedeutung sein ('a="a"' - ok, 'a = "a"' nicht), nur eingeschränkte Rückgabewerte von Funktionen, unerwartete Subshells, umständliche Stringfunktionen, wird schnell kryptisch - und ich finde "fi" bescheuert :) ). Ist kein so gutes Tool finde ich, bzw. es gibt inzwischen bessere. Aber wenn Du damit klarkommst und es benutzen willst, wird es schon ok sein.
 
Christina schrieb:
Was bedeutet das @ hier als Index im Array?
Code:
for i in ${!fa[@]}; do
Ist das ein explizit ungültiger Ausdruck in der bash wie der NULL-Pointer in C?
@ steht für Array als solches, d.h. liefert immer generelle Informationen zum Array, Beispiel
${!fa[@]} alle indexes, von 0 bis x
${#fa[@]} Gesamtgröße = Anzahl der Elemente (auch das hätten wir verwenden können)
${fa[@]} liefert alle Elemente

usw.

.. wie NULL-Pointer in C?
Nein
 
OP
Christina

Christina

Moderator
Teammitglied
Gräfin Klara schrieb:
${#fa[@]} Gesamtgröße = Anzahl der Elemente (auch das hätten wir verwenden können)
Also so: (C-ähnliche Notation)
Code:
len_a=${#fa[@]}
for ((i=0; i<${len_a}; i++)); do
Mit doppelter Klammer ((…)), oder? Funktionieren tut's.

Wie schreibt man eigentlich in der bash die if-Anweisung korrekt? Mit doppelter eckiger Klammer [[…]] ?
Ich habe verschiedene Varianten im www gesehen.
Code:
len_a=${#fa[@]}
len_b=${#fb[@]}
if [[ $len_a = $len_b ]]; then
  …
else
  printf "Anzahl Dateien unterschiedlich: Quellverzeichnis %d <-> Zielverzeichnis %d.\n" "$len_a" "$len_b"
fi
Funktionieren tut's.
 

abgdf

Guru
Christina schrieb:
Wie schreibt man eigentlich in der bash die if-Anweisung korrekt? Mit doppelter eckiger Klammer [[…]] ?
Das tun wohl die meisten. Es ist wohl eine Abkürzung für:
Code:
if test $len_a = $len_b; then ...
Was ich lieber verwende, da man dabei wenigstens halbwegs nachvollziehen kann, was da passiert.
Doppelte eckige Klammern, und dann noch notwendige Leerzeichen dazwischen, ohne die es nicht funktioniert ... also ich find's völlig unintuitiv.

Seltsamerweise wird Perl immer vorgehalten, wie häßlich der Code ist. Dabei ist bash-Code noch viel häßlicher. :)

P.S.: Übrigens: "-eq", nicht "=": Du willst ja Zahlen vergleichen, nicht Strings.
In Perl ist es übrigens umgekehrt. Auch toll. Nichtmal einheitlich.
 

josef-wien

Ultimate Guru
Ohne Pfad-Angabe ist test ein shell builtin und entspricht [ (ohne Pfad-Angabe ebenfalls ein shell builtin).

[[ ]] ist eine flexiblere Form. Siehe: https://tldp.org/LDP/abs/html/abs-guide.html#DBLBRACKETS
 
OP
Christina

Christina

Moderator
Teammitglied
abgdf schrieb:
P.S.: Übrigens: "-eq", nicht "=": Du willst ja Zahlen vergleichen, nicht Strings.
In Perl ist es übrigens umgekehrt. Auch toll. Nichtmal einheitlich.
-eq funktioniert auch.
Code:
if [ $len_a -eq $len_b ]; then
Code:
if [ "$len_a" -eq "$len_b" ]; then
Integer möchte ich vergleichen. Wie ist es korrekt?
lg Christina
 
Christina schrieb:
Code:
len_a=${#fa[@]}
for ((i=0; i<${len_a}; i++)); do
..Funktionieren tut's.
Kein Wunder, ist ja auch korrekt
Ich hätte es folgend geschrieben (C-alike)
Code:
for((i=0,k=${#fa[@]}; i < k; i++)); do



Christina schrieb:
Wie schreibt man eigentlich in der bash die if-Anweisung korrekt? Mit doppelter eckiger Klammer [[…]] ?
Ich habe verschiedene Varianten im www gesehen.
[ .. ] hat historische Gründe. Es läßt sich nicht ändern, nur erweitern, sonst würden Millionen alte scripts nicht mehr funktionieren.
[[ .. ]] ist neu, es bietet mehr und kann auch alles was [ .. ] kann. Denk nicht darüber nach, verwende [[ .. ]]


Christina schrieb:
Code:
len_a=${#fa[@]}
len_b=${#fb[@]}
if [[ $len_a = $len_b ]]; then
  …
else
  printf "Anzahl Dateien unterschiedlich: Quellverzeichnis %d <-> Zielverzeichnis %d.\n" "$len_a" "$len_b"
fi
Funktionieren tut's.
= für Vergleich ist aus der Unix Steinzeit, wird in vielen scripts noch verwendet, kann man nicht ändern, Grund siehe oben.
len_a=${#fa[@]} liefert dir einen integer (Zahl). integers werden mit -eq, -ne, usw. verglichen. [[ $i -eq $k ]];
Wenn du = anwendest, dann werden die Zahlen vor dem Vergleich in strings umgewandelt, diesen Doppelmoppel wollen wir nicht.
Strings testest du auf Gleichheit mit [[ "$str_a" == "$str_b" ]]; also das einfache = streichst du für Vergleiche aus deinem Gedächtnis.

Code:
if [[ $len_a -eq $len_b ]]; then
  …
elif [[ $len_a -gt $len_b ]]; then
 ...
elif [[ "$str_a" == "$str_b" && $len_a -ne $len_b ]]; then
...
else
  ...
fi

Gruß
Gräfin Klara
 
OP
Christina

Christina

Moderator
Teammitglied
Vielen Dank! :)
Hier ist nochmal das ganze Script und die (gewünschte) Ausgabe. Ich habe beim Print bei Index statt "$1" noch "$((i+1))" eingefügt, damit die Nummerierung bei 01 statt 00 beginnt. Ich hoffe, das ist im bash-Script so richtig.
Code:
#!/bin/bash

echo -e "FLAC-Zielverzeichnis: \c"
read destin_path
source_path="."
IFS=$'\n'

fa=($(find $source_path -maxdepth 1 -type f -name "*.flac" -printf "%f\n" | sort -f))
fb=($(find $destin_path -maxdepth 1 -type f -name "*.flac" -printf "%p\n" | sort -f))
len_a=${#fa[@]}
len_b=${#fb[@]}
if [[ $len_a -eq $len_b ]]; then
  for ((i=0; i<${len_a}; i++)); do
    printf "%.2d: %s -> %s\n" "$((i+1))" "${fa[i]}" "${fb[i]}"
    metaflac --export-tags-to=- ${fa[i]} | metaflac --remove-all-tags --import-tags-from=- ${fb[i]}
  done
else
  printf "Anzahl Dateien unterschiedlich: Quellverzeichnis %d <-> Zielverzeichnis %d.\n" "$len_a" "$len_b"
fi
exit 0

# Zeile 13 alternativ:
# i=0
# for i in ${!fa[@]}; do
01: 01. Here You Come Again.flac -> /home/christina/Tascam/Dolly Parton/T063_DAT-Band.flac
02: 02. Baby Come Out Tonight.flac -> /home/christina/Tascam/Dolly Parton/T064_DAT-Band.flac
03: 03. It’s All Wrong, but It’s All Right.flac -> /home/christina/Tascam/Dolly Parton/T065_DAT-Band.flac
04: 04. Me and Little Andy.flac -> /home/christina/Tascam/Dolly Parton/T066_DAT-Band.flac
05: 05. Lovin’ You.flac -> /home/christina/Tascam/Dolly Parton/T067_DAT-Band.flac
06: 06. Cowgirl and the Dandy.flac -> /home/christina/Tascam/Dolly Parton/T068_DAT-Band.flac
07: 07. Two Doors Down.flac -> /home/christina/Tascam/Dolly Parton/T069_DAT-Band.flac
08: 08. God’s Coloring Book.flac -> /home/christina/Tascam/Dolly Parton/T070_DAT-Band.flac
09: 09. As Soon as I Touched Him.flac -> /home/christina/Tascam/Dolly Parton/T071_DAT-Band.flac
10: 10. Sweet Music Man.flac -> /home/christina/Tascam/Dolly Parton/T072_DAT-Band.flac
Wenn jetzt kein Fehler mehr drin ist, habe ich ein schönes Beispiel fürs nächste Script. Learning by doing. :)
 
Oben