Préserver la structure du répertoire lors du déplacement de fichiers à l'aide de la recherche

22

J'ai créé le script suivant qui déplace les fichiers anciens tels que définis du répertoire source vers le répertoire de destination. Cela fonctionne parfaitement.

#!/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"

Ce script déplace très bien les fichiers, il déplace également les fichiers du sous-répertoire source, mais il ne crée pas de sous-répertoire dans le répertoire de destination. Je veux y implémenter cette fonctionnalité supplémentaire.

avec exemple

/home/ketan     : source directory

/home/ketan/hex : source subdirectory

/home/maxi      : destination directory

Lorsque j'exécute ce script, il déplace également les fichiers hex dans le répertoire maxi, mais j'ai besoin que le même hex soit créé dans le répertoire maxi et y déplace ses fichiers dans le même hex.

Ketan Patel
la source

Réponses:

13

Au lieu de s'exécuter mv /home/ketan/hex/foo /home/maxi, vous devrez faire varier le répertoire cible en fonction du chemin produit par find. C'est plus facile si vous passez d'abord au répertoire source et exécutez find .. Vous pouvez maintenant simplement ajouter le répertoire de destination à chaque élément produit par find. Vous devrez exécuter un shell dans la find … -execcommande pour effectuer la concaténation et créer le répertoire cible si nécessaire.

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" {} \;

Notez que pour éviter de citer des problèmes si $destinationcontient des caractères spéciaux, vous ne pouvez pas simplement le remplacer dans le script shell. Vous pouvez l'exporter dans l'environnement pour qu'il atteigne le shell interne, ou vous pouvez le passer comme argument (c'est ce que j'ai fait). Vous pourriez gagner un peu de temps d'exécution en regroupant les shappels:

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" {} +

Alternativement, dans zsh, vous pouvez utiliser la zmvfonction et les qualificatifs. et m glob pour ne faire correspondre que les fichiers normaux dans la bonne plage de dates. Vous devrez passer une mvfonction alternative qui crée d'abord le répertoire cible si nécessaire.

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- arrête d'être méchant'
la source
for x do, vous avez un manquant ;là :). De plus, je n'ai aucune idée de ce que vous vouliez réaliser, $0mais je suis tout à fait convaincu que ce serait sh:).
Michał Górny
@ MichałGórny for x; don'est techniquement pas compatible POSIX (vérifiez la grammaire ), mais les shells modernes permettent à la fois for x doet for x; do; certains vieux obus Bourne ne bougeaient pas for x; do. Sur les coquilles modernes, avec sh -c '…' arg0 arg1 arg2 arg3, arg0devient $0, arg1devient $1, etc. Si vous voulez $0être sh, vous devez écrire sh -c '…' sh arg1 arg2 arg3. Encore une fois, certains obus Bourne se sont comportés différemment, mais POSIX le précise.
Gilles 'SO- arrête d'être méchant'
pushdsemble être un meilleur choix cd, car il est moins intrusif dans l'environnement actuel.
jpmc26
16

Je sais que findc'était spécifié, mais cela ressemble à un travail pour rsync.

J'utilise le plus souvent les éléments suivants:

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

Voici un bon exemple si vous souhaitez déplacer uniquement des fichiers d'un type de fichier particulier ( exemple ):

rsync -rv --include '*/' --include '*.js' --exclude '*' --prune-empty-dirs Source/ Target/
ryanjdillon
la source
beaucoup plus propre que les autres solutions :)
Guillaume
2
J'ai trouvé que --remove-source-filesc'était utile, ce qui entraîne le déplacement des fichiers au lieu de leur copie. Ceci est une excellente utilisation de rsync.
Tom
3

vous pouvez le faire en utilisant deux instances de find (1)

Il y a toujours cpio (1)

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

Vérifiez les arguments de cpio. Ceux que j'ai donnés

Mark Lamourine
la source
1
Cela rompra tous les noms de fichiers avec des espaces.
Chris Down
1
Cela copie les fichiers au lieu de les déplacer.
Gilles 'SO- arrête d'être méchant'
Ce n'est peut-être pas une réponse parfaite, mais cela m'a aidé à déplacer les fichiers en préservant les chemins de manière plus ou moins simple (j'ai suffisamment d'espace pour copier les fichiers puis les supprimer). Vote positif
AhHatem
3

Ce n'est pas aussi efficace, mais le code est plus facile à lire et à comprendre, à mon avis, si vous copiez simplement les fichiers, puis supprimez ensuite.

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

Remarque: faille découverte par @MV pour les opérations automatisées:

L'utilisation de deux opérations distinctes est risquée. Si certains fichiers sont âgés de plus de 7 jours pendant l'opération de copie, ils ne seront pas copiés mais ils seront supprimés par l'opération de suppression. Pour quelque chose qui est fait manuellement une fois que cela ne peut pas être un problème, mais pour les scripts automatisés, cela peut entraîner une perte de données

Elliott Post
la source
2
L'utilisation de deux opérations distinctes est risquée. Si certains fichiers sont âgés de plus de 7 jours pendant l'opération de copie, ils ne seront pas copiés mais ils seront supprimés par l'opération de suppression. Pour quelque chose qui est fait manuellement une fois que cela peut ne pas être un problème, mais pour les scripts automatisés, cela peut entraîner une perte de données.
MV.
2
La solution simple à cette faille est d'exécuter find une fois, d'enregistrer la liste en tant que fichier texte, puis d'utiliser xargs deux fois pour effectuer la copie, puis la supprimer.
David M. Perlman
1

Vous pouvez le faire en ajoutant le chemin absolu du fichier renvoyé par findà votre chemin de destination:

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
la source
Chris passe maintenant à la maison en maxi
Ketan Patel
@ K.KPatel Non, ce n'est pas le cas. Il conserve simplement votre structure de répertoires.
Chris Down
Cela casse s'il $destinationcontient des caractères spéciaux, car il subit une expansion dans la coque intérieure. Peut-être que tu voulais dire destination='\'"$destination"\''? Cela continue encore '. En outre, cela crée des fichiers tels que /home/maxi/home/ketan/hex/fooau lieu de /home/maxi/hex/foo.
Gilles 'SO- arrête d'être méchant'
0

Mieux (plus rapide et sans consommer d'espace de stockage en faisant une copie au lieu de déplacer), n'est pas non plus affecté par les noms de fichiers s'ils contiennent des caractères spéciaux dans leurs noms:

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'

Ou plus rapidement, en déplaçant un tas de fichiers en même temps pour plusieurs CPU, en utilisant la commande "parallèle":

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

PS: Vous avez une faute de frappe, "soure" devrait être "source". J'ai gardé le nom de la variable.

Bogdan Velcea
la source
0

C'est moins élégant mais facile si le nombre / taille des fichiers n'est pas trop grand

Compressez vos fichiers dans une ziparchive, puis décompressez à la destination sans l' -joption. Par défaut, zip créera la structure de répertoires relative.

Chris Johnson
la source
0

Essayez de cette façon:

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

Parce qu'il ne semble pas y avoir de solution vraiment facile à cela et que j'en ai besoin très souvent, j'ai créé cet utilitaire open source pour linux (nécessite python): https://github.com/benapetr/smv

Il existe plusieurs façons de l'utiliser pour obtenir ce dont vous avez besoin, mais le plus simple serait probablement quelque chose comme ceci:

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

Vous pouvez également l'exécuter en mode sec afin qu'il ne fasse rien d'autre que d'imprimer ce qu'il ferait

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

Ou dans le cas où cette liste de fichiers est trop longue pour tenir dans la ligne d'argument et que cela ne vous dérange pas d'exécuter python pour chaque fichier, alors

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