Bash: copia los archivos nombrados de forma recursiva, conservando la estructura de la carpeta

103

Estaba esperando:

cp -R src/prog.js images/icon.jpg /tmp/package

produciría una estructura simétrica en el directorio de destino:

/tmp
|
+-- package
    |
    +-- src
    |   |
    |   +-- prog.js
    |
    +-- images
        |
        +-- icon.jpg

pero, en cambio, ambos archivos se copian en / tmp / package. Una copia plana. (Esto está en OSX).

¿Existe una función bash simple que pueda usar para copiar todos los archivos, incluidos los archivos especificados con comodines (por ejemplo, src / *. Js) en el lugar que les corresponde dentro del directorio de destino? Un poco como "para cada archivo, ejecutemkdir -p $(dirname "$file"); cp "$file" $(dirname "$file") ", pero quizás un solo comando.

Este es un hilo relevante, lo que sugiere que no es posible. Sin embargo, la solución del autor no es tan útil para mí, porque me gustaría simplemente proporcionar una lista de archivos, comodines o no, y copiarlos todos en el directorio de destino. IIRC MS-DOS xcopy hace esto, pero parece que no hay equivalente para cp.

mahemoff
fuente

Respuestas:

154

¿Ha intentado utilizar la opción --parents? No sé si OS X lo admite, pero funciona en Linux.

cp --parents src/prog.js images/icon.jpg /tmp/package

Si eso no funciona en OS X, intente

rsync -R src/prog.js images/icon.jpg /tmp/package

como sugirió aif.

ustun
fuente
4
Gracias. resulta que "cp --parents" no es posible en mac, pero es bueno conocer la bandera de otros Unixen. rsync -R es la solución portátil más simple para este problema.
mahemoff
1
Acepté este por su elegancia / memorabilidad, pero acabo de descubrir que no copia directorios completos (al menos en OSX), mientras que el tar de abajo lo hace.
mahemoff
cp --parentses una opción ilegal en OSX (BSD cp), pero gcp(GNU cp) funciona bien. Si aún no está en su sistema, utilice brew install coreutils. U tendrá muchas utilidades con prefijo g.
kyb
@mahemoff cp -R --parentsy rsync -rRcopia relativamente tanto archivos como directorios.
Vortico
22

De una sola mano:

tar cf - <files> | (cd /dest; tar xf -)
Randy Proctor
fuente
oh, me gusta mucho más esto que mi respuesta.
EMPraptor
4
También puede usar la -Copción para hacer el chdir por usted, tar cf - _files_ | tar -C /dest xf -o algo así.
D.Shawley
gracias, esto es conciso, aunque prefiero rsync por simplicidad.
mahemoff
1
¡Excelente! ¿Alguien sabe cómo convertir esto en un comando de shell? Debe aceptar N entradas, las primeras N-1 son los archivos a copiar y la última es la carpeta de destino.
2012
@arod $ {! #} es el último parámetro y utilícelo para obtener los argumentos anteriores stackoverflow.com/questions/1215538/… . Si escribe el comando, enlace a la esencia aquí.
mahemoff
18

Alternativamente, si eres de la vieja escuela, usa cpio:

cd /source;
find . -print | cpio -pvdmB /target

Claramente, puede filtrar la lista de archivos al contenido de su corazón.

La opción '-p' es para el modo 'pass-through' (frente a '-i' para entrada o '-o' para salida). La '-v' es detallada (enumera los archivos a medida que se procesan). La '-m' conserva los tiempos de modificación. La '-B' significa usar 'bloques grandes' (donde los bloques grandes son 5120 bytes en lugar de 512 bytes); es posible que no tenga ningún efecto en estos días.

Jonathan Leffler
fuente
1
Es mejor usar -print0con la combinación de --nullopción para que no se rompa con caracteres especiales y tal:find . -print0 | cpio -pvdmB --null /target
haridsv
Llámelo de la vieja escuela, llámelo portátil, llámelo como quiera, pero definitivamente confío cpioen esta tarea. Estoy de acuerdo en que se deben usar las opciones -print0y -null, de lo contrario, en algún momento, alguien le dará algunas carpetas con 'caracteres especiales' (espacios, lo más probable) y algo sucederá. No es que esté hablando por experiencia personal, pero puede intentar hacer una copia de seguridad de un montón de archivos y terminar con la mitad de ellos respaldados debido a que hay espacios en los nombres de los archivos. (Está bien, estoy hablando por experiencia personal.)
bballdave025
@ bballdave025: Solo tiene problemas con cpiocomo se muestra si los nombres de archivo contienen líneas nuevas; entonces normalmente termina con un mensaje de error sobre dos (o más) nombres de archivo que no se encuentran para cada línea nueva en un nombre de archivo. (A veces, es posible que reciba menos mensajes, pero eso requiere un cuidado considerable al construir el caso de prueba). Cuando lo usé cpio, no había --nullopción; Las opciones de doble guión no formaban parte de las notaciones de opciones de SVR4 y el concepto de -print0no estaba presente en findninguna de las dos. Pero eso fue hace mucho tiempo (a mediados de los 90, por ejemplo, antes de que Linux alcanzara el dominio).
Jonathan Leffler
Gracias por los detalles, @Jonathan_Leffler. Me encanta aprender cosas aquí. Ahora, estoy tratando de recordar qué error de copia recursiva cometí que me dio problemas con los espacios; hay muchas formas en que pude haber cometido ese error,
bballdave025
@ bballdave025— Una posibilidad es usar xargsen la mezcla - se divide en espacios en blanco - espacios en blanco, tabulaciones, nuevas líneas. OTOH, no estoy seguro de cómo o por qué harías eso. El cpiomanual de GNU en el modo de salida es claro acerca de un nombre de archivo por línea. El manual de usuario del SVR4 (impreso en 1990) es más vago: cpio -o(modo de copia) lee la entrada estándar para obtener una lista de nombres de ruta y copia esos archivos en la salida estándar junto con el nombre de ruta y la información de estado. Por lo tanto, existe la posibilidad de que haya roto nombres en espacios.
Jonathan Leffler
16

La opción -R de rsync hará lo que esperas. Es una copiadora de archivos con muchas funciones. Por ejemplo:

$ rsync -Rv src/prog.js images/icon.jpg /tmp/package/
images/
images/icon.jpg
src/
src/prog.js

sent 197 bytes  received 76 bytes  546.00 bytes/sec
total size is 0  speedup is 0.00

Resultados de muestra:

$ find /tmp/package
/tmp/package
/tmp/package/images
/tmp/package/images/icon.jpg
/tmp/package/src
/tmp/package/src/prog.js
Ryan Bright
fuente
1

Tratar...

for f in src/*.js; do cp $f /tmp/package/$f; done

así que por lo que estabas haciendo originalmente ...

for f in `echo "src/prog.js images/icon.jpg"`; do cp $f /tmp/package/$f; done

o

v="src/prog.js images/icon.jpg"; for f in $v; do cp $f /tmp/package/$f; done
EMPraptor
fuente
No necesitas echoni $vaquí. Además, este método fallará si el directorio correspondiente no existe en el destino.
Weijun Zhou