Preserva la struttura delle directory quando si spostano i file utilizzando find


22

Ho creato il seguente script che sposta i file dei vecchi tempi come definito dalla directory di origine alla directory di destinazione. Funziona perfettamente.

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

Questo script sposta i file alla grande, sposta anche i file della sottodirectory di origine, ma non crea la sottodirectory nella directory di destinazione. Voglio implementare questa funzionalità aggiuntiva in esso.

con l'esempio

/home/ketan     : source directory

/home/ketan/hex : source subdirectory

/home/maxi      : destination directory

Quando eseguo questo script, sposta anche i file esadecimali nella directory maxi, ma ho bisogno che lo stesso esagono debba essere creato nella directory maxi e spostare i suoi file nello stesso esadecimale lì.

Risposte:


13

Invece di eseguire mv /home/ketan/hex/foo /home/maxi, dovrai variare la directory di destinazione in base al percorso prodotto da find. Questo è più semplice se si passa prima alla directory di origine e si esegue find .. Ora puoi semplicemente anteporre la directory di destinazione a ciascun elemento prodotto da find. È necessario eseguire una shell nel find … -execcomando per eseguire la concatenazione e, se necessario, creare la directory di destinazione.

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

Nota che per evitare di citare problemi se $destinationcontiene caratteri speciali, non puoi semplicemente sostituirlo all'interno dello script della shell. Puoi esportarlo nell'ambiente in modo che raggiunga la shell interna, oppure puoi passarlo come argomento (è quello che ho fatto). Puoi risparmiare un po 'di tempo di esecuzione raggruppando le shchiamate:

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

In alternativa, in zsh, è possibile utilizzare la zmvfunzione e i qualificatori. e m glob per abbinare solo i file regolari nell'intervallo di date corretto. Dovrai passare una mvfunzione alternativa che crea prima la directory di destinazione, se necessario.

autoload -U zmv
mkdir_mv () {
  mkdir -p -- $3:h
  mv -- $2 $3
}
zmv -Qw -p mkdir_mv $source/'**/*(.m-'$days')' '$destination/$1$2'

for x do, ti sei perso ;lì :). Inoltre, non ho idea di cosa tu voglia ottenere, $0ma sono abbastanza convinto che sarebbe sh:).
Michał Górny,

@ MichałGórny non for x; doè tecnicamente conforme a POSIX (controlla la grammatica ), ma le shell moderne consentono sia for x doe for x; do; alcune vecchie conchiglie Bourne non brontolavano for x; do. Sulle conchiglie moderne, con sh -c '…' arg0 arg1 arg2 arg3, arg0diventa $0, arg1diventa $1, ecc. Se vuoi $0essere sh, devi scrivere sh -c '…' sh arg1 arg2 arg3. Ancora una volta, alcune shell Bourne si sono comportate diversamente, ma POSIX lo specifica.
Gilles 'SO- smetti di essere malvagio' il

pushdsembra una scelta migliore che cd, in quanto meno invadente nell'ambiente attuale.
jpmc26,

16

So che è findstato specificato, ma sembra un lavoro per rsync.

Il più delle volte uso quanto segue:

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

Ecco un buon esempio se vuoi spostare solo file di un particolare tipo di file ( esempio ):

rsync -rv --include '*/' --include '*.js' --exclude '*' --prune-empty-dirs Source/ Target/

molto più pulito delle altre soluzioni :)
Guillaume

2
Ho trovato --remove-source-filesutile, il che fa sì che i file vengano spostati anziché copiati. Questo è un fantastico uso di rsync.
Tom,

3

potresti farlo usando due istanze di find (1)

C'è sempre cpio (1)

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

Controlla gli argomenti per cpio. Quelli che ho dato


1
Ciò si interromperà su tutti i nomi di file con spazi bianchi.
Chris Down,

1
Questo copia i file invece di spostarli.
Gilles 'SO- smetti di essere malvagio' il

Potrebbe non essere una risposta perfetta, ma mi ha aiutato a spostare i file preservando i percorsi in un modo più o meno diretto (ho abbastanza spazio per copiare i file e poi eliminarli). Upgrade
AhHatem,

3

Non è così efficiente, ma il codice è più facile da leggere e capire, a mio avviso, se copi i file e poi li elimini in seguito.

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

Avviso: difetto rilevato da @MV per operazioni automatizzate:

L'uso di due operazioni separate è rischioso. Se alcuni file diventano più vecchi di 7 giorni durante l'operazione di copia, non verranno copiati ma verranno eliminati dall'operazione di eliminazione. Per qualcosa che viene fatto manualmente una volta questo potrebbe non essere un problema, ma per gli script automatici questo può portare alla perdita di dati


2
L'uso di due operazioni separate è rischioso. Se alcuni file diventano più vecchi di 7 giorni durante l'operazione di copia, non verranno copiati ma verranno eliminati dall'operazione di eliminazione. Per qualcosa che viene fatto manualmente una volta questo potrebbe non essere un problema, ma per gli script automatici questo può portare alla perdita di dati.
MV.

2
La soluzione semplice a questo difetto è eseguire find una volta, salvare l'elenco come file di testo, quindi utilizzare due volte xargs per eseguire la copia e quindi l'eliminazione.
David M. Perlman,

1

Puoi farlo aggiungendo il percorso assoluto del file restituito findal percorso di destinazione:

find "$soure" -type f -mtime "-$days" -print0 | xargs -0 -I {} sh -c '
    file="{}"
    destination="'"$destination"'"
    mkdir -p "$destination/${file%/*}"
    mv "$file" "$destination/$file"'

Chris ora si trasferisce in casa al massimo
Ketan Patel il

@ K.KPatel No, non lo è. Mantiene semplicemente la struttura della directory.
Chris Down,

Questo si interrompe se $destinationcontiene caratteri speciali, perché subisce l'espansione nella shell interna. Forse intendevi destination='\'"$destination"\''? Ciò si interrompe ancora '. Inoltre, questo crea file come /home/maxi/home/ketan/hex/fooinvece di /home/maxi/hex/foo.
Gilles 'SO- smetti di essere malvagio' il

0

Meglio (più veloce e senza consumare spazio facendo copia anziché spostare), inoltre, i nomi dei file non sono influenzati se contengono caratteri speciali nei loro nomi:

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 più velocemente, spostando un mucchio di file contemporaneamente per più CPU, usando il comando "parallelo":

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: hai un refuso, "soure" dovrebbe essere "source". Ho mantenuto il nome della variabile.


0

Questo è meno elegante ma facile se il numero / dimensione dei file non è troppo grande

Comprimi i tuoi file in un ziparchivio, quindi decomprimi nella destinazione senza l' -jopzione. Per impostazione predefinita, zip creerà la relativa struttura di directory.


0

Prova in questo modo:

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

0

Poiché non sembra esserci una soluzione davvero semplice a questo e ne ho bisogno molto spesso, ho creato questa utility open source per linux (richiede python): https://github.com/benapetr/smv

Esistono diversi modi per utilizzarlo per ottenere ciò di cui hai bisogno, ma probabilmente il più semplice sarebbe qualcosa del genere:

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

Puoi anche eseguirlo in modalità dry in modo che non faccia altro che stampare ciò che farebbe

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

O nel caso in cui tale elenco di file sia troppo lungo per rientrare nella riga dell'argomento e non ti dispiaccia eseguire Python per ogni singolo file, allora

find "$soure" -type f -mtime "-$days" -exec smv {} "$destination" \;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.