Manipola il nome del file inviato dal comando find


10

Sono relativamente nuovo in Bash e sto provando a fare qualcosa che in apparenza sembra piuttosto semplice: esegui trova su una gerarchia di directory per ottenere tutti i file * .wma, esegui l'output in un comando in cui li converto in mp3 e salva il file convertito come .mp3. Pensavo che il comando dovesse apparire come il seguente (ho lasciato fuori il comando di conversione audio e sto usando invece echo per l'illustrazione):

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

A quanto ho capito, l'argomento -print0 mi permetterà di gestire i nomi di file che hanno spazi (cosa che molti di questi fanno come file musicali). Mi aspetto quindi (come risultato di xargs) che ogni percorso del file da find sia catturato in f, e che usando la sottostringa match / delete dalla fine della stringa, che dovrei fare eco al percorso del file originale con un mp3 estensione invece di wma. Tuttavia, invece di questo risultato, sto vedendo quanto segue:

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

Quindi la mia domanda (a parte lo specifico 'cosa sto facendo di sbagliato qui'), è questa: i valori che sono il risultato di un'operazione di pipe devono essere trattati in modo diverso nelle operazioni di manipolazione di stringhe rispetto a quelli che sono il risultato di un'assegnazione variabile ?


1
Non c'è alcun motivo per utilizzare xargscon find. Viene fornito con -execun'opzione. Puoi semplicemente aggiungere il comando che intendi utilizzare alla tua domanda e qualcuno può mostrarti il findcomando corretto ?
ixtmixilix,

sì (come ho notato di seguito), purché riesca ancora a manipolare la stringa su ogni risultato del comando find (ad esempio il {}membro)
Howard Dierking

in realtà ci sono casi limite dove xargsè più adatto di exec. Vedere questo stackpost stackoverflow.com/questions/896808/find-exec-cmd-vs-xargs per un caso emblematico.
d34dh0r53,

@ d34dh0r53 What edge case? La discussione a cui ti colleghi non indica alcun punto.
Gilles 'SO- smetti di essere malvagio' il

Risposte:


8

Come altre risposte hanno già identificato, ${f%.*}viene espanso dalla shell prima di eseguire il xargscomando. È necessario che questa espansione avvenga una volta per ogni nome di file, con la variabile di shell fimpostata sul nome del file (passando -I fnon lo fa: xargsnon ha nozione di variabile di shell, cerca la stringa fnel comando, quindi se si usato ad es xargs -I e echo …. avrebbe eseguito comandi simili ./somedir/somefile.wmacho .mp3).

Mantenendo questo approccio, dire xargsdi invocare una shell in grado di eseguire l'espansione. Meglio, dire find- xargsè uno strumento in gran parte obsoleto ed è difficile da usare in modo corretto, come le versioni moderne di ritrovamento hanno un costrutto che fa lo stesso lavoro (e più) con un minor numero di problemi idraulici. Invece di find … -print0 | xargs -0 command …, corri find … -exec command … {} +.

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

L'argomento _è $0nella shell; i nomi dei file vengono passati come argomenti posizionali su cui for f; do …scorre. Una versione più semplice di questo comando esegue una shell separata per ciascun file, che è equivalente ma leggermente più lenta:

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

In realtà non è necessario utilizzarlo findqui, supponendo che si stia eseguendo una shell abbastanza recente (ksh93, bash ≥4,0 o zsh). In bash, inserisci il shopt -s globstartuo .bashrcper attivare il **/modello glob da ricorrere nelle sottodirectory (in ksh, cioè set -o globstar). Quindi puoi correre

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

(Se hai chiamato le directory *.wma, aggiungi [ -f "$f" ] || continueall'inizio del ciclo.)


grandi spiegazioni e mi hanno indicato una direzione che non avevo nemmeno realizzato (aggiornare la mia shell bash per ottenere la funzionalità globstar) - alla fine, il globbing ricorsivo era la soluzione che teneva il comando semplice e realizzava tutto ciò che stavo cercando di fare . Grazie!
Howard Dierking

6

Nel caso in cui la tua soluzione valuti $ {f%. *}. Mp3 viene eseguito nella shell in cui stai invocando l'intero comando, non nella shell biforcuta dagli xargs . E nella tua shell non c'è una variabile f , è sostituita da una stringa vuota.

Soluzione che utilizza xargs con -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

Ma lo farei così:

% find ./ -name '*.wma' -type f | sed 's,\.wma$,.mp3,'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.