Preserve la estructura de directorios al mover archivos usando find

22

He creado el siguiente script que mueve los archivos de antaño tal como se define desde el directorio de origen al directorio de destino. Funciona perfectamente

#!/bin/bash

echo "Enter Your Source Directory"
read soure

echo "Enter Your Destination Directory"
read destination 

echo "Enter Days"
read days



 find "$soure" -type f -mtime "-$days" -exec mv {} "$destination" \;

  echo "Files which were $days Days old moved from $soure to $destination"

Este script mueve muy bien los archivos, también mueve archivos del subdirectorio de origen, pero no crea el subdirectorio en el directorio de destino. Quiero implementar esta característica adicional en él.

con ejemplo

/home/ketan     : source directory

/home/ketan/hex : source subdirectory

/home/maxi      : destination directory

Cuando ejecuto este script, también mueve los archivos hexadecimales en el directorio maxi, pero necesito que ese mismo hex se cree en el directorio maxi y mueva sus archivos allí en el mismo hex.

Ketan Patel
fuente

Respuestas:

13

En lugar de ejecutarse mv /home/ketan/hex/foo /home/maxi, deberá variar el directorio de destino en función de la ruta producida por find. Esto es más fácil si primero cambia al directorio de origen y ejecuta find .. Ahora puede simplemente anteponer el directorio de destino a cada elemento producido por find. Deberá ejecutar un shell en el find … -execcomando para realizar la concatenación y crear el directorio de destino si es necesario.

destination=$(cd -- "$destination" && pwd) # make it an absolute path
cd -- "$source" &&
find . -type f -mtime "-$days" -exec sh -c '
  mkdir -p "$0/${1%/*}"
  mv "$1" "$0/$1"
' "$destination" {} \;

Tenga en cuenta que para evitar problemas de citas si $destinationcontiene caracteres especiales, no puede simplemente sustituirlo dentro del script de shell. Puede exportarlo al entorno para que llegue al shell interno, o puede pasarlo como un argumento (eso es lo que hice). Puede ahorrar un poco de tiempo de ejecución agrupando shllamadas:

destination=$(cd -- "$destination" && pwd) # make it an absolute path
cd -- "$source" &&
find . -type f -mtime "-$days" -exec sh -c '
  for x do
    mkdir -p "$0/${x%/*}"
    mv "$x" "$0/$x"
  done
' "$destination" {} +

Como alternativa, en zsh, puede utilizar la zmvfunción , y las .y m los calificadores glob para solo los archivos normales en el intervalo de fechas derecha. Deberá pasar una mvfunción alternativa que primero crea el directorio de destino si es necesario.

autoload -U zmv
mkdir_mv () {
  mkdir -p -- $3:h
  mv -- $2 $3
}
zmv -Qw -p mkdir_mv $source/'**/*(.m-'$days')' '$destination/$1$2'
Gilles 'SO- deja de ser malvado'
fuente
for x do, tienes una falta ;allí :). Además, no tengo idea de lo que querías lograr, $0pero estoy bastante convencido de que sería sh:).
Michał Górny
@ MichałGórny for x; dotécnicamente no es compatible con POSIX (verifique la gramática ), pero los shells modernos permiten ambos for x doy for x; do; algunas viejas conchas de Bourne no se quejaron for x; do. En las conchas modernas, con sh -c '…' arg0 arg1 arg2 arg3, se arg0convierte $0, se arg1convierte $1, etc. Si quieres $0ser sh, necesitas escribir sh -c '…' sh arg1 arg2 arg3. Nuevamente, algunos shells Bourne se comportaron de manera diferente, pero POSIX lo especifica.
Gilles 'SO- deja de ser malvado'
pushdParece una mejor opción que cd, ya que es menos intrusivo en el entorno actual.
jpmc26
16

Sé que findfue especificado, pero esto suena como un trabajo para rsync.

A menudo uso lo siguiente:

rsync -axuv --delete-after --progress Source/ Target/

Aquí hay un buen ejemplo si solo desea mover archivos de un tipo de archivo en particular ( ejemplo ):

rsync -rv --include '*/' --include '*.js' --exclude '*' --prune-empty-dirs Source/ Target/
ryanjdillon
fuente
mucho más limpio que las otras soluciones :)
Guillaume
2
Descubrí que --remove-source-filesfue útil, lo que efectivamente hace que los archivos se muevan en lugar de copiarse. Este es un uso impresionante de rsync.
Tom
3

puedes hacerlo usando dos instancias de find (1)

Siempre hay cpio (1)

(cd "$soure" && find  | cpio -pdVmu "$destination")

Verifique los argumentos para cpio. Los que di

Mark Lamourine
fuente
1
Esto se romperá en cualquier nombre de archivo con espacios en blanco.
Chris Down
1
Esto copia archivos en lugar de moverlos.
Gilles 'SO- deja de ser malvado'
Puede que no sea una respuesta perfecta, pero me ayudó a mover archivos conservando rutas de una manera más o menos directa (tengo suficiente espacio para copiar los archivos y luego eliminarlos). Votado a favor
AhHatem
3

No es tan eficiente, pero el código es más fácil de leer y entender, en mi opinión, si solo copia los archivos y luego los elimina.

find /original/file/path/* -mtime +7 -exec cp {} /new/file/path/ \;
find /original/file/path/* -mtime +7 -exec rm -rf {} \;

Aviso: Falla descubierta por @MV para operaciones automatizadas:

Usar dos operaciones separadas es arriesgado. Si algunos archivos tienen más de 7 días de antigüedad mientras se realiza la operación de copia, no se copiarán, pero serán eliminados por la operación de eliminación. Para algo que se hace manualmente una vez, esto puede no ser un problema, pero para los scripts automáticos esto puede conducir a la pérdida de datos

Elliott Post
fuente
2
Usar dos operaciones separadas es arriesgado. Si algunos archivos tienen más de 7 días de antigüedad mientras se realiza la operación de copia, no se copiarán, pero serán eliminados por la operación de eliminación. Para algo que se hace manualmente una vez, esto puede no ser un problema, pero para los scripts automáticos esto puede conducir a la pérdida de datos.
MV.
2
La solución fácil a esta falla es ejecutar find una vez, guardar la lista como un archivo de texto y luego usar xargs dos veces para hacer la copia y luego eliminarla.
David M. Perlman
1

Puede hacer esto agregando la ruta absoluta del archivo devuelto finda su ruta de destino:

find "$soure" -type f -mtime "-$days" -print0 | xargs -0 -I {} sh -c '
    file="{}"
    destination="'"$destination"'"
    mkdir -p "$destination/${file%/*}"
    mv "$file" "$destination/$file"'
Chris Down
fuente
Chris ahora se muda de su casa al máximo
Ketan Patel
@ K.KPatel No, no lo hace. Simplemente mantiene su estructura de directorios.
Chris Down
Esto se rompe si $destinationcontiene caracteres especiales, ya que se expande en el caparazón interno. Quizás quisiste decir destination='\'"$destination"\''? Eso todavía se rompe '. Además, esto crea archivos como en /home/maxi/home/ketan/hex/foolugar de /home/maxi/hex/foo.
Gilles 'SO- deja de ser malvado'
0

Mejor (más rápido y sin consumir espacio de almacenamiento haciendo copia en lugar de mover), tampoco se ve afectado por los nombres de archivo si contienen caracteres especiales en sus nombres:

export destination
find "$soure" -type f "-$days" -print0 | xargs -0 -n 10 bash -c '
for file in "$@"; do
  echo -n "Moving $file to $destination/"`dirname "$file"`" ... "
  mkdir -p "$destination"/`dirname "$file"`
  \mv -f "$file" "$destination"/`dirname "$file"`/ || echo " fail !" && echo "done."
done'

O más rápido, moviendo un montón de archivos al mismo tiempo para múltiples CPU, usando el comando "paralelo":

echo "Move oldest $days files from $soure to $destination in parallel (each 10 files by "`parallel --number-of-cores`" jobs):"
function move_files {
  for file in "$@"; do
    echo -n "Moving $file to $destination/"`dirname "$file"`" ... "
    mkdir -p "$destination"/`dirname "$file"`
    \mv -f "$file" "$destination"/`dirname "$file"`/ || echo " fail !" && echo "done."
  done
}
export -f move_files
export destination
find "$soure" -type f "-$days" -print0 | parallel -0 -n 10 move_files

PD: Tienes un error tipográfico, "soure" debería ser "fuente". Mantuve el nombre de la variable.

Bogdan Velcea
fuente
0

Esto es menos elegante pero fácil si el número / tamaño de los archivos no es demasiado grande

Comprima sus archivos en un ziparchivo y luego descomprímalos en el destino sin la -jopción. Por defecto, zip creará la estructura de directorios relativa.

Chris Johnson
fuente
0

Intenta de esta manera:

IFS=$'\n'
for f in `find "$soure" -type f -mtime "-$days"`;
do
  mkdir -p "$destination"/`dirname $f`;
  mv $f "$destination"/`dirname $f`;
done
Alessio
fuente
0

Debido a que parece que no hay una solución realmente fácil para esto y la necesito muy a menudo, creé esta utilidad de código abierto para Linux (requiere Python): https://github.com/benapetr/smv

Hay varias formas de usarlo para lograr lo que necesita, pero probablemente la más simple sería algo como esto:

 # -vf = verbose + force (doesn't stop on errors)
smv -vf `find some_folder -type f -mtime "-$days"` target_folder

También puede ejecutarlo en modo seco para que no haga nada más que imprimir lo que haría

smv -rvf `find some_folder -type f -mtime "-$days"` target_folder

O en caso de que esa lista de archivos sea demasiado larga para caber en la línea de argumento y no le importe ejecutar Python para cada archivo, entonces

find "$soure" -type f -mtime "-$days" -exec smv {} "$destination" \;
Petr
fuente