copiar 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 cpque ele deve executar a operação de cópia na ordem do tamanho do arquivo, para que os arquivos menores sejam copiados primeiro?

nbubis
fonte
1
Apenas para ter certeza de que não há um problema XY envolvido, você pode explicar por que deseja fazer isso?
Goldilocks
4
@ TAFKA'goldilocks '- tenho muitos arquivos de vídeo e gostaria de testar a qualidade de cada diretório. O menor vídeo me dará uma indicação rápida de se o restante dos arquivos também está ruim.
Nbubis

Respostas:

10

Isso faz todo o trabalho de uma só vez - em todos os diretórios filhos, tudo em um único fluxo, sem problemas de nome de arquivo. Ele copiará do menor para o maior todos os arquivos que você tiver. Você precisará mkdir ${DESTINATION}se ele 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? O que isso não faz é diretórios filhos vazios . 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 argumento muito bom em sua resposta para preservar as permissões de diretório, devo tentar também. Eu acho que isso fará isso:

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

Eu estaria disposto a apostar que é mais rápido do que mkdirnunca.

mikeserv
fonte
1
Maldito mikeserv! +1
goldilocks
3
@ TAFKA'goldilocks 'Vou aceitar isso como um elogio. Muito obrigado.
mikeserv
15

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

Primeiro transfira apenas os arquivos pequenos:

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

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

rsync -a srcdir dstdir

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

Obviamente, a ordem de transferência arquivo a arquivo não é estritamente do menor para o maior, mas acho que pode ser a solução mais simples que atende ao espírito de seus requisitos.

cpugeniusmv
fonte
Aqui você recebe 2 cópias de links físicos e links flexíveis são transformados em arquivos reais para duas cópias de cada. Você faria muito melhor com --copy-dest=DIRe / ou --compare-dest=DIReu acho. Só sei porque tive que --hard-dereferenceme adicionar tardepois de postar minha própria resposta porque estava faltando os links. Eu acho que rsyncrealmente se comporta de maneira mais específica aos sistemas de arquivos locais com os outros - eu costumava usá-lo com chaves USB e inundava o barramento, a menos que eu definisse um limite de largura de banda. Acho que deveria ter usado um desses outros.
mikeserv
1
+1 para o "método rápido e sujo". Mais simples é geralmente melhor, pelo menos para fins de automação e manutenção futura. Eu acho que isso é realmente muito limpo. Às vezes, "elegante" vs "kludgy" e "robusto" vs "instável" podem entrar em conflito como objetivos de design, mas há um bom equilíbrio que pode ser alcançado, e acho que isso é elegante e bastante robusto.
Curinga
4

Não cpdiretamente, isso está muito além de suas habilidades. Mas você pode organizar cpos arquivos na ordem certa.

O Zsh convenientemente permite classificar arquivos por tamanho com um qualificador glob . Aqui está um trecho de código zsh que copia arquivos em ordem crescente de tamanho, de baixo /path/to/source-directorypara baixo /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 zcpfunção No entanto, você precisa primeiro criar os diretórios de destino, o que pode ser feito em um oneliner críptico.

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 isso, precisará inscrever um programa de cópia adequado, como cpioou pax. Se você fizer isso, não precisará telefonar cpou zcpalém disso.

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

Eu não acho que exista alguma maneira cp -rde fazer isso diretamente. Como pode ser um período indeterminado antes de você obter uma solução find/ assistente awk, aqui está um rápido 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 isto: ./whatever.pl /src/path /dest/path

  • Os argumentos devem ser ambos caminhos absolutos ; ~, ou qualquer outra coisa que o shell expanda para um caminho absoluto é bom.

  • Se você adicionar um terceiro argumento (qualquer coisa, exceto um literal 0), em vez de copiar, ele imprimirá para padronizar um relatório do que faria, com tamanhos de arquivos em bytes anexados, 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 cpcomando na linha 34 é um comando literal do shell, para que você possa fazer o que quiser com os comutadores (usei apenas -apara preservar todas as características).

  • File::Finde File::Basenamesão os dois módulos principais, ou seja, estão disponíveis em todas as instalações do perl.

Cachinhos Dourados
fonte
indiscutivelmente, esta é a única resposta correta aqui. Ou foi ... o título - acabou de mudar ...? Minha janela do navegador é chamada, cp - copy smallest files first?mas o título da postagem é copy smallest files first?De qualquer forma, as opções nunca ferem é a minha filosofia, mas você e David são os únicos que usaram cpe você é o único que conseguiu.
mikeserv
@mikeserv A única razão que usei cpfoi porque é a maneira mais simples de preservar as características do arquivo * nix no perl (orientado a várias plataformas). O motivo que a barra do navegador cp - indica é por causa de um recurso SE (IMO, pateta), pelo qual a mais popular das tags selecionadas aparece como prefixo do título real.
Goldilocks
Ok, então retiro meu elogio. Na verdade, você não vê muitas vezes pearlsaindo da madeira por aqui.
mikeserv
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 ler

David Wilkins
fonte
Você não precisa pelo menos fazer algo sobre o $ IFS?
mikeserv
Sim ... Eu continuo assumindo ninguém tem novas linhas em seus nomes
David Wilkins
1
Isso também não parece lidar com a recursão pela hierarquia de diretórios descrita pelo OP.
precisa saber é o seguinte
1
@cpugeniusmv Correto ... De alguma forma, perdi a parte recursiva .... Eu poderia modificar isso para lidar com a recursão, mas acho que nesse momento outras respostas fazem um trabalho melhor. Vou deixar isso aqui, caso isso ajude alguém que vê a pergunta.
21430 David Wilkins
1
@ DavidWilkins - isso ajuda muito.
Nbubis