copie os arquivos menores primeiro?

15

Eu tenho um diretório grande contendo subdiretórios e arquivos que desejo copiar recursivamente.

Existe alguma maneira de dizer ao cp que ele deve executar a operação de cópia na ordem do tamanho do arquivo, para que os menores arquivos sejam copiados primeiro?

    
por nbubis 08.04.2014 / 19:27

5 respostas

10

Isso faz todo o trabalho de uma só vez - em todos os diretórios filhos, tudo em um único fluxo sem nenhum problema de nome de arquivo. Ele copia do menor para o maior todo arquivo que você tem. Você precisará mkdir ${DESTINATION} , se ainda não existir.

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 -

Você sabe o que, no entanto? O que isto não faz é vazio diretórios filho. Eu poderia fazer algum redirecionamento sobre esse pipeline, mas é apenas uma condição de corrida esperando para acontecer. O mais simples é provavelmente o melhor. Então faça isso depois:

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

Ou, como Gilles faz um bom argumento em sua resposta para preservar as permissões de diretório, eu deveria tentar também. Eu acho que isso vai fazer isso:

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

Eu estaria disposto a apostar que é mais rápido que mkdir de qualquer maneira.

    
por 08.04.2014 / 21:08
15

Aqui está um método rápido e sujo usando rsync . Para este exemplo, estou considerando que qualquer coisa abaixo de 10 MB seja "pequena".

Primeiro transfira apenas os arquivos pequenos:

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

Em seguida, transfira os arquivos restantes. Os arquivos pequenos transferidos anteriormente não serão copiados novamente, a menos que tenham sido modificados.

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

É claro que a ordem de transferência arquivo por arquivo não é estritamente menor para a maior, mas acho que pode ser a solução mais simples que atende ao espírito de suas necessidades.

    
por 08.04.2014 / 20:59
4

Não cp diretamente, muito além de suas habilidades. Mas você pode mandar chamar cp nos arquivos na ordem correta.

O Zsh permite a classificação de arquivos por tamanho com um qualificador de glob . Aqui está um trecho de zsh que copia arquivos em ordem crescente de tamanho de /path/to/source-directory para /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

Em vez de um loop, você pode usar a função zcp . No entanto, você precisa criar os diretórios de destino primeiro, o que pode ser feito em um oneliner enigmático.

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'

Isso não preserva a propriedade dos diretórios de origem. Se você quiser, precisará inscrever um programa de cópia adequado, como cpio ou pax . Se você fizer isso, não precisará chamar cp ou zcp .

cd /path/to/source-directory
print -rN **/*(^.) **/*(.oL) | cpio -0 -p /path/to/destination-directory
    
por 09.04.2014 / 02:04
2

Eu não acho que haja alguma maneira de obter cp -r para fazer isso diretamente. Como pode ser um período de tempo indeterminado antes de você obter uma solução find / awk do assistente, aqui está um script perl rápido:

#!/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';
    }
} 
  • Use isto: ./whatever.pl /src/path /dest/path

  • Os argumentos devem ser ambos caminhos absolutos ; ~ , ou qualquer outra coisa que o shell expande para um caminho absoluto, está bem.

  • Se você adicionar um terceiro argumento (qualquer coisa, exceto o literal 0 ), em vez de copiá-lo, imprimirá um relatório sobre o que ele faria, com tamanhos de arquivos em bytes prefixados, por exemplo.

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

    Observe que eles estão em ordem crescente por tamanho.

  • O comando cp na linha 34 é um comando shell literal, então você pode fazer o que quiser com os switches (eu usei -a para preservar todas as características).

  • File::Find e File::Basename são ambos módulos principais, ou seja, estão disponíveis em todas as instalações do perl.

por 08.04.2014 / 20:52
1

outra opção seria usar cp com a saída de du:

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

Isso ainda pode ser feito em uma linha, mas eu divido para que você possa lê-lo

    
por 08.04.2014 / 21:31