copiar primero los archivos más pequeños?

15

Tengo un directorio grande que contiene subdirectorios y archivos que deseo copiar de forma recursiva.

¿Hay alguna manera de decir cpque debe realizar la operación de copia en orden de tamaño de archivo, de modo que los archivos más pequeños se copien primero?

nbubis
fuente
1
Solo para estar seguro de que no hay un problema XY involucrado, ¿puedes explicar por qué quieres hacer esto?
Ricitos
44
@ TAFKA'goldilocks ': tengo muchos archivos de video y me gustaría probar la calidad de cada directorio. El video más pequeño me dará una indicación rápida de si el resto de los archivos también son malos.
nbubis

Respuestas:

10

Esto hace todo el trabajo de una vez: en todos los directorios secundarios, todo en una sola secuencia sin ningún problema de nombre de archivo. Copiará de menor a mayor cada archivo que tenga. Tendrá que hacerlo mkdir ${DESTINATION}si aún no existe.

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 -

¿Sabes qué? Lo que esto no hace es directorios secundarios vacíos . Podría hacer un poco de redireccionamiento sobre esa tubería, pero es solo una condición de carrera esperando a suceder. Más simple es probablemente el mejor. Entonces haz esto después:

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

O, dado que Gilles hace un muy buen punto en su respuesta para preservar los permisos de directorio, debería intentarlo también. Creo que esto lo hará:

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

Estaría dispuesto a apostar que es más rápido que de mkdirtodos modos.

mikeserv
fuente
1
¡Maldita sea mikeserv! +1
Ricitos
3
@ TAFKA'goldilocks 'Lo tomaré como un cumplido. Muchas gracias.
mikeserv
15

Aquí hay un método rápido y sucio usando rsync. Para este ejemplo, considero que cualquier cosa de menos de 10 MB es "pequeña".

Primero transfiera solo los archivos pequeños:

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

Luego transfiere los archivos restantes. Los archivos pequeños transferidos previamente no se volverán a copiar a menos que se hayan modificado.

rsync -a srcdir dstdir

Desde 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.

Por supuesto, el orden de transferencia archivo por archivo no es estrictamente menor a mayor, pero creo que puede ser la solución más simple que cumpla con el espíritu de sus requisitos.

cpugeniusmv
fuente
Aquí obtienes 2 copias de enlaces duros y los enlaces blandos se transforman en archivos reales para dos copias de cada uno. Te iría mucho mejor --copy-dest=DIRy / o --compare-dest=DIRcreo. Solo sé porque tuve que --hard-dereferenceagregarme tardespués de publicar mi propia respuesta porque me faltaban los enlaces. Creo que, de rsynctodos modos, se comporta de manera más específica con los otros sistemas de archivos locales: solía usarlo con llaves USB e inundaría el bus a menos que estableciera un límite de ancho de banda. Creo que debería haber usado cualquiera de esos otros en su lugar.
mikeserv
1
+1 para el "método rápido y sucio". Más simple es generalmente mejor al menos para fines de automatización y mantenibilidad futura. Creo que esto es bastante limpio. "Elegante" vs "kludgy" y "robusto" vs "inestable" a veces pueden entrar en conflicto como objetivos de diseño, pero hay un buen equilibrio que se puede alcanzar, y creo que esto es elegante y bastante robusto.
Comodín el
4

No cpdirectamente, eso está más allá de sus habilidades. Pero puede hacer arreglos para llamar cplos archivos en el orden correcto.

Zsh convenientemente permite ordenar archivos por tamaño con un calificador global . Aquí hay un fragmento de zsh que copia archivos en orden creciente de tamaño de abajo /path/to/source-directorya abajo /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

En lugar de un bucle, puede usar la zcpfunción. Sin embargo, primero debe crear los directorios de destino, lo que se puede hacer en una línea críptica.

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'

Esto no preserva la propiedad de los directorios de origen. Si lo desea, deberá contratar un programa de copia adecuado como cpioo pax. Si hace eso, no necesita llamar cpo zcpademás.

cd /path/to/source-directory
print -rN **/*(^.) **/*(.oL) | cpio -0 -p /path/to/destination-directory
Gilles 'SO- deja de ser malvado'
fuente
2

No creo que haya forma cp -rde hacerlo directamente. Dado que puede ser un período de tiempo indeterminado antes de obtener una solución find/ asistente awk, aquí hay un script rápido en 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`;
    }
} 
  • Utilizar esta: ./whatever.pl /src/path /dest/path

  • Los argumentos deben ser ambos caminos absolutos ; ~, o cualquier otra cosa que el shell se expanda a una ruta absoluta está bien.

  • Si agrega un tercer argumento (cualquier cosa, excepto un literal 0), en lugar de copiarlo, imprimirá al estándar un informe de lo que haría, con tamaños de archivos en bytes antepuestos, por ejemplo

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

    Observe que estos están en orden ascendente por tamaño.

  • El cpcomando en la línea 34 es un comando de shell literal, por lo que puede hacer lo que quiera con los interruptores (solo solía -apreservar todos los rasgos).

  • File::Findy File::Basenameson ambos módulos principales, es decir, están disponibles en todas las instalaciones de perl.

encerrada dorada
fuente
Podría decirse que esta es la única respuesta correcta aquí. ¿O fue ... el título, solo cambió ...? Mi ventana del navegador se llama cp - copy smallest files first?pero el título de la publicación es copy smallest files first?De todos modos, las opciones nunca duelen es mi filosofía, pero aún así, tú y David son los únicos que usaron cpy tú eres el único que lo logró.
mikeserv
@mikeserv La única razón que usé cpfue porque es la forma más simple de preservar las características del archivo * nix en el perl (orientado a múltiples plataformas). La razón por la que dice la barra de su navegador cp - se debe a una función SE (IMO bobo) por la cual la más popular de las etiquetas seleccionadas aparece prefijada al título real.
Ricitos
Ok, entonces retiro mi cumplido. En realidad no, no se ve a menudo pearlsalir de la carpintería por aquí.
mikeserv
1

otra opción sería usar cp con la salida de du:

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

Esto aún podría hacerse en una línea, pero lo dividí para que pueda leerlo

David Wilkins
fuente
¿No necesita al menos hacer algo sobre $ IFS?
mikeserv
Sí ... sigo suponiendo que nadie tiene nuevas líneas en sus nombres de archivo
David Wilkins
1
Esto tampoco parece manejar la recursividad a través de la jerarquía de directorios que describió el OP.
cpugeniusmv
1
@cpugeniusmv Correcto ... De alguna manera me perdí la parte recursiva ... Podría modificar esto para manejar la recursividad, pero creo que en este punto otras respuestas hacen un mejor trabajo. Dejaré esto aquí en caso de que ayude a alguien que vea la pregunta.
David Wilkins
1
@DavidWilkins: esto ayuda mucho.
nbubis