¿Cómo puedo hacer una operación de "copiar si se cambia"?

34

Me gustaría copiar un conjunto de archivos del directorio A al directorio B, con la advertencia de que si un archivo en el directorio A es idéntico a un archivo en el directorio B, ese archivo no debe copiarse (y, por lo tanto, su tiempo de modificación no debe ser actualizado). ¿Hay alguna manera de hacerlo con las herramientas existentes, sin escribir mi propio script para hacerlo?

Para elaborar un poco sobre mi caso de uso: estoy autogenerando un montón de .carchivos en un directorio temporal (por un método que tiene que generarlos incondicionalmente), y cuando los vuelva a generar, me gustaría copiar solo los que han cambiado al directorio de origen real, dejando intactos los que no han cambiado (con sus viejos tiempos de creación) para que makesepa que no es necesario volver a compilarlos. (Sin .cembargo, no todos los archivos generados son archivos, así que necesito hacer comparaciones binarias en lugar de comparaciones de texto).

(Como nota: esto surgió de la pregunta que hice en https://stackoverflow.com/questions/8981552/speeding-up-file-comparions-with-cmp-on-cygwin/8981762#8981762 , donde estaba intentando para acelerar el archivo de script que estaba usando para hacer esta operación, pero se me ocurre que realmente debería preguntar si hay una mejor manera de hacerlo que escribir mi propio script, especialmente porque cualquier forma simple de hacerlo en un shell el script invocará algo así como cmpen cada par de archivos, y comenzar todos esos procesos lleva demasiado tiempo).

Brooks Moses
fuente
1
Puede usar diff -qr dirA dirBpara ver qué archivos son únicos dirAy dirB, respectivamente.
1
@ brooks-moses, este es realmente un trabajo adecuado para ccache !
aculich
3
@hesse si desea mostrar los archivos únicos que puede usar diff, pero si desea ver qué ha cambiado, use rsync -avnco a lo largo rsync --archive --verbose --dry-run --checksum.
aculich

Respuestas:

29

rsync es probablemente la mejor herramienta para esto. Hay muchas opciones en este comando, así que lea la página de manual . Creo que quieres la opción --checksum o la --ignore-times

Adam Terrey
fuente
Debería haber notado que ya lo intenté, sin éxito. Ambas opciones solo afectan si rsync hace una copia, pero, incluso cuando no hace una copia, actualiza el tiempo de modificación del archivo de destino al mismo que el origen (si -tse especifica la opción) o al tiempo de sincronización (si -tno se especifica)
Brooks Moses el
44
@Brooks Moses: no lo hace. Al menos mi versión de rsyncno. Si hago esto: mkdir src dest; echo a>src/a; rsync -c src/* dest; sleep 5; touch src/a; rsync -c src/* dest, a continuación, stat dest/amuestra su -mtime y ctime son 5 segundos más antiguas que las de src/a.
angus
@angus: ¿Eh? De acuerdo, tienes razón. La clave parece ser la --checksumopción, y aunque linux.die.net/man/1/rsync no contiene absolutamente nada que implique que tenga algún efecto sobre si la fecha de modificación se actualiza, no obstante, deja la fecha de modificación de destino. intacto (Por otro lado, la --ignore-timesopción no tiene este efecto; con ella, la fecha de modificación aún se actualiza). Dado que esto parece estar completamente indocumentado, ¿puedo confiar en ello?
Brooks Moses el
2
@BrooksMoses: Creo que puede confiar en él: rsyncel flujo de trabajo es: 1) verificar si el archivo necesita ser actualizado; 2) si es así, actualice el archivo. La --checksumopción dice que no debe actualizarse, por lo rsyncque no debe continuar con el paso 2).
enzotib
2
@BrooksMoses: --ignore-timessin --checksumcopiaría todos los archivos, y también actualizaría la marca de tiempo, incluso si los archivos son idénticos.
enzotib
13

Puedes usar el -uinterruptor para que te cpguste:

$ cp -u [source] [destination]

Desde la página del manual:

   -u, --update
       copy only when the SOURCE file is newer than the destination file or 
       when the destination file is missing
gu1
fuente
44
Hola y bienvenidos al sitio. Esperamos que las respuestas sean un poco más sustanciales aquí. Por ejemplo, podría haber incluido una explicación de lo que hace la -ubandera y cómo funciona y cómo esto ayudaría al OP. Sin embargo, en este caso particular, no ayudaría al OP ya que copiaría archivos idénticos si fueran más nuevos y cambiaría sus marcas de tiempo, que es precisamente lo que el OP quiere evitar.
terdon
1
De un comentario sobre una A similar que ya se eliminó: "Esto no funcionará, ya que copiaría también archivos idénticos, si la marca de tiempo del origen es más reciente (y, por lo tanto, actualiza la marca de tiempo del destino, contra la solicitud de OP)".
slm
No responde la pregunta en absoluto, pero aún así la encuentro útil.
usuario31389
7

Si bien el uso rsync --checksumes una buena forma general de "copiar si se cambia", en su caso particular, ¡hay una solución aún mejor!

Si desea evitar la recompilación innecesaria de archivos, ¡debe usar ccache, que fue creado exactamente para este propósito! De hecho, no solo evitará recompilaciones innecesarias de sus archivos generados automáticamente, sino que también acelerará las cosas cada vez que lo haga make cleany volverá a compilar desde cero.

A continuación, estoy seguro de que preguntarás: "¿Es seguro?" Bueno, sí, como señala el sitio web:

¿Es seguro?

Sí. El aspecto más importante de un caché del compilador es producir siempre exactamente la misma salida que produciría el compilador real. Esto incluye proporcionar exactamente los mismos archivos de objetos y exactamente las mismas advertencias del compilador que se generarían si usa el compilador real. La única forma en que debería poder decir que está usando ccache es la velocidad.

Y es fácil de usar simplemente agregándolo como un prefijo en la CC=línea de su archivo MAKE (o puede usar enlaces simbólicos, pero la forma del archivo MAKE probablemente sea mejor).

aculich
fuente
1
Inicialmente entendí mal y pensé que estaba sugiriendo que use ccache para hacer parte de la generación, pero ahora entiendo: su sugerencia fue que simplemente copie todos los archivos y luego use ccache en el proceso de compilación, evitando así reconstruir los que No había cambiado. Es una buena idea, pero no me irá bien en mi caso: tengo cientos de archivos, generalmente solo cambio uno o dos a la vez, y me estoy ejecutando en Cygwin, donde simplemente inicio los cientos de procesos ccache para ver cada uno El archivo llevaría varios minutos. ¡Sin embargo, votó porque es una buena respuesta para la mayoría de las personas!
Brooks Moses el
No, no estaba sugiriendo que copie todos los archivos, sino que simplemente puede generar automáticamente sus archivos .c en el lugar (elimine el paso de copia y escríbalos directamente). Y luego solo usa ccache. No sé a qué te refieres con iniciar cientos de procesos ccache ... es solo un envoltorio liviano alrededor de gcc que es bastante rápido y también acelerará la reconstrucción de otras partes de tu proyecto. ¿Has intentado usarlo? Me gustaría ver una comparación del tiempo entre el uso de su método de copia vs ccache. De hecho, podría combinar los dos métodos para obtener los beneficios de ambos.
aculich
1
Bien, ahora entiendo sobre la copia. Para aclarar, lo que quiero decir es esto: si genero los archivos en su lugar, tengo que llamar ccache file.c -o file.oo su equivalente, varios cientos de veces porque hay varios cientos de file.carchivos. Cuando estaba haciendo eso con cmp, en lugar de hacerlo ccache, me llevó varios minutos, y cmpes tan liviano como ccache. El problema es que, en Cygwin, iniciar un proceso lleva un tiempo no despreciable, incluso para un proceso completamente trivial.
Brooks Moses
1
Como punto de datos, for f in src/*; do /bin/true.exe; donelleva 30 segundos, así que sí. De todos modos, prefiero mi editor basado en Windows, y aparte de este tipo de problema de sincronización, Cygwin funciona bastante bien con mi flujo de trabajo como el lugar liviano para probar cosas localmente si no estoy cargando en los servidores de compilación. Es útil tener mi shell y mi editor en el mismo sistema operativo. :)
Brooks Moses
1
Si desea utilizar su editor basado en Windows, puede hacerlo fácilmente con las carpetas compartidas si instala Guest Additions ... pero bueno, si Cygwin le conviene, ¿quién soy yo para decir algo diferente? Parece una pena tener que saltar a través de aros extraños como este ... y la compilación en general también sería más rápida en una máquina virtual.
aculich 01 de
3

Esto debería hacer lo que necesitas

diff -qr ./x ./y | awk '{print $2}' | xargs -n1 -J% cp % ./y/

Dónde:

  • x es tu carpeta actualizada / nueva
  • y es el destino al que desea copiar
  • awk tomará el segundo argumento de cada línea del comando diff (tal vez necesitará algunas cosas adicionales para los nombres de archivo con espacio; no puedo probarlo ahora)
  • xargs -J% insertará el nombre del archivo a cp en el lugar apropiado
Patkos Csaba
fuente
1
-1 porque esto es demasiado complicado, no portátil ( -Jes específico de bsd; con GNU xargs lo es -I), y no funciona correctamente si el mismo conjunto de archivos ya no existe en ambas ubicaciones (si touch x/booentonces grep me da Only in ./x: booque causa errores en la tubería). Use una herramienta creada para el trabajo, como rsync --checksum.
aculich
O mejor aún, para este caso específico, use ccache .
aculich
+1 porque es un conjunto de comandos bien conocidos que puedo romper para usar en tareas similares (vine aquí para hacer una diferencia), aún así rsync puede ser mejor para esta tarea en particular
ntg
3

Me gusta usar unison a favor rsyncporque es compatible con múltiples maestros, ya que configuré mis claves ssh y vpn por separado.

Entonces, en mi crontab de solo un host, los dejo sincronizar cada 15 minutos:

* / 15 * * * * [-z "$ (pidof unison)"] && (tiempo de espera 25m unison -sortbysize -ui text -batch -times / home / master ssh: //192.168.1.12//home/master -path dev -logfile /tmp/sync.master.dev.log) &> /tmp/sync.master.dev.log

Entonces puedo estar desarrollando en ambos lados y los cambios se propagarán. De hecho, para proyectos importantes, tengo hasta 4 servidores que reflejan el mismo árbol (3 ejecutan al unísono desde cron, señalando al que no lo hace). De hecho, los hosts Linux y Cygwin son mixtos, excepto que no espere sentido de enlaces blandos en win32 fuera del entorno cygwin.

Si sigue esta ruta, haga el espejo inicial en el lado vacío sin el -batch, es decir

unison -ui text  -times /home/master ssh://192.168.1.12//home/master -path dev

Por supuesto, hay una configuración para ignorar los archivos de respaldo, archivos, etc.

 ~/.unison/default.prf :
# Unison preferences file
ignore = Name {,.}*{.sh~}
ignore = Name {,.}*{.rb~}
ignore = Name {,.}*{.bak}
ignore = Name {,.}*{.tmp}
ignore = Name {,.}*{.txt~}
ignore = Name {,.}*{.pl~}
ignore = Name {.unison.}*
ignore = Name {,.}*{.zip}

    # Use this command for displaying diffs
    diff = diff -y -W 79 --suppress-common-lines

    ignore = Name *~
    ignore = Name .*~
    ignore = Path */pilot/backup/Archive_*
    ignore = Name *.o
Marcos
fuente
Miré eso, pero no pude encontrar una unisonopción que significa "no actualizar las fechas de última modificación del archivo". ¿Hay uno? De lo contrario, esta es una gran respuesta a un problema completamente diferente.
Brooks Moses
1
-timeshace eso por mi Unison también tiene un modo de marcha en seco, creo.
Marcos
Bueno, establecer times=false(o dejar -times) haría eso. No sé cómo me perdí eso en la documentación antes. ¡Gracias!
Brooks Moses
Encantado de ayudar. Soy muy exigente cuando se trata de preservar cosas como modtimes, permisos y enlaces blandos. A menudo se pasa por alto
Marcos
1

Si bien rsync --checksumes la respuesta correcta, tenga en cuenta que esta opción es incompatible --timesy que --archiveincluye --times, por lo que si lo desea rsync -a --checksum, realmente necesita hacerlo rsync -a --no-times --checksum.

Vladimir Kornea
fuente
¿Qué quieres decir con "incompatible"?
OV
¿Qué quieres decir con "es la respuesta correcta"?
thoni56