¿Por qué no funciona find -exec mv {} ./target/ +?

98

Quiero saber exactamente qué {} \;y {} \+y | xargs ...hacer. Por favor aclare estos con explicaciones.

Los siguientes 3 comandos se ejecutan y generan el mismo resultado, pero el primer comando toma un poco de tiempo y el formato también es un poco diferente.

find . -type f -exec file {} \;
find . -type f -exec file {} \+
find . -type f | xargs file

Es porque el primero ejecuta el filecomando para cada archivo que proviene del findcomando. Entonces, básicamente se ejecuta como:

file file1.txt
file file2.txt

Pero los últimos 2 encuentran con los -execcomandos ejecutar el comando de archivo una vez para todos los archivos como se muestra a continuación:

file file1.txt file2.txt

Luego ejecuto los siguientes comandos en los que el primero se ejecuta sin problema, pero el segundo da un mensaje de error.

find . -type f -iname '*.cpp' -exec mv {} ./test/ \;
find . -type f -iname '*.cpp' -exec mv {} ./test/ \+ #gives error:find: missing argument to `-exec'

Para el comando con {} \+, me da el mensaje de error.

find: missing argument to `-exec'

¿porqué es eso? ¿Alguien puede explicar qué estoy haciendo mal?

Shahadat Hossain
fuente
La verdadera pregunta es simple, ¿por qué el primero funciona y el segundo no? (1) encontrar. -type f -iname ' .cpp' -exec mv {} ./test/ \; (2) encontrar. -type f -iname ' .cpp' -exec mv {} ./test/ \ +
Shahadat Hossain

Respuestas:

185

La página del manual (o el manual GNU en línea ) explica prácticamente todo.

buscar comando -exec {} \;

Para cada resultado, command {}se ejecuta. Todas las apariciones de {}son reemplazadas por el nombre del archivo. ;tiene como prefijo una barra inclinada para evitar que el shell lo interprete.

buscar comando -exec {} +

Cada resultado se agrega commandy se ejecuta posteriormente. Teniendo en cuenta las limitaciones de longitud del comando, supongo que este comando se puede ejecutar más veces, con la página del manual apoyándome:

el número total de invocaciones del comando será mucho menor que el número de archivos coincidentes.

Tenga en cuenta esta cita de la página del manual:

La línea de comando está construida de la misma manera que xargs construye sus líneas de comando

Es por eso que no se permiten caracteres entre {}y +excepto espacios en blanco. +hace que find detecte que los argumentos deben agregarse al comando como xargs.

La solución

Afortunadamente, la implementación de GNU de mvpuede aceptar el directorio de destino como argumento, con -tel parámetro más largo o con uno --target. Su uso será:

mv -t target file1 file2 ...

Tu findcomando se convierte en:

find . -type f -iname '*.cpp' -exec mv -t ./test/ {} \+

Desde la página del manual:

comando -exec;

Ejecutar comando; Es verdadero si se devuelve el estado 0. Todos los siguientes argumentos para encontrar se toman como argumentos del comando hasta que un argumento que consta de ';' se encuentra. La cadena `{} 'se reemplaza por el nombre del archivo actual que se procesa en todos los lugares donde aparece en los argumentos del comando, no solo en los argumentos donde está solo, como en algunas versiones de find. Es posible que sea necesario escapar de estas dos construcciones (con un `\ ') o entre comillas para protegerlas de la expansión del shell. Consulte la sección EJEMPLOS para ver ejemplos del uso de la opción -exec. El comando especificado se ejecuta una vez para cada archivo coincidente. El comando se ejecuta en el directorio de inicio. Hay problemas de seguridad inevitables en torno al uso de la acción -exec; debería utilizar la opción -execdir en su lugar.

-exec comando {} +

Esta variante de la acción -exec ejecuta el comando especificado en los archivos seleccionados, pero la línea de comando se construye agregando cada nombre de archivo seleccionado al final; el número total de invocaciones del comando será mucho menor que el número de archivos coincidentes. La línea de comando se construye de la misma manera que xargs construye sus líneas de comando. Solo se permite una instancia de "{}" dentro del comando. El comando se ejecuta en el directorio de inicio.

Lekensteyn
fuente
1
Sé realmente cómo funciona, he revisado este manual varias veces, pero tengo un mensaje de error al usar {} +, aunque funciona para {} \; y estoy usando Cygwin en Windows.
Shahadat Hossain
1
@Shahadat: ¿ha leído la parte anterior a "Desde la página del manual"? Ha puesto ./test/entre {}y +, pero no se permiten caracteres que no sean espacios en blanco entre estos.
Lekensteyn
estás diciendo que no debería poner ./test/ entre {} y +. Entonces, ¿cómo funcionará el comando mv? mv necesita una fuente que es {} y necesita un destino que es ./test/ y terminación con +. ¿Puedes escribir el comando que te parezca correcto?
Shahadat Hossain
@Shahadat: Veo lo que estás tratando de lograr. Windows es lento en la ejecución de programas, por lo que desea combinarlo en un solo comando. Agregaré una alternativa a la respuesta.
Lekensteyn
1
El +comando AFAIU es un poco extraño ya que pega los archivos al "final" (y no en lugar de {}), así que ¿por qué usarlo {}? Esto es confuso. Gracias por la -topción que no conocía, ¡parece que esa opción se creó como una solución a ese -exec +problema!
e2-e4
6

Encontré el mismo problema en Mac OSX , usando un shell ZSH : en este caso no hay -topción mv, así que tuve que encontrar otra solución. Sin embargo, el siguiente comando tuvo éxito:

find .* * -maxdepth 0 -not -path '.git' -not -path '.backup' -exec mv '{}' .backup \;

El secreto fue citar los frenos . No es necesario que las llaves estén al final del execcomando.

Probé en Ubuntu 14.04 (con shells BASH y ZSH ), funciona igual.

Sin embargo, cuando se usa el +signo, parece que tiene que estar al final del execcomando.

arvymetal
fuente
{}debe citarse en las conchas fishy rc, pero no en zsh, bashni en ninguna otra concha de las familias Bourne o csh.
Stephane Chazelas
@StephaneChazelas Sí, vuelto a probar en Ubuntu con bash, de hecho, las comillas no son necesarias. Curiosamente, tuve un problema si no los citaba en MacOS (usando zsh). Pero no tengo una Mac al alcance para intentarlo de nuevo ...
arvymetal
3

El equivalente estándar de find -iname ... -exec mv -t dest {} +para findimplementaciones que no son compatibles -inameo mvimplementaciones que no son compatibles -tes usar un shell para reordenar los argumentos:

find . -name '*.[cC][pP][pP]' -type f -exec sh -c '
  exec mv "$@" /dest/dir/' sh {} +

Al usar -name '*.[cC][pP][pP]', también evitamos depender de la configuración regional actual para decidir cuál es la versión en mayúsculas de co p.

Tenga en cuenta que +, al contrario que ;no es especial en ningún shell, por lo que no es necesario citarlo (aunque citar no hará daño, excepto, por supuesto, con shells como rcese que no se admiten \como operador de comillas).

El arrastre /en /dest/dir/es para que mvfalla con un error en lugar de cambiar el nombre foo.cppa /dest/diren el caso en el que sólo una cppse encontró archivo y /dest/dirno existe o no es un directorio (o enlace simbólico al directorio).

Stephane Chazelas
fuente
+1 ... la operación en shell como preliminar a la ejecución de un comando es realmente útil para una variedad de casos de uso ... agradable.
Cbhihe
0
find . -name "*.mp3" -exec mv --target-directory=/home/d0k/Музика/ {} \+
DarkLabs
fuente
Agregue alguna explicación a su respuesta para que otros puedan aprender de ella
Nico Haase
Debe responder la pregunta, que pidió una explicación. Solo código no es una respuesta.
Lajos Arpad
-1

no, la diferencia entre +y \;debe invertirse. +agrega los archivos al final del comando exec, luego ejecuta el comando exec y \;ejecuta el comando para cada archivo.

El problema es find . -type f -iname '*.cpp' -exec mv {} ./test/ \+que find . -type f -iname '*.cpp' -exec mv {} ./test/ + no debería ser necesario escapar de él o terminar el+

xargs no he usado en mucho tiempo, pero creo que funciona como +.

Mike Ramírez
fuente
También probé con esto pero recibí el mismo mensaje de error. Además, en todas partes encontré para usar solo + pero en mi cygwin tengo que usar \ + o "+" para trabajar.
Shahadat Hossain
oh, este es un entorno cygwin. Lo siento, entonces no sé, no uso el shell cygwin, solo uso un * nix.
Mike Ramirez
1
@Shahadat Hossain intento -name "*.cpp"Casi no uso -iname a menos que quiera hacer una búsqueda de expresiones regulares difícil, como -iname '??? work. * \. Cpp'
Mike Ramirez
1
@ Mike: Creo que no entiendes la diferencia entre -inamey -name. -inamees la versión que no distingue entre mayúsculas y minúsculas -namey no tiene diferencias en el manejo de las expresiones regulares. Sugiero probar los comandos antes de publicar, su comando también falla en mi shell.
Lekensteyn
1
@Lekensteyn Ya se estableció que ese era el caso antes de su comentario. Pensé que había reconocido a Shahadat antes de tu publicación, fue un simple "ok". No, no lo ejecuté manualmente, lo hice desde la parte superior de mi cabeza y rara vez uso esa forma de búsqueda de expresiones regulares con find. Era solo una cosa del tipo "podría ayudar".
Mike Ramirez