copiare prima i file più piccoli?


15

Ho una grande directory contenente sottodirectory e file che desidero copiare in modo ricorsivo.

C'è un modo per dire cpche dovrebbe eseguire l'operazione di copia in ordine di dimensione del file, in modo che i file più piccoli vengano copiati per primi?


1
Solo per essere sicuro che non ci sia un problema XY in questione, puoi spiegare perché vuoi farlo?
— Riccioli d'oro

4
@ TAFKA'goldilocks '- Ho molti file video e vorrei testare la qualità di ogni directory. Il video più piccolo mi darà una rapida indicazione se anche il resto dei file è difettoso.
— nbubis,

Risposte:


10

Questo fa tutto il lavoro in una volta sola - in tutte le directory secondarie, tutte in un singolo flusso senza problemi di nome file. Copia dal più piccolo al più grande ogni file che hai. Sarà necessario mkdir ${DESTINATION}se non esiste già.

find . ! -type d -print0 |
du -b0 --files0-from=/dev/stdin |
sort -zk1,1n | 
sed -zn 's/^[^0-9]*[0-9]*[^.]*//p' |
tar --hard-dereference --null -T /dev/stdin -cf - |
    tar -C"${DESTINATION}" --same-order -xvf -

Sai una cosa, però? Ciò che ciò non fa è svuotare le directory secondarie. Potrei fare un reindirizzamento su quella pipeline, ma è solo una condizione di gara in attesa di accadere. Il più semplice è probabilmente il migliore. Quindi fallo solo dopo:

find . -type d -printf 'mkdir -p "'"${DESTINATION}"'/%p"\n' |
    . /dev/stdin

Oppure, dal momento che Gilles fa un ottimo punto nella sua risposta per preservare i permessi delle directory, dovrei provare anche io. Penso che questo lo farà:

find . -type d -printf '[ -d "'"${DESTINATION}"'/%p" ] || 
    cp "%p" -t "'"${DESTINATION}"'"\n' |
. /dev/stdin

Sarei disposto a scommettere che è più veloce di mkdircomunque.


1
Accidenti, mikeserv! +1
— Riccioli d'oro

3
@ TAFKA'goldilocks 'Lo prenderò come un complimento. Grazie mille.
— Mikeserv,

15

Ecco un metodo rapido e sporco con rsync. Per questo esempio sto prendendo in considerazione qualcosa di meno di 10 MB come "piccolo".

Innanzitutto trasferisci solo i file di piccole dimensioni:

rsync -a --max-size=10m srcdir dstdir

Quindi trasferire i file rimanenti. I file di piccole dimensioni precedentemente trasferiti non verranno copiati a meno che non siano stati modificati.

rsync -a srcdir dstdir

A partire dal man 1 rsync

   --max-size=SIZE
          This  tells  rsync to avoid transferring any file that is larger
          than the specified SIZE. The SIZE value can be suffixed  with  a
          string  to  indicate  a size multiplier, and may be a fractional
          value (e.g. "--max-size=1.5m").

          This option is a transfer rule, not an exclude,  so  it  doesn’t
          affect  the  data  that  goes  into  the file-lists, and thus it
          doesn’t affect deletions.  It just limits  the  files  that  the
          receiver requests to be transferred.

          The  suffixes  are  as  follows:  "K"  (or  "KiB") is a kibibyte
          (1024), "M" (or "MiB") is a mebibyte (1024*1024),  and  "G"  (or
          "GiB")  is  a gibibyte (1024*1024*1024).  If you want the multi‐
          plier to be 1000 instead of  1024,  use  "KB",  "MB",  or  "GB".
          (Note: lower-case is also accepted for all values.)  Finally, if
          the suffix ends in either "+1" or "-1", the value will be offset
          by one byte in the indicated direction.

          Examples:    --max-size=1.5mb-1    is    1499999    bytes,   and
          --max-size=2g+1 is 2147483649 bytes.

Naturalmente, l'ordine di trasferimento file per file non è strettamente dal più piccolo al più grande, ma penso che possa essere la soluzione più semplice che soddisfa lo spirito delle tue esigenze.


Qui ottieni 2 copie di hard-link e i soft-link vengono trasformati in file effettivi per due copie di ciascuno. Faresti molto meglio con --copy-dest=DIRe / o --compare-dest=DIRpenso. So solo perche 'ho dovuto aggiungere --hard-dereferencea me stesso di tardopo la pubblicazione la mia risposta, perché mi mancava i link. Penso che in rsyncrealtà si comporti in modo più specifico con i file system locali con quegli altri - lo usavo con le chiavi USB e inondava il bus a meno che non imposti un limite di larghezza di banda. Penso che avrei dovuto usare uno di questi altri invece.
— Mikeserv,

1
+1 per il "metodo rapido e sporco". Di solito è più semplice, almeno per scopi di automazione e manutenibilità futura. Penso che questo sia in realtà abbastanza pulito. "Elegante" vs "kludgy" e "robusto" vs "instabile" possono talvolta essere in conflitto come obiettivi di progettazione, ma esiste un buon equilibrio che può essere raggiunto e penso che sia elegante e abbastanza robusto.
— Wildcard il

4

Non cpdirettamente, questo va ben oltre le sue capacità. Ma puoi organizzare di chiamare cpi file nel giusto ordine.

Zsh consente convenientemente di ordinare i file per dimensione con un qualificatore glob . Ecco uno snippet zsh che copia i file in ordine crescente di dimensioni da sotto /path/to/source-directorya sotto /path/to/destination-directory.

cd /path/to/source-directory
for x in **/*(.oL); do
  mkdir -p /path/to/destination-directory/$x:h
  cp $x /path/to/destination-directory/$x:h
done

Invece di un ciclo, è possibile utilizzare la zcpfunzione. Tuttavia, è necessario creare prima le directory di destinazione, che possono essere eseguite in un criptico oneliner.

autoload -U zmv; alias zcp='zmv -C'
cd /path/to/source-directory
mkdir **/*(/e\''REPLY=/path/to/destination-directory/$REPLY'\')
zcp -Q '**/*(.oL)' '/path/to/destination-directory/$f'

Ciò non preserva la proprietà delle directory di origine. Se lo desideri, dovrai arruolare un programma di copia adatto come cpioo pax. Se lo fai, non è necessario chiamare cpo zcpin aggiunta.

cd /path/to/source-directory
print -rN **/*(^.) **/*(.oL) | cpio -0 -p /path/to/destination-directory

2

Non penso che ci sia modo cp -rdi farlo direttamente. Dal momento che potrebbe essere un periodo di tempo indeterminato prima di ottenere una procedura guidata find/ magica awk, ecco un veloce script perl:

#!/usr/bin/perl
use strict;
use warnings FATAL => qw(all);

use File::Find;
use File::Basename;

die "No (valid) source directory path given.\n"
    if (!$ARGV[0] || !-d -r "/$ARGV[0]");

die "No (valid) destination directory path given.\n"
    if (!$ARGV[1] || !-d -w "/$ARGV[1]");

my $len = length($ARGV[0]);
my @files;
find (
    sub {
        my $fpath = $File::Find::name;
        return if !-r -f $fpath;
        push @files, [
            substr($fpath, $len),
            (stat($fpath))[7],
        ]
    }, $ARGV[0]
);

foreach (sort { $a->[1] <=> $b->[1] } @files) {
    if ($ARGV[2]) {
        print "$_->[1] $ARGV[0]/$_->[0] -> $ARGV[1]/$_->[0]\n";
    } else {
        my $dest = "$ARGV[1]/$_->[0]";
        my $dir = dirname($dest);
        mkdir $dir if !-e $dir;
        `cp -a "$ARGV[0]/$_->[0]" $dest`;
    }
} 
  • Usa questo: ./whatever.pl /src/path /dest/path

  • Gli argomenti dovrebbero essere entrambi percorsi assoluti ; ~o qualsiasi altra cosa che la shell si espande in un percorso assoluto va bene.

  • Se aggiungi un terzo argomento (qualsiasi cosa, tranne un letterale 0), invece di copiarlo, stamperà un rapporto standard su cosa farebbe, con dimensioni dei file in byte anteposte, ad es.

    4523 /src/path/file.x -> /dest/path/file.x
    12124 /src/path/file.z -> /dest/path/file.z

    Si noti che questi sono in ordine crescente per dimensione.

  • Il cpcomando sulla riga 34 è un comando letterale della shell, quindi puoi fare quello che vuoi con gli switch (ho usato solo -aper preservare tutti i tratti).

  • File::Finde File::Basenamesono entrambi i moduli principali, cioè sono disponibili in tutte le installazioni di perl.


probabilmente, questa è l'unica risposta corretta qui. O era ... il titolo - appena cambiato ...? La mia finestra del browser si chiama cp - copy smallest files first?ma il titolo del post è copy smallest files first?Ad ogni modo, le opzioni non fanno mai male è la mia filosofia, ma comunque tu e David siete gli unici che hanno usato cpe siete gli unici a avercela fatta.
— mikeserv,

@mikeserv L'unico motivo che ho usato cpera perché è il modo più semplice per preservare le caratteristiche del file * nix nel perl (orientato alla piattaforma). Il motivo indicato nella barra del browser cp - è a causa di una funzione SE (IMO goofy) in base alla quale il più popolare dei tag selezionati viene visualizzato come prefisso al titolo effettivo.
— Riccioli d'oro

Ok, quindi ritiro il mio complimento. Non proprio, spesso non vedi pearluscire dalla falegnameria qui intorno.
— Mikeserv,

1

un'altra opzione sarebbe usare cp con l'output di du:

oldIFS=$IFS
IFS=''
for i in $(du -sk *mpg | sort -n | cut -f 2)
do
    cp $i destination
done
IFS=$oldIFS

Questo potrebbe ancora essere fatto su una riga, ma l'ho diviso in modo da poterlo leggere


Non devi almeno fare qualcosa per $ IFS?
— Mikeserv,

Sì ... Continuo a supporre che nessuno abbia una nuova riga nei nomi dei file
— David Wilkins,

1
Anche questo non sembra gestire la ricorsione attraverso la gerarchia di directory descritta dall'OP.
— cpugeniusmv,

1
@cpugeniusmv Corretto ... In qualche modo mi è sfuggita la parte ricorsiva .... Potrei modificarla per gestire la ricorsione, ma a questo punto altre risposte fanno un lavoro migliore. Lascio questo qui nel caso in cui aiuti qualcuno che vede la domanda.
— David Wilkins,

1
@DavidWilkins - questo aiuta molto.
— nbubis,
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.