Bearbeiten Sie den Dateinamen, der vom Befehl find weitergeleitet wird

10

Ich bin relativ neu in Bash und versuche, etwas zu tun, das auf den ersten Blick ziemlich einfach zu sein schien - führen Sie find über eine Verzeichnishierarchie aus, um alle * .wma-Dateien abzurufen, und leiten Sie diese Ausgabe an einen Befehl weiter, in dem ich sie in mp3 und konvertiere Speichern Sie die konvertierte Datei als .mp3. Meiner Meinung nach sollte der Befehl wie folgt aussehen (ich habe den Befehl zur Audiokonvertierung weggelassen und verwende stattdessen Echo zur Veranschaulichung):

$ find ./ -name '*.wma' -type f -print0 | xargs -0 -I f echo ${f%.*}.mp3

So wie ich es verstehe, kann ich mit dem Argument -print0 Dateinamen mit Leerzeichen verarbeiten (was viele davon tun, da es sich um Musikdateien handelt). Ich erwarte dann (als Ergebnis von xargs), dass jeder Dateipfad von find in f erfasst wird und dass ich unter Verwendung der Teilzeichenfolge match / delete am Ende der Zeichenfolge den ursprünglichen Dateipfad mit einer MP3-Datei wiedergeben sollte Erweiterung statt wma. Anstelle dieses Ergebnisses sehe ich jedoch Folgendes:

*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
...

Meine Frage (abgesehen von der spezifischen Frage, was ich hier falsch mache) lautet also: Müssen Werte, die das Ergebnis einer Pipe-Operation sind, bei String-Manipulationsoperationen anders behandelt werden als diejenigen, die das Ergebnis einer Variablenzuweisung sind ?

Howard Dierking
quelle
1
Es gibt keinen Grund, xargsmit zu verwenden find. Es kommt mit einer -execOption. Können Sie Ihrer Frage einfach den Befehl hinzufügen, den Sie verwenden möchten, und jemand kann Ihnen den richtigen findBefehl zeigen?
Ixtmixilix
Ja (wie unten erwähnt), solange ich noch die String-Manipulation für jedes Ergebnis des Befehls find durchführen kann (z. B. das {}Mitglied)
Howard Dierking
Tatsächlich gibt es Randfälle, in denen dies xargsbesser geeignet ist als exec. Ein Beispiel hierfür finden Sie in diesem Stackpost stackoverflow.com/questions/896808/find-exec-cmd-vs-xargs .
d34dh0r53
@ d34dh0r53 Welcher Randfall? Der Thread, auf den Sie verlinken, zeigt auf keinen.
Gilles 'SO - hör auf böse zu sein'

Antworten:

8

Wie andere Antworten bereits identifiziert haben, ${f%.*}wird diese von der Shell erweitert, bevor sie den xargsBefehl ausführt. Sie müssen diese Erweiterung für jeden Dateinamen einmal ausführen, wobei die Shell-Variable fauf den Dateinamen festgelegt ist (Übergabe -I ffunktioniert nicht: Hat xargskeine Vorstellung von einer Shell-Variablen, sucht nach der Zeichenfolge fim Befehl, wenn Sie dies möchten verwendet zB xargs -I e echo …hätte es Befehle wie ./somedir/somefile.wmacho .mp3) ausgeführt.

Wenn Sie diesen Ansatz beibehalten xargs, rufen Sie an, eine Shell aufzurufen, die die Erweiterung ausführen kann. Besser, sagen find- xargsist ein weitgehend veraltetes Werkzeug und schwer richtig zu verwenden, da moderne Versionen von find ein Konstrukt haben, das die gleiche Aufgabe (und mehr) mit weniger Installationsschwierigkeiten erledigt. Statt find … -print0 | xargs -0 command …laufen find … -exec command … {} +.

find . -name '*.wma' -type f -exec sh -c 'for f; do echo "${f%.*}.mp3"; done' _ {} +

Das Argument _ist $0in der Shell; Die Dateinamen werden als Positionsargumente übergeben, die sich in einer for f; do …Schleife befinden. Eine einfachere Version dieses Befehls führt für jede Datei eine separate Shell aus, die äquivalent, aber etwas langsamer ist:

find . -name '*.wma' -type f -exec sh -c 'echo "${0%.*}.mp3"' {} \;

Sie müssen es hier eigentlich nicht verwenden find, vorausgesetzt, Sie führen eine relativ neue Shell aus (ksh93, bash ≥4.0 oder zsh). Geben Sie in bash shopt -s globstaryour ein .bashrc, um das **/Glob-Muster für die Rekursion in Unterverzeichnissen zu aktivieren (in ksh also set -o globstar). Dann kannst du rennen

for f in **/*.wma; do
  echo "${f%.*}.mp3"
done

(Wenn Sie Verzeichnisse aufgerufen haben *.wma, fügen Sie diese [ -f "$f" ] || continueam Anfang der Schleife hinzu.)

Gilles 'SO - hör auf böse zu sein'
quelle
Tolle Erklärungen und Hinweise in eine Richtung, die ich noch nicht einmal erkannt hatte (Upgrade meiner Bash-Shell, um Globstar-Funktionalität zu erhalten) - am Ende war rekursives Globbing die Lösung, die den Befehl einfach hielt und alles erledigte, was ich versuchte . Vielen Dank!
Howard Dierking
6

Bei Ihrer Lösungsbewertung von $ {f%. *}. Mp3 wird in der Shell ausgeführt, in der Sie den gesamten Befehl aufrufen, nicht in der von den xargs gegabelten Shell . Und in Ihrer Shell gibt es keine f- Variable, sie wird durch eine leere Zeichenfolge ersetzt.

Lösung mit xargs mit -I f :

% cat 1.sh 
#!/bin/sh
f=$1
echo ${f%.*}.mp3
% /bin/ls -1
1.sh
aaa.wma
bbbb.wma
ccccc.wma
% find ./ -name '*.wma' -type f -print0 | xargs -0 -I f ./1.sh f
./aaa.mp3
./ccccc.mp3
./bbbb.mp3

Aber ich würde es so machen:

% find ./ -name '*.wma' -type f | sed 's,\.wma$,.mp3,'
0xFF
quelle