copier d'abord les plus petits fichiers?

15

J'ai un grand répertoire contenant des sous-répertoires et des fichiers que je souhaite copier récursivement.

Existe-t-il un moyen de dire cpqu'il doit effectuer l'opération de copie par ordre de taille de fichier, afin que les plus petits fichiers soient copiés en premier?

nbubis
la source
1
Pour être sûr qu'il n'y a pas de problème XY , pouvez-vous expliquer pourquoi vous voulez faire cela?
goldilocks
4
@ TAFKA'goldilocks '- J'ai beaucoup de fichiers vidéo et j'aimerais tester la qualité de chaque répertoire. La plus petite vidéo me donnera une indication rapide de si le reste des fichiers est également mauvais.
nbubis

Réponses:

10

Cela fait tout le travail en une seule fois - dans tous les répertoires enfants, le tout dans un seul flux sans aucun problème de nom de fichier. Il copiera du plus petit au plus grand chaque fichier que vous avez. Vous devrez le faire mkdir ${DESTINATION}s'il n'existe pas déjà.

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 -

Mais tu sais quoi? Ce que cela ne fait pas, c'est des répertoires enfants vides . Je pourrais faire une redirection sur ce pipeline, mais ce n'est qu'une condition de concurrence qui attend de se produire. Le plus simple est probablement le meilleur. Alors faites-le juste après:

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

Ou, puisque Gilles fait un très bon point dans sa réponse pour préserver les autorisations de répertoire, je devrais essayer aussi. Je pense que cela le fera:

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

Je serais prêt à parier que c'est plus rapide que de mkdirtoute façon.

mikeserv
la source
1
Bon sang mikeserv! +1
goldilocks
3
@ TAFKA'goldilocks 'Je vais prendre ça comme un compliment. Merci beaucoup.
mikeserv
15

Voici une méthode rapide et sale à utiliser rsync. Pour cet exemple, je considère que tout ce qui est inférieur à 10 Mo est "petit".

Transférez d'abord uniquement les petits fichiers:

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

Transférez ensuite les fichiers restants. Les petits fichiers précédemment transférés ne seront pas recopiés sauf s'ils ont été modifiés.

rsync -a srcdir dstdir

De 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  doesnt
          affect  the  data  that  goes  into  the file-lists, and thus it
          doesnt 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.

Bien sûr, l'ordre de transfert fichier par fichier n'est pas strictement du plus petit au plus grand, mais je pense que c'est peut-être la solution la plus simple qui répond à l'esprit de vos exigences.

cpugeniusmv
la source
Ici, vous obtenez 2 copies de liens physiques et les liens logiciels sont transformés en fichiers réels pour deux copies de chacun. Vous feriez beaucoup mieux avec --copy-dest=DIRet / ou --compare-dest=DIRje pense. Je ne sais que parce que j'ai dû --hard-dereferencem'y ajouter taraprès avoir posté ma propre réponse parce que je manquais les liens. Je pense que rsyncse comporte en fait plus spécifique aux systèmes de fichiers locaux avec ces autres de toute façon - je l'utilisais avec des clés USB et cela inonderait le bus à moins que je ne fixe une limite de bande passante. Je pense que j'aurais dû utiliser l'un de ces autres à la place.
mikeserv
1
+1 pour la "méthode rapide et sale". Plus simple est généralement meilleur au moins à des fins d'automatisation et de maintenance future. Je pense que c'est en fait assez propre. "Élégant" vs "kludgy" et "robuste" vs "instable" peuvent parfois entrer en conflit comme objectifs de conception mais il y a un bon équilibre qui peut être atteint, et je pense que c'est élégant et assez robuste.
Wildcard
4

Pas cpdirectement, c'est bien au-delà de ses capacités. Mais vous pouvez vous arranger pour appeler cples fichiers dans le bon ordre.

Zsh permet commodément de trier les fichiers par taille avec un qualificatif glob . Voici un extrait zsh qui copie les fichiers dans un ordre croissant de taille de dessous /path/to/source-directoryen dessous /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

Au lieu d'une boucle, vous pouvez utiliser la zcpfonction. Cependant, vous devez d'abord créer les répertoires de destination, ce qui peut être fait dans un oneliner cryptique.

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'

Cela ne préserve pas la propriété des répertoires source. Si vous le souhaitez, vous devrez enrôler un programme de copie approprié tel que cpioou pax. Si vous faites cela, vous n'avez pas besoin d'appeler cpou zcpen plus.

cd /path/to/source-directory
print -rN **/*(^.) **/*(.oL) | cpio -0 -p /path/to/destination-directory
Gilles 'SO- arrête d'être méchant'
la source
2

Je ne pense pas qu'il y ait moyen cp -rde le faire directement. Comme il peut s'écouler une période de temps indéterminée avant d'obtenir une solution find/ assistant awk, voici un script perl rapide:

#!/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`;
    }
} 
  • Utilisez ceci: ./whatever.pl /src/path /dest/path

  • Les arguments doivent être tous deux des chemins absolus ; ~, ou toute autre chose que le shell étend à un chemin absolu est très bien.

  • Si vous ajoutez un troisième argument (n'importe quoi, à l'exception d'un littéral 0), au lieu de le copier, il imprimera pour standardiser un rapport de ce qu'il ferait, avec des tailles de fichiers en octets ajoutées, par exemple

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

    Notez qu'ils sont classés par ordre croissant de taille.

  • La cpcommande sur la ligne 34 est une commande shell littérale, vous pouvez donc faire ce que vous voulez avec les commutateurs (je viens de l'utiliser -apour préserver tous les traits).

  • File::Findet File::Basenamesont les deux modules de base, c'est-à-dire qu'ils sont disponibles dans toutes les installations de perl.

boucle d'or
la source
sans doute, c'est la seule bonne réponse ici. Ou c'était ... le titre - juste changé ...? Ma fenêtre de navigateur est appelée cp - copy smallest files first?mais le titre de l'article est juste copy smallest files first?Quoi qu'il en soit, les options ne font jamais de mal est ma philosophie, mais quand même, vous et David êtes les seuls à avoir utilisé cpet vous êtes le seul à l'avoir réussi.
mikeserv
@mikeserv La seule raison pour laquelle j'ai utilisé cpétait parce que c'est le moyen le plus simple de conserver les caractéristiques des fichiers * nix dans le Perl (orienté multiplateforme). La raison pour laquelle la barre de votre navigateur le dit cp - est due à une fonctionnalité (IMO goofy) SE dans laquelle le plus populaire des tags sélectionnés apparaît préfixé au titre réel.
goldilocks
Ok, alors je retire mon compliment. Pas vraiment, vous ne voyez pas souvent pearlsortir des boiseries ici.
mikeserv
1

une autre option serait d'utiliser cp avec la sortie de du:

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

Cela pourrait toujours être fait sur une seule ligne, mais je l'ai divisé pour que vous puissiez le lire

David Wilkins
la source
N'avez-vous pas au moins besoin de faire quelque chose à propos de $ IFS?
mikeserv
Oui ... Je continue de supposer que personne n'a de nouvelle ligne dans ses noms de fichiers
David Wilkins
1
Cela ne semble pas non plus gérer la récursivité dans la hiérarchie de répertoires décrite par l'OP.
cpugeniusmv
1
@cpugeniusmv Correct ... J'ai en quelque sorte manqué la partie récursive .... Je pourrais modifier cela pour gérer la récursivité, mais je pense qu'à ce stade, d'autres réponses font un meilleur travail. Je laisse cela ici au cas où cela aiderait quelqu'un qui voit la question.
David Wilkins
1
@DavidWilkins - cela aide beaucoup.
nbubis