Manipule el nombre del archivo canalizado desde el comando find

10

Soy relativamente nuevo en Bash y estoy tratando de hacer algo que en la superficie parecía bastante sencillo: ejecutar find en una jerarquía de directorios para obtener todos los archivos * .wma, canalizar esa salida a un comando donde los convierto a mp3 y guarde el archivo convertido como .mp3. Pensé que el comando debería tener el siguiente aspecto (he dejado el comando de conversión de audio y en su lugar estoy usando echo como ilustración):

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

Según tengo entendido, el argumento -print0 me permitirá manejar nombres de archivos que tienen espacios (que muchos de estos hacen como archivos de música). Entonces espero (como resultado de xargs) que cada ruta de archivo de find se capture en f, y que usando la coincidencia / eliminación de la subcadena desde el final de la cadena, debería hacer eco de la ruta de archivo original con un mp3 extensión en lugar de wma. Sin embargo, en lugar de este resultado, estoy viendo lo siguiente:

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

Entonces, mi pregunta (aparte del 'qué estoy haciendo mal aquí') es esta: los valores que son el resultado de una operación de tubería deben tratarse de manera diferente en las operaciones de manipulación de cadenas que los que son el resultado de una asignación variable ?

Howard Dierking
fuente
1
No hay razón para usar xargscon find. Viene con una -execopción. ¿Puedes agregar el comando que vas a usar a tu pregunta y alguien puede mostrarte el findcomando correcto ?
ixtmixilix
sí (como señalé a continuación), siempre que pueda hacer la manipulación de la cadena en cada resultado del comando de búsqueda (por ejemplo, el {}miembro)
Howard Dierking
en realidad hay casos extremos donde xargses más adecuado que exec. Vea este stackpost stackoverflow.com/questions/896808/find-exec-cmd-vs-xargs para ver un caso en cuestión.
d34dh0r53
@ d34dh0r53 ¿Qué caso límite? El hilo al que enlaza no señala ninguno.
Gilles 'SO- deja de ser malo'

Respuestas:

8

Como otras respuestas ya han identificado, ${f%.*}el shell lo expande antes de ejecutar el xargscomando. Necesita que esta expansión suceda una vez para cada nombre de archivo, con la variable de shell festablecida en el nombre del archivo (pasar -I fno hace eso: xargsno tiene noción de variable de shell, busca la cadena fen el comando, por lo que si desea usado, por ejemplo xargs -I e echo …, habría ejecutado comandos como ./somedir/somefile.wmacho .mp3).

Siguiendo con este enfoque, diga xargsque invoque un shell que pueda realizar la expansión. Mejor, diga find: xargses una herramienta en gran parte obsoleta y es difícil de usar correctamente, ya que las versiones modernas de find tienen una construcción que hace el mismo trabajo (y más) con menos dificultades de plomería. En lugar de find … -print0 | xargs -0 command …correr find … -exec command … {} +.

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

El argumento _está $0en la cáscara; los nombres de archivo se pasan como argumentos posicionales que se for f; do …repiten. Una versión más simple de este comando ejecuta un shell separado para cada archivo, que es equivalente pero un poco más lento:

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

En realidad, no necesita usar findaquí, suponiendo que esté ejecutando un shell razonablemente reciente (ksh93, bash ≥4.0 o zsh). En bash, pon shopt -s globstartu .bashrcpara activar el **/patrón glob para que se repita en subdirectorios (en ksh, eso es set -o globstar). Entonces puedes correr

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

(Si tiene directorios llamados *.wma, agregue [ -f "$f" ] || continueal comienzo del ciclo).

Gilles 'SO- deja de ser malvado'
fuente
excelentes explicaciones, así como también me apuntaron en una dirección que ni siquiera me había dado cuenta (mejorando mi shell bash para obtener la funcionalidad globstar): al final, el globado recursivo fue la solución que mantuvo el comando simple y logró todo lo que estaba tratando de hacer . ¡Gracias!
Howard Dierking
6

En el caso de la evaluación de su solución de $ {f%. *}. Mp3 se realiza en el shell en el que está invocando el comando completo, no en el shell bifurcado por los xargs . Y en su shell no hay una variable f , se sustituye por una cadena vacía.

Solución usando 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

Pero lo haría de esta manera:

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