¿Cómo pasar el resultado de `find` como una lista de archivos?

11

La situación es que tengo un reproductor de MP3 mpg321que acepta una lista de archivos como argumento. Mantengo mi música en un directorio llamado "música", en el que hay algunos directorios más. Solo quiero jugar a todos, así que ejecuto el programa con

mpg321 $(find /music -iname "*\.mp3")

. El problema es que algunos nombres de archivos tienen espacios en blanco, y el programa divide esos nombres en partes más pequeñas y se queja de la falta de archivos. Envolviendo el resultado de findentre comillas

mpg321 "$(find /music -iname "*\.mp3")"

no ayuda porque todo se convertirá en un gran "nombre de archivo", que obviamente no se encuentra.

¿Cómo puedo hacer esto entonces? Si eso es importante, lo estoy usando bash, pero lo cambiaré zshpronto.

phunehehe
fuente

Respuestas:

17

Intente usar find's -print0u -printfoption en combinación con xargsesto:

find /music -iname "*\.mp3" -print0 | xargs -0 mpg321

Cómo funciona esto se explica en la página del manual de find :

-print0

Cierto; imprima el nombre completo del archivo en la salida estándar, seguido de un carácter nulo (en lugar del carácter de nueva línea que utiliza -print). Esto permite que los nombres de archivos que contienen líneas nuevas u otros tipos de espacios en blanco sean interpretados correctamente por los programas que procesan la salida de búsqueda. Esta opción corresponde a la opción -0 de xargs.

Steven D
fuente
Perdón por toda la edición. Me parece recordar que el patrón -print0 xarg -0 falla en otros tipos de espacios en blanco, pero no pude encontrar un ejemplo.
Steven D
oh si eso funciona! pero todavía me pregunto por qué mpg321 $(find /music -iname "*\.mp3" -print0)no?
phunehehe
1
Bueno, creo que eso no funciona por dos razones: (1) los nombres de archivo están separados por caracteres nulos, que no es lo que mpg321 espera en absoluto y (2) xargs está haciendo el trabajo de escapar del otro espacio en blanco, no encontrar.
Steven D
2
@phunehehe, @Steven: mpg321no tiene nada que ver con eso, es el shell que está dividiendo la salida finden argumentos separados. Y -print0 | xargs -0funcionará con todos los nombres de archivo posibles.
Gilles 'SO- deja de ser malvado'
8
find /music -iname "*\.mp3" -exec mpg123 {} +

Con GNU find, también puede usar -print0yxargs -0 , pero no tiene mucho sentido aprender otra herramienta más. La -exec ... {} +sintaxis recibe poca mención porque Linux la adquirió más tarde -print0, pero no hay razón para no usarla ahora.

Con zsh o bash 4, esto es mucho más simple:

mpg123 **/*.[Mm][Pp]3

Solo en zsh, puede hacer que (parte de un) patrón no distinga entre mayúsculas y minúsculas:

mpg123 (#i)**/*.mp3
Gilles 'SO- deja de ser malvado'
fuente
+1 a esto, "find -print0" es un truco feo.
p-static
2

Creo que la solución de Steven es la mejor, pero otra forma es usar la -Ibandera de xargs , que le permite especificar una cadena que luego se reemplazará en el comando con el argumento (en lugar de simplemente agregar el argumento al final del comando). Puede usar eso para citar el argumento:

find /music -iname "*\.mp3" | xargs -0 -Ifoo mpg321 "foo"
Michael Mrozek
fuente
No entiendo esta respuesta ... Estás usando la -0bandera de xargs sin encontrar -print0. Además, la -Ibandera de xargs tiene una serie de consecuencias. A diferencia de simple, xargsque comprimirá todas las líneas de stdin en un comando, xargs -Ise ejecutará mpg321potencialmente cientos o miles de veces (una vez para cada archivo), lo que ciertamente no es la intención. También tenga en cuenta que citar a foo es innecesario, como lo xargshace internamente. Observe que si comprime todos los nombres de archivo en la línea de línea y lo envía xargs -I, no se abrirán.
Seis
1

Por lo general, es mejor hacerlo directamente, -exec ${tgt_process} \{\} +pero si necesita obtener una lista de nombres de archivo delimitada de manera confiable en un archivo o transmisión findpor cualquier razón, puede hacer esto:

find -exec sh -c 'printf "///%s///\n" "$@"' -- \{\} +

Lo que obtienes de eso son dos cadenas únicas . En la parte superior de cada nombre de archivo está la cadena \n///y en la cola de cada nombre de archivo está la cadena ///\n. Estas dos cadenas no aparecen en ningún otro lugar de findla salida, excepto en esas posiciones, independientemente de los caracteres que contengan los nombres de archivo.

Además, el uso anterior es POSIX de línea base portátil y se puede confiar en que funcione en casi cualquier sistema Unix. Esto no es cierto para el uso de un delimitador de byte nulo, a pesar de su conveniencia, recomendado por algunos otros.

Pero, de nuevo, esto sólo es necesario si se puede no directamente -execa su $tgt_processpor cualquier razón, como que debe ser su objetivo. Por un lado, el método anterior aún requiere análisis. Por ejemplo, si desea que se cite cada shell de nombre de archivo, primero debe asegurarse de que se escapen las comillas en el nombre de archivo:

find ... + | sed 's|'\''|&"&"&|g;s|///|'\''|g'

Eso genera una matriz de nombres de archivo con escape de shell correctamente, independientemente de cuáles sean sus caracteres constitutivos. Ahora solo tiene que esperar que su aplicación en el extremo receptor no la destruya.

mikeserv
fuente
0

Otra forma de hacerlo es escapar de todos los caracteres especiales que aparecen en los nombres de archivo. P.ej:

find /music -iname "*\.mp3" | sed 's!\([] \*\$\/&[]\)!\\\1!g' | xargs mpg321

Básicamente, esto pasará los nombres de archivo correctamente escapados a xargs para su ejecución y no habrá ningún problema.

Priyabrata Patnaik
fuente
1
Esto todavía se rompe en las nuevas líneas en los nombres de archivo. Y también podría ser así sed 's|.|\\&|g': eso es lo que POSIX recomienda de todos modos.
mikeserv