find -exec en script bash con expansión variable

14

Estoy tratando de ejecutar un comando similar al siguiente en un script bash. Debe buscar a través de todas las subcarpetas $sourcediry copiar todos los archivos de cierto tipo al nivel raíz de $targetdir.

#!/bin/bash

# These are set as arguments to the script, not hard-coded
sourcedir="/path/to/sourcedir"
targetdir="/path/to/targetdir"

find "$sourcedir" -type f -name "*.type" -exec sh -c 'cp "$1" "$2/`basename "$1"`"' "{}" "$targetdir" \;

Esto parece estar bastante cerca, excepto que {}no se pasa en cuanto $2a-exec sh -c ...

Me gustaría hacer esto lo más cerca posible de la "manera correcta", con tolerancia para caracteres especiales en nombres de archivo (específicamente caracteres de comillas simples).

Editar: veo gente sugiriendo usar xargso encadenar argumentos. Tenía la impresión de que esto solo está bien para un número limitado de argumentos. Si tengo, por ejemplo, miles de archivos .jpg que intento copiar de varios directorios de galería en un directorio de presentación de diapositivas gigante, ¿seguirán funcionando las soluciones que encadenan argumentos?

Edición 2: mi problema era que faltaba un _antes de mi primera opción para sh en el -execcomando. Para cualquiera que tenga curiosidad sobre cómo hacer que el comando find funcione, agregue _y todo estará bien:

find "$sourcedir" -type f -name "*.type" -exec sh -c 'cp "$1" "$2"' _ "{}" "$targetdir" \;

Sin embargo, he aceptado una respuesta a continuación porque cumple la misma tarea, pero es más eficiente y elegante.

Mateo
fuente
44
Esa es exactamente la razón xargspor la que alguna vez se creó, para manejar automáticamente grandes cantidades de argumentos en comandos regulares que tenían límites. También a tener en cuenta, la mayoría de los límites máximos de argumentos, se han mejorado enormemente para las utilidades GNU estándar. También verá un beneficio de rendimiento, evitando todas esas bifurcaciones de proceso, que en miles de archivos es relevante.
JM Becker
Con gnu-find y + en lugar de ";", puede manejar múltiples argumentos a la vez con find también. Y guarda el argumento complicado que pasa con -print0.
usuario desconocido
@userunknown: estoy respondiendo a esto debajo de tu respuesta.
JM Becker
@user unknown Bueno, ME ENCANTA este código. Es al menos totalmente compatible con POSIX y funcionará sin nada de GNU en la máquina. Hay aquellos momentos en que no necesita esto, sobre todo en los servidores en el trabajo.
syntaxerror

Respuestas:

6

¿Desea copiar archivos de un tipo específico a un directorio específico? Esto se hace mejor con xargs, y ni siquiera lo necesitas sh. Esta es una forma más apropiada de hacerlo, también debería ejecutarse de manera más eficiente.

find "$sourcedir" -type f -name "*.type" | xargs cp -t targetdir

Si necesita manejar nombres de archivo especiales, utilícelos NULLcomo separador

find "$sourcedir" -type f -name "*.type" -print0 | xargs -0 cp -t "$targetdir"
JM Becker
fuente
1
Para el segundo caso, no se olvide de añadir -print0a findy -0axargs
SiegeX
@ SiegeX, ya estaba haciendo eso, antes de notar tu comentario.
JM Becker
Además, NULLno es necesario si lo usa '{}', es importante cuando no lo hace. El beneficio real entre uno de otro, aparte del POSIXcumplimiento, es el rendimiento.
JM Becker
6

Debe pasar {}como argumento al shell y luego recorrer cada arg.

find "$sourcedir" -type f -name "*.type" -exec sh -c 'for f; do cp "$f" "$0"; done' "$targetdir" {} +

Nota : La forma en que esto funciona es que el primer argumento para un shell es el nombre del shell , podemos explotar esto pasando el nombre como $targetdiry luego usar el parámetro especial $0dentro del script del shell para acceder a ese targetdir.

SiegeX
fuente
1
"$targetdir"no se expande entre comillas simples.
enzotib
5

Si no crees en la iglesia de xargs:

find "$sourcedir" -type f -name "*.mp3" -exec cp -t "$targetdir" {} +

Explicación:

cp -t a b c d 

copia b, cyd al directorio de destino a.

-exec cmd {} +

invoca el comando en un gran grupo de archivos a la vez, no uno tras otro (que es estándar, si lo usa en ";"lugar de +). Es por eso que tiene que tirar del targetdir al frente y marcarlo explícitamente como objetivo.

Esto funciona para gnu-find y podría no ser para otras implementaciones de find. Por supuesto, también se basa en la bandera -t.

TechZilla, por supuesto, está en el extremo derecho, ya shque no es necesario invocar cp.

Si no lo usa xargs, lo cual es innecesario en la mayoría de los casos en combinación find, se salvará de aprender el indicador -print0y -0.

usuario desconocido
fuente
1
Obviamente, qué método cualquiera podría preferir es algo subjetivo. Dicho esto, existen razones para seguir utilizando xargs. El mayor ejemplo, ¿qué pasa si no está encontrando archivos? Uso xargs en lugar de forbucles generales todo el tiempo, findes mucho más pequeño en alcance. Además, findsolo admite que +si está utilizando GNU find, no está definido en POSIX. Entonces, si bien debe sentirse libre de preferir findsolo, no hace todo lo que hace xargs. Cuando consideras GNU xargstambién obtienes lo -Pque hace multi-core. Vale la pena aprender de todos xargsmodos.
JM Becker
Subjetividad hablando, mi opinión obviamente difiere. Objetivamente, por otro lado, su respuesta también es correcta. Es una de las pocas mejores soluciones disponibles.
JM Becker
@TechZilla: Espero recordar volver a visitar este sitio cuando find comience a admitir la invocación paralela. :) En la mayoría de los casos con copia / movimiento, la velocidad del disco será el factor limitante, pero los SSD pueden cambiar la imagen. Tienes razón, que proporcionar una solución que no sea GNU es algo bueno. De lo contrario, la solución de búsqueda es objetivamente más corta, más simple y utiliza solo dos procesos.
usuario desconocido
0
read -p "SOURCE: " sourcedir
read -p "TYPE: " type
read -p "TARGET: " targetdir
find -L $sourcedir -iname "*.$type" -exec cp -v {} $targetdir \;
Berthad33
fuente
3
Considere agregar alguna explicación de su solución.
HalosGhost