¿Cómo uso find cuando el nombre del archivo contiene espacios?

17

Quiero canalizar nombres de archivos a otros programas, pero todos se ahogan cuando los nombres contienen espacios.

Digamos que tengo un archivo llamado.

foo bar

¿Cómo puedo obtener findel nombre correcto?

Obviamente quiero:

foo\ bar

o:

"foo bar"

EDITAR : No quiero pasar xargs, quiero obtener una cadena con el formato correcto findpara poder canalizar la cadena de nombres de archivo directamente a otro programa.

error
fuente
55
¿a qué lo estás canalizando? ¿Conoces la -execbandera con find? potencialmente podría aliviar este error y hacer que su comando sea más eficiente al hacerlo en -execlugar de canalizarlo a otros comandos. Just my $ .02
h3rrmiller
66
@bug: findformatea bien los nombres de archivo; están escritos un nombre por línea. (Por supuesto, esto es ambiguo si un nombre de archivo contiene un carácter de nueva línea). Entonces, el problema es que el extremo receptor se "ahoga" cuando obtiene un espacio, lo que significa que debe decirnos cuál es el extremo receptor si desea una respuesta significativa. .
rici 01 de
2
Lo que llama "formateado correctamente" es realmente "escapado para el consumo por parte del shell". La mayoría de las utilidades que pueden leer un montón de nombres de archivo se ahogarían con un nombre escapado de shell, pero de hecho tendría sentido para (por ejemplo) findofrecer una opción para generar nombres de archivo en un formato adecuado para el shell. En general, sin embargo, la extensión -print0GNU findfunciona bien para muchos otros escenarios (también), y debe aprender a usarla en cualquier caso.
tripleee
2
@bug: Por cierto, ls $(command...)no alimenta la lista stdin. Pone la salida de $(command...)directamente en la línea de comando. En ese caso, es el shell el que está leyendo la c, y usará el valor actual de $IFSpara decidir cómo dividir las palabras en la salida. En general, es mejor usarlo xargs. No notará un golpe de rendimiento.
rici 01 de
2
find -printf '"%p"\n'agregará comillas dobles alrededor de cada nombre encontrado, pero no citará correctamente las comillas dobles en un nombre de archivo. Si los nombres de sus archivos no tienen comillas dobles incrustadas, puede ignorar el problema: o pasarlo sed 's/"/&&/g;s/^""/"/;s/""$/"/'. Si sus nombres de archivo terminan siendo manejados por el shell, probablemente debería usar comillas simples en lugar de comillas dobles (de lo contrario, sweet$HOMEse convertirá en algo así sheet/home/you). Y esto todavía no es muy robusto contra los nombres de archivo con nuevas líneas en ellos. ¿Cómo quieres manejar eso?
tripleee

Respuestas:

18

POSIXLY:

find . -type f -exec sh -c '
  for f do
    : command "$f"
  done
' sh {} +

Con findsoportes -print0y xargssoportes -0:

find . -type f -print0 | xargs -0 <command>

-0 la opción le dice a xargs que use el carácter ASCII NUL en lugar de espacio para finalizar (separar) los nombres de archivo.

Ejemplo:

find . -maxdepth 1 -type f -print0 | xargs -0 ls -l
Cuonglm
fuente
No funciona Cuando corro ls $(find . -maxdepth 1 -type f -print0 | xargs -0)tengo ls: cannot access ./foo: No such file or directory ls: cannot access bar: No such file or directory
bug
1
¿Lo has intentado como Gnouc lo escribió realmente? Si insiste en hacerlo a su manera, intente encerrarlo $(..)entre comillas dobles"$(..)"
evilsoup
3
@bug: tu comando está equivocado. Intente exactamente Irte y lea la página de manual de findy xargs.
Cuonglm 01 de
Ya veo, nuevamente quiero obtener una cadena formateada que pueda canalizar directamente.
error
1
@bug: solo usa xargs -0 <tu programa>
cuonglm
10

El uso -print0es una opción, pero no todos los programas admiten el uso de flujos de datos delimitados por nullbytes, por lo que tendrá que usar xargsla -0opción para algunas cosas, como señaló la respuesta de Gnouc.

Una alternativa sería usar find's -execu -execdiropciones. El primero de los siguientes alimentará los nombres de archivo a somecommanduno a la vez, mientras que el segundo se expandirá a una lista de archivos:

find . -type f -exec somecommand '{}' \;
find . -type f -exec somecommand '{}' +

Es posible que en muchos casos sea mejor usar el globbing. Si tiene un shell moderno (bash 4+, zsh, ksh), puede obtener un engrosamiento recursivo con globstar( **). En bash, debes configurar esto:

shopt -s globstar
somecommand ./**/*.txt ## feeds all *.txt files to somecommand, recursively

Tengo una línea que dice shopt -s globstar extgloben mi .bashrc, por lo que esto siempre está habilitado para mí (y también lo son los globos extendidos, que también son útiles).

Si no desea la recursividad, obviamente solo use ./*.txten su lugar, para usar cada * .txt en el directorio de trabajo. findtiene algunas capacidades de búsqueda detallada muy útiles, y es obligatorio para decenas de miles de archivos (en ese punto, se encontrará con el número máximo de argumentos del shell), pero para el uso diario a menudo es innecesario.

maldad
fuente
Hola @evilsoup, ¿qué hace el {} en este script?
Ayusman
3

Personalmente, usaría la -execacción de búsqueda para resolver este tipo de problema. O, si es necesario, lo xargsque permite la ejecución paralela.

Sin embargo, hay una manera de llegar finda producir una lista de nombres de archivos legibles por bash. Como era de esperar, utiliza -execy bash, en particular, una extensión del printfcomando:

find ... -exec bash -c 'printf "%q " "$@"' printf {} ';'

Sin embargo, si bien eso imprimirá correctamente las palabras con escape de shell, no será utilizable $(...), ya $(...)que no interpreta las comillas o los escapes. (El resultado $(...)está sujeto a la división de palabras y la expansión del nombre de ruta, a menos que esté entre comillas). Por lo tanto, lo siguiente no hará lo que desea:

ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)

Lo que deberías hacer es:

eval "ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)"

(Tenga en cuenta que no he hecho ningún intento real de probar la monstruosidad anterior).

Pero entonces también podrías hacer:

find ... -exec ls {} +
rici
fuente
No creo que el lsescenario capture adecuadamente el caso de uso del OP, pero esto es solo especulación, ya que no se nos ha mostrado lo que realmente está tratando de lograr. Esta solución en realidad funciona muy bien; Obtengo el resultado que esperaba (vagamente) para todos los nombres de archivos divertidos que intenté, incluidotouch "$(tr a-z '\001-\026' <<<'the quick brown fox jumped over the lazy dogs')"
tripleee
@triplee: No tengo idea de lo que OP quiere hacer tampoco. La única ventaja real de construir la cadena entre comillas para pasar evales que todavía no tiene que pasarla eval; puede guardarlo en un parámetro y usarlo más tarde, tal vez varias veces con diferentes comandos. Sin embargo, la OP no da ninguna indicación de que ese es el caso de uso (y si lo fuera, tal vez sería mejor poner los nombres de archivo en una matriz, aunque eso es difícil también.)
RICI
0
find ./  | grep " "

obtendrá los archivos y directorios que contiene espacios

find ./ -type f  | grep " " 

te conseguirá los archivos contiene espacios

find ./ -type d | grep " "

obtendrá los directorios contiene espacios

Kannan Kumarasamy
fuente
-2
    find . -type f -name \*\  | sed -e 's/ /<thisisspace>/g'
usuario283965
fuente
Esta es una respuesta interesante, pero no es una respuesta a esta pregunta.
Scott