staging.inyokaproject.org

Bash-Skript: jeder 2. Dateipfad wird am Anfang merkwürdig abgeschnitten

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

UlfZibis

Anmeldungsdatum:
13. Juli 2011

Beiträge: 3351

Hallo,

zunächst einmal folgendes Script (für alle *.flac-Dateien sollen gruppiert nach Album die Dynamik-Werte ausgegeben werden):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash
dirs=0;
flacdirs=0;
while read -r -d '' dir; do
  ((dirs++))
  files=0;
  while read -r -d '' file; do
#    echo "$dir";
    ((flacdirs++));
    ((files++));
    echo $file;
    vol_detect=`ffmpeg -hide_banner -i "${file}" -af volumedetect -f null /dev/null 2>&1 >/dev/null \
    | grep "Parsed_volumedetect_0"`;
#    echo $vol_detect;
    mean_=${vol_detect#*mean_volume: };
    mean_=${mean_%% *};
    max_=${vol_detect#*max_volume: };
    max_=${max_%% *};
    echo mean_volume: $mean_ dB max_volume: $max_ dB;
  done < <(find "$dir" -maxdepth 1 -iname "*.flac" -print0 | sort -z);
  if [[ $files > 0 ]]; then
    echo files: $files; echo;
  fi;
done < <(find Ablage -mindepth 1 -type d -print0 | sort -z)
echo Number of dirs total: $dirs;
echo Number of dirs with flac files: $flacdirs;

Auf eine solche Dateistruktur angewandt erhalte ich:

Ablage/Al Di Meola & John McLaughlin & Paco De Lucía - Friday Night In San Francisco (1981)/01. Mediterranean Sundance Rio Ancho.flac
mean_volume: -21.1 dB max_volume: -0.0 dB
Al Di Meola & John McLaughlin & Paco De Lucía - Friday Night In San Francisco (1981)/02. Short Tales Of The Black Forest.flac
mean_volume: dB max_volume: dB
Ablage/Al Di Meola & John McLaughlin & Paco De Lucía - Friday Night In San Francisco (1981)/03. Frevo Rasgado.flac
mean_volume: -27.4 dB max_volume: -1.1 dB
e/Al Di Meola & John McLaughlin & Paco De Lucía - Friday Night In San Francisco (1981)/04. Fantasia Suite.flac
mean_volume: dB max_volume: dB
Ablage/Al Di Meola & John McLaughlin & Paco De Lucía - Friday Night In San Francisco (1981)/05. Guardian Angel.flac
mean_volume: -22.5 dB max_volume: -3.1 dB
files: 5

Es fällt auf, dass nur für jede 2. Datei ein Ergebnis ausgegeben wird. Woran könnte das liegen?
Auch jeder 2. Dateipfad wird am Anfang merkwürdig abgeschnitten.

Das auskommentierte echo $vol_detect; liefert:

[Parsed_volumedetect_0 @ 0x62fac5a6b080] n_samples: 0 [Parsed_volumedetect_0 @ 0x62fac5a63500] n_samples: 122609732 [Parsed_volumedetect_0 @ 0x62fac5a63500] mean_volume: -21.1 dB [Parsed_volumedetect_0 @ 0x62fac5a63500] max_volume: -0.0 dB [Parsed_volumedetect_0 @ 0x62fac5a63500] histogram_0db: 82 [Parsed_volumedetect_0 @ 0x62fac5a63500] histogram_1db: 1059 [Parsed_volumedetect_0 @ 0x62fac5a63500] histogram_2db: 5229 [Parsed_volumedetect_0 @ 0x62fac5a63500] histogram_3db: 15947 [Parsed_volumedetect_0 @ 0x62fac5a63500] histogram_4db: 36406 [Parsed_volumedetect_0 @ 0x62fac5a63500] histogram_5db: 69766

... allerdings auch nur für jede 2. Datei. Also da schon "liegt der Hase im Pfeffer".

Außerdem würde mich interessieren, ob und wie man die beiden Zeilen 15,16 bzw. 17,18 jeweils in einen Ausdruck verwandeln kann.
Da müsste doch mit folgendem Regex was zu machen sein:

.*mean_volume: (\S*?) db.*

Ich weiß aber leider nicht, wie man aus einem Ergebnis mittels grep nur den Inhalt zwischen den Klammern auslesen kann.

Wenn diese Probleme gelöst sind, geht es dann weiter mit Berechnungen aus diesen Werten mittels bc.

Moderiert von shiro:

Dieser Thread ist von "Bash-Skript: Rechnen mit bc" umbenannt worden in "Bash-Skript: jeder 2. Dateipfad wird am Anfang merkwürdig abgeschnitten".

TK87

Anmeldungsdatum:
8. Juli 2019

Beiträge: 266

Moin,

UlfZibis schrieb:

Es fällt auf, dass nur für jede 2. Datei ein Ergebnis ausgegeben wird. Woran könnte das liegen?

Sieh dir mal den Dateipfade der jeweils 2. Datei an, diese sind unvollständig und die Datei existiert somit nicht. Der ffmpeg-Befehl scheint ein paar Zeichen von stdin einzulesen, frag mich aber bitte nicht warum.

Außerdem würde mich interessieren, ob und wie man die beiden Zeilen 15,16 bzw. 17,18 jeweils in einen Ausdruck verwandeln kann.

1
2
mean_=`grep -oP "(?<=mean_volume: ).*?dB" <<<$vol_detect`
max_=`grep -oP "(?<=max_volume: ).*?dB" <<<$vol_detect`

Gruß Thomas

shiro Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1303

... Zeilen 15,16 bzw. 17,18 jeweils in einen Ausdruck verwandeln kann. Da müsste doch mit folgendem Regex was zu machen sein:

Ja, dein Vorschlag war fast richtig. Vereinfacht geht das so:

$ f="test.flac"
$ eval $(ffmpeg -hide_banner -i "$f" -af volumedetect -f null /dev/null 2>&1 >/dev/null | 
 sed -n 's/.*Parsed_volumedetect_0 .* \(mean\|max\)_volume: \([^ ]*\) dB.*/\1_=\2/p')
$ echo $f
$ echo mean_volume: $mean_ dB max_volume: $max_ dB
test.flac
mean_volume: -25.6 dB max_volume: -5.6 dB
$ 

Dies hat allerdings nichts mit deiner im Thema gestellten Frage zu "bc" zu tun.

UlfZibis

(Themenstarter)

Anmeldungsdatum:
13. Juli 2011

Beiträge: 3351

TK87 schrieb:

Sieh dir mal den Dateipfade der jeweils 2. Datei an, diese sind unvollständig und die Datei existiert somit nicht. Der ffmpeg-Befehl scheint ein paar Zeichen von stdin einzulesen, frag mich aber bitte nicht warum.

Ja, wenn ich den ffmpeg-Befehl weglasse, werden die Dateipfade nicht abgeschnitten:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/bin/bash
dirs=0;
flacdirs=0;
while read -r -d '' dir; do
  ((dirs++))
  files=0;
  while read -r -d '' file; do
#    echo "$dir";
    ((files++));
    echo $file;
#    vol_detect=`ffmpeg -hide_banner -i "${file}" -af volumedetect -f null /dev/null 2>&1 >/dev/null \
#    | grep "Parsed_volumedetect_0"`;
#    echo $vol_detect;
#    mean_=${vol_detect#*mean_volume: };
#    mean_=${mean_%% *};
#    max_=${vol_detect#*max_volume: };
#    max_=${max_%% *};
#    echo mean_volume: $mean_ dB max_volume: $max_ dB;
  done < <(find "$dir" -maxdepth 1 -iname "*.flac" -print0 | sort -z);
  if [[ $files > 0 ]]; then
    ((flacdirs++));
    echo files: $files; echo;
  fi;
done < <(find Ablage -mindepth 1 -type d -print0 | sort -z)
echo Number of dirs total: $dirs;
echo Number of dirs with flac files: $flacdirs;
echo ; echo ;
1
2
mean_=`grep -oP "(?<=mean_volume: ).*?dB" <<<$vol_detect`
max_=`grep -oP "(?<=max_volume: ).*?dB" <<<$vol_detect`

Da kommt dann folgendes bei raus (also " dB" wird fehlerhafterweise an die Zahl angehangen):

Ablage/Al Di Meola & John McLaughlin & Paco De Lucía - Friday Night In San Francisco (1981)/01. Mediterranean Sundance Rio Ancho.flac
mean_volume: -21.1 dB dB max_volume: -0.0 dB dB
Al Di Meola & John McLaughlin & Paco De Lucía - Friday Night In San Francisco (1981)/02. Short Tales Of The Black Forest.flac
mean_volume: dB max_volume: dB
Ablage/Al Di Meola & John McLaughlin & Paco De Lucía - Friday Night In San Francisco (1981)/03. Frevo Rasgado.flac
mean_volume: -27.4 dB dB max_volume: -1.1 dB dB
e/Al Di Meola & John McLaughlin & Paco De Lucía - Friday Night In San Francisco (1981)/04. Fantasia Suite.flac
mean_volume: dB max_volume: dB
Ablage/Al Di Meola & John McLaughlin & Paco De Lucía - Friday Night In San Francisco (1981)/05. Guardian Angel.flac
mean_volume: -22.5 dB dB max_volume: -3.1 dB dB
files: 5

UlfZibis

(Themenstarter)

Anmeldungsdatum:
13. Juli 2011

Beiträge: 3351

shiro schrieb:

Vereinfacht geht das so:

$ f="test.flac"
$ eval $(ffmpeg -hide_banner -i "$f" -af volumedetect -f null /dev/null 2>&1 >/dev/null | 
 sed -n 's/.*Parsed_volumedetect_0 .* \(mean\|max\)_volume: \([^ ]*\) dB.*/\1_=\2/p')
$ echo $f
$ echo mean_volume: $mean_ dB max_volume: $max_ dB
test.flac
mean_volume: -25.6 dB max_volume: -5.6 dB
$ 

Ja das ist ja sehr raffiniert. Wenn ich es richtig verstehe, führt eval dann aber zur 2-maligen Ausführung von ffmpeg, was zu doppelter Ausführungszeit führt.
Außerdem hätte ich gerne den Zwischenwert vol_detect. Wie kann obiges denn dementsprechend gesplittet werden?

Dies hat allerdings nichts mit deiner im Thema gestellten Frage zu "bc" zu tun.

Ja das Problem hab' ich erst mal wieder rausgenommen nachdem ich bemerkt hatte, dass da im Vorfeld noch das Problem mit dem Ergebnis für jede 2. Datei auftaucht. Das bc-Problem zeige ich, wenn ersteres mal geklärt ist.

kB Team-Icon

Supporter, Wikiteam
Avatar von kB

Anmeldungsdatum:
4. Oktober 2007

Beiträge: 9837

UlfZibis schrieb […]

Wenn diese Probleme gelöst sind, geht es dann weiter mit Berechnungen aus diesen Werten mittels bc.

Du kennst die Regeln für das Forum? Insbesondere „Nur eine Frage pro Thema“? Du missachtest sie hier (und auch früher schon wiederholt) massiv, in dem Du erst Vorfragen stellst, nach deren Beantwortung Du sogar erst zum Thema dieser Diskussion kommen willst.

Bitte beachte die Regeln des Forums!

UlfZibis

(Themenstarter)

Anmeldungsdatum:
13. Juli 2011

Beiträge: 3351

Ja dann bitte ich das Wikiteam, das Thema umzubenennen: "Bash-Skript: jeder 2. Dateipfad wird am Anfang merkwürdig abgeschnitten"

TK87

Anmeldungsdatum:
8. Juli 2019

Beiträge: 266

UlfZibis schrieb:

Da kommt dann folgendes bei raus (also " dB" wird fehlerhafterweise an die Zahl angehangen)

Ich dacchte das war das Ziel, das andere "dB" könntest du ja bei der echo-Ausgabe weglassen. Aber gut, dann halt ohne das "dB" dahinter...

1
2
mean_=`grep -oP "(?<=mean_volume: ).*?(?= dB)" <<<$vol_detect`
max_=`grep -oP "(?<=max_volume: ).*?(?= dB)" <<<$vol_detect`

UlfZibis

(Themenstarter)

Anmeldungsdatum:
13. Juli 2011

Beiträge: 3351

TK87 schrieb

Ich dacchte das war das Ziel,

ich will mit dem Wert ja mittels bc was berechnen, da würde " db" dann stören.

Aber gut, dann halt ohne das "dB" dahinter...

Ganz lieben Dank, denn so weit bin ich in Regex noch nicht soweit vorgedrungen, wie das mit der "(?⇐...)"-Logik funktioniert. Bin ja schon froh, dass ich das mit dem greedy verstanden habe. Folgendes funktioniert auch (und dürfte sogar performanter sein):

1
mean_=`grep -oP "(?<=mean_volume: )\S*" <<<$vol_detect`

shiro Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1303

... führt eval dann aber zur 2-maligen Ausführung von ffmpeg, was zu doppelter Ausführungszeit führt.

Nein.

Erläuterung:

  • In der Subshell ($(...)) wird eine bash-Befehlskette (Definition der Variablen mean_ und max_) erzeugt, die mittels "eval" in den aktuellen Prozesskontext übernommen wird.

  • Der "ffmpeg" Befehl wird einmalig je Datei ($f) ausgeführt.

  • Die Ausgabe dieses Befehls wird an "sed" übergeben, der Zeilen mit "Parsed_val..." und entweder "mean_volume" oder "max_volume" sucht. Die "mean" oder "max" Strings werden in Variable 1 (\1) übernommen, die Zeichen hinter "..: ", die kein Blank ([^ ]*) enthalten werden als Werte in die Variable 2 (\2) übernommen. Ausgegeben wird dann die Zeichenkette zur Definition der Bash-Variable (z.B. mean_=-25.6).

Außerdem hätte ich gerne den Zwischenwert vol_detect.

Das bekommst du ganz einfach, indem du wie bisher die Ausgabe von "ffmpeg" der Variablen zuweist.

Hier ein Beispiel:

$ ( find Ablage/ -type f -iname "*.flac" | sort | tee /tmp/1.$$ |
while read f; do
 echo "$f"
 vol_detect=$(ffmpeg -hide_banner -i "$f" -af volumedetect -f null /dev/null 2>&1 >/dev/null <<<" " | grep "Parsed_volumedetect_0")
 eval $(sed -n 's/.* \(mean\|max\)_volume: \([^ ]*\) dB.*/\1_=\2/p'<<<"$vol_detect")
 echo mean_volume: $mean_ dB max_volume: $max_ dB
done
sed 's#\(.*\)/.*#\1#' /tmp/1.$$ | sort | uniq -c | tee /tmp/2.$$ | sed 's/^ */files: /'
echo Number of dirs with flac files: $(cat /tmp/2.$$ | wc -l)
rm /tmp/[12].$$ )
Ablage/Album1/test1.flac
mean_volume: -41.3 dB max_volume: -1.7 dB
Ablage/Album1/test 2.flac
mean_volume: -25.6 dB max_volume: -5.6 dB
Ablage/Album2/Test 3.flac
mean_volume: -18.7 dB max_volume: -0.4 dB
files: 2 Ablage/Album1
files: 1 Ablage/Album2
Number of dirs with flac files: 2
$ 

Hinweis: "ffmpeg" frisst ein Zeichen von stdin (siehe "Press [q] to stop, [?] for help" in der "ffmpeg" Ausgabe). Daher bekommt das Programm dieses über den Here-String (<<<" "). Damit die Ausgabe nicht immer direkt hinter dem Befehl erfolgt, habe ich den Block der Übersicht halber geklammert und für die Statistik ein paar temporäre Dateien erzeugt (/tmp/[12].$$).

TK87

Anmeldungsdatum:
8. Juli 2019

Beiträge: 266

UlfZibis schrieb:

Ganz lieben Dank, denn so weit bin ich in Regex noch nicht soweit vorgedrungen, wie das mit der "(?⇐...)"-Logik funktioniert.

Das ist ganz einfach

(?=...)     Lookahead assertion.  Prüft ob ... hinter der aktuellen Stelle steht.
(?!...)     Negative lookahead.   Schließt Treffer aus, bei denen ... dahinter steht.
(?<=...)    Lookbehind assertion. Prüft, ob ... vor der aktuellen Stelle steht.
(?<!...)    Negative lookbehind.  Schließt Treffer aus, bei denen ... davor steht

Die Assertion selbst werden im RegEx nicht berücksichtigt und verändern auch die aktuelle Position nicht (können also auch mehrfach angewandt werden).

Beachte, das sed ein älteres RegEx-Flavour nutzt und keine Assertitionen unterstützt.

RegEx Legende

UlfZibis

(Themenstarter)

Anmeldungsdatum:
13. Juli 2011

Beiträge: 3351

shiro schrieb:

Erläuterung:

  • ...

Sehr schön, danke für die Fleißarbeit. So verstehe ich jetzt wohl, was da passiert:

  • sed behandelt jede Zeile der Ausgabe von ffmpeg separat (oder wird für jede Zeile wiederholt ausgeführt). So wird dann einmal mean_ und ein andermal max_ ermittelt.

  • <<<" " füttert ffmpeg mit einer Leereingabe, was weiteres Einlesen stoppt. ## Sehr geil der Trick

  • vol_detect=$(...) ist dasselbe wie vol_detect=`...`.

  • $ ( find Ablage/ -type f -iname "*.flac" | sort | while read f; ... ) macht dasselbe wie while read -r -d "" file; ... < <(find "$dir" -maxdepth 1 -iname "*.flac" -print0 | sort -z);

Verstehe ich das so richtig?

Was ich noch nicht verstehe: Wenn ich ffmpeg direkt ausgeben lasse, erhalte ich eine mehrzeilige Ausgabe. Füttere ich die Ausgabe von ffmpeg in die Variable vol_detect und gebe diese per echo aus, werden die Zeilenumbrüche durch ein Leerzeichen ersetzt. Warum ist das so?

Damit die Ausgabe nicht immer direkt hinter dem Befehl erfolgt, habe ich den Block der Übersicht halber geklammert und für die Statistik ein paar temporäre Dateien erzeugt (/tmp/[12].$$).

Man lernt nie aus, danke für das Beispiel.
Kann man mit tee eigentlich auch in eine Variable schreiben, statt in eine Datei?

TK87 schrieb:

Das ist ganz einfach

 ... 

Die Assertion selbst werden im RegEx nicht berücksichtigt und verändern auch die aktuelle Position nicht (können also auch mehrfach angewandt werden).

Danke Dir.

Beachte, das sed ein älteres RegEx-Flavour nutzt und keine Assertitionen unterstützt.

Dafür kann sed Regex-Gruppierungen aber separat verarbeiten und ausgeben, was per grep wohl nicht möglich ist, und braucht dann die Assertions (zumindest hier) nicht.

TK87

Anmeldungsdatum:
8. Juli 2019

Beiträge: 266

UlfZibis schrieb:

Dafür kann sed Regex-Gruppierungen aber separat verarbeiten und ausgeben, was per grep wohl nicht möglich ist

grep gibt halt immer nur Trefferzeilen aus.

Wenn man beim Ersetzen die Assertionen benötigt, kann man statt sed auch einfach perl mit den Schaltern "-pe" nutzen. Die Syntax ist dann nahezu identisch mit der von sed, aber es gibt eben mehr Möglichkeiten wie u.a. die Assertionen.

UlfZibis

(Themenstarter)

Anmeldungsdatum:
13. Juli 2011

Beiträge: 3351

TK87 schrieb:

UlfZibis schrieb:

Dafür kann sed Regex-Gruppierungen aber separat verarbeiten und ausgeben, was per grep wohl nicht möglich ist

grep gibt halt immer nur Trefferzeilen aus.

Oder nur Treffer mit der Option -o, --only-matching, aber nicht die Umkehrung. Allerdings habe ich jetzt in der RegEx Legende unter "String Replac­ement" gesehen, dass wohl auch grep die Auswahl aus einer Gruppierung mittels $n unterstützen müsste.

... eben mehr Möglichkeiten wie u.a. die Assertionen.

... die dann aus "Treffern" Exklusion machen.

shiro Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1303

Verstehe ich das so richtig?

Ja.

sed behandelt jede Zeile der Ausgabe von ffmpeg separat (oder wird für jede Zeile wiederholt ausgeführt). So wird dann einmal mean_ und ein andermal max_ ermittelt.

Korrekt. Wenn man bei "sed" keinen "-z" Schalter setzt, wird zeilenweise gearbeitet. Wenn man "sed -z" macht, steht der ganze Stream zur Verfügung. Dann muss man die "^$" Elemente anders interpretieren und Zeilenenden über "\n" suchen. Das macht man häufig, wenn die zu suchenden/ersetzenden Komponenten sich über mehrere Zeilen erstrecken und man nicht mir dem Multi-Line ("m") Qualifier arbeiten möchte. Für die hier zu betrachtenden Aufgabe ist aber eine zeilenweise Bearbeitung angebracht.

<<<" " füttert ffmpeg mit einer Leereingabe, was weiteres Einlesen stoppt.

Ja, ich hatte mich gewundert, dass der vollständige Pfad der Datei nicht zur Verfügung stand und somit nur jede 2. Datei behandelt wurde. Da habe ich mir den Output von ffmpeg mal angeschaut und gesehen, dass "ffmpeg" sich ein Zeichen vom Input-Stream stibitzt, der eigentlich durch "read" gelesen werden sollte.

vol_detect=$(...) ist dasselbe wie vol_detect=`...`.

Ja, bei der "ksh" hatte ich damals (vor ca 30 Jahren) nur die `...` Variante zur Verfügung. Der Nachfolger bash hat dieses Konstrukt aus Kompatibilität übernommen und die $(...) Variante zugefügt, was aus meiner Sicht logischer war, da man mit "$Var" den Inhalt von "Var" ausgab und man eine Subshell über die Klammern "()" definierte.

$ ( find Ablage/ -type f -iname "*.flac" | sort | while read f; ... ) macht dasselbe wie while read -r -d "" file; ... < <(find "$dir" -maxdepth 1 -iname "*.flac" -print0 | sort -z);

Jein. Sinngemäß ja, aber deine Variante liefert auch Verzeichnisse, in denen keine ".flac" Dateien vorhanden sind. Daher ist die Ermittlung der Statistik (Anzahl Verzeichnisse, Anzahl .flac Dateien) ein klein wenig anders mit den "$$" Dateien gelöst.

Füttere ich die Ausgabe von ffmpeg in die Variable vol_detect und gebe diese per echo aus, werden die Zeilenumbrüche durch ein Leerzeichen ersetzt. Warum ist das so?

Die Variable "vol_detect" enthält durchaus die Zeilenumbrüche, was du sehen kannst, wenn du ein

$ echo "$vol_detect"

machst. Ohne das Quoten (") werden die Zeilenumbrüche in ein Leerzeichen gewandelt und du erhältst alles in einer Zeile dargestellt. Das ist aber normales Verhalten der Shell und so gewünscht.

Kann man mit tee eigentlich auch in eine Variable schreiben, statt in eine Datei?

Ja, das ist möglich aber etwas komplizierter als in eine Datei. Aber da das "/tmp" Verzeichnis im Memory liegt, wirst du keine großartigen Geschwindigkeitsverbesserungen durch die Variablen-Zuweisung bekommen.

Hier aber das Beispiel, wie man das Aufsplitten des Streams durch "tee" nicht in eine Datei sondern in eine Variable macht:

$ # Tee mit Split der Ausgabe in die Variable "var1"
$ { var1=$(find Ablage/ -type f -iname "*.flac" | sort | tee /dev/fd/3); } 3>&1
Ablage/Album1/test1.flac
Ablage/Album1/test 2.flac
Ablage/Album2/Test 3.flac
$ echo "$var1"
Ablage/Album1/test1.flac
Ablage/Album1/test 2.flac
Ablage/Album2/Test 3.flac
$ 

Verstanden? Wenn du willst, kann ich auch näher erläutern, wie obiges mit dem Stream beim fd3 funktioniert (z.B. default: fd1=stdout, fd2=stderr).

Antworten |