сначала скопировать самые маленькие файлы?


15

У меня есть большой каталог, содержащий подкаталоги и файлы, которые я хочу копировать рекурсивно.

Есть ли способ сказать, cpчто он должен выполнять операцию копирования в порядке размера файла, чтобы сначала копировать самые маленькие файлы?


1
Просто чтобы убедиться, что проблема XY не связана, можете ли вы объяснить, почему вы хотите это сделать?
Златовласка

4
@ TAFKA'goldilocks '- у меня много видеофайлов, и я хотел бы проверить качество каждого каталога. Самое маленькое видео даст мне быструю информацию о том, что все остальные файлы тоже плохие.
Нбубис

Ответы:


10

Это делает всю работу за один раз - во всех дочерних каталогах, все в одном потоке без каких-либо проблем с именами файлов. Он будет копировать от самого маленького до самого большого файла, который у вас есть. Вам нужно будет, mkdir ${DESTINATION}если он еще не существует.

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 -

Вы знаете что, хотя? Чего это не делает, так это пустых дочерних каталогов. Я мог бы сделать некоторое перенаправление по этому конвейеру, но это просто состояние гонки, которое должно произойти. Самый простой, вероятно, лучший. Просто сделайте это потом:

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

Или, поскольку Жиль очень хорошо указал в своем ответе на вопрос о сохранении разрешений для каталогов, я должен попытаться также. Я думаю, что это сделает это:

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

Я был бы готов поспорить, что это быстрее, чем в mkdirлюбом случае.


1
Черт возьми, mikeserv! +1
Златовласка

3
@ TAFKA'goldilocks 'Я приму это как комплимент. Огромное спасибо.
mikeserv

15

Вот быстрый и грязный метод с использованием rsync . Для этого примера я рассматриваю что-нибудь под 10 МБ, чтобы быть "маленьким".

Сначала передайте только небольшие файлы:

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

Затем перенесите оставшиеся файлы. Ранее переданные небольшие файлы не будут повторно скопированы, если они не были изменены.

rsync -a srcdir dstdir

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

Конечно, порядок передачи файлов за файлом не является строго наименьшим, но я думаю, что это может быть простейшее решение, которое соответствует духу ваших требований.


Здесь вы получаете 2 копии жестких ссылок, а софт-ссылки преобразуются в реальные файлы по две копии каждой. Вы бы сделали намного лучше с --copy-dest=DIRи / или --compare-dest=DIRя думаю. Я знаю только потому, что мне пришлось добавить --hard-dereferenceсебя tarпосле публикации моего собственного ответа, потому что я пропускал ссылки. Я думаю, что на rsyncсамом деле ведет себя более специфично для локальных файловых систем с другими - в любом случае я использовал его с USB-ключами, и он заполнил бы шину, если бы я не установил ограничение пропускной способности. Я думаю, что я должен был использовать любой из этих других вместо этого.
mikeserv

1
+1 за "быстрый и грязный метод". Проще обычно лучше, по крайней мере, для целей автоматизации и дальнейшей ремонтопригодности. Я думаю, что это на самом деле довольно чисто. «Элегантный» против «грязный» и «надежный» против «неустойчивый» может иногда вступать в противоречие с целями дизайна, но есть хороший баланс, который можно достичь, и я думаю, что это элегантно и достаточно надежно.
Подстановочный

4

Не cp напрямую, это намного выше его способностей. Но вы можете организовать вызов cpфайлов в правильном порядке.

Zsh позволяет удобно сортировать файлы по размеру с помощью квалификатора glob . Вот фрагмент zsh, который копирует файлы в порядке возрастания размера от нижнего /path/to/source-directoryдо нижнего /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

Вместо цикла вы можете использовать zcpфункцию. Однако сначала нужно создать каталоги назначения, что можно сделать в загадочном документе.

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'

Это не сохраняет владения исходными каталогами. Если вы хотите этого, вам нужно подключить подходящую программу копирования, такую ​​как cpioили pax. Если вы это сделаете, вам не нужно звонить cpили zcpдополнительно.

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

2

Я не думаю, что есть какой-то способ cp -rсделать это напрямую. Поскольку может пройти неопределенный период времени, прежде чем вы получите волшебное find/ awkрешение, вот быстрый скрипт на 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`;
    }
} 
  • Использовать этот: ./whatever.pl /src/path /dest/path

  • Оба аргумента должны быть абсолютными путями ; ~или что-либо еще, что расширяет оболочку по абсолютному пути, это нормально.

  • Если вы добавите третий аргумент (что угодно, кроме литерала 0), вместо его копирования будет распечатан стандартный отчет о том, что он будет делать, с предварительно добавленными размерами файлов, например

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

    Обратите внимание, что они в порядке возрастания по размеру.

  • Команда cpв строке 34 является буквальной командой оболочки, так что вы можете делать с переключателями все, что захотите (я просто использовал -aдля сохранения всех характеристик).

  • File::Findи File::Basenameоба являются основными модулями, т.е. они доступны во всех установках perl.


возможно, это единственный правильный ответ здесь. Или это было ... название - просто изменилось ...? Окно моего браузера называется, cp - copy smallest files first?но название поста просто В copy smallest files first?любом случае, варианты никогда не повредят - это моя философия, но, тем не менее, вы и Дэвид - единственные, кто их использовал, cpи вы единственный, кто их выполнил.
mikeserv

@mikeserv Единственная причина, которую я использовал, cpзаключалась в том, что это самый простой способ сохранить характеристики файла * nix в (кроссплатформенном) Perl. Причина, по которой ваш браузер говорит, cp - заключается в том, что (IMO глупая) функция SE, благодаря которой наиболее популярный из выбранных тегов появляется с префиксом к реальному заголовку.
Златовласка

Хорошо, тогда я снимаю свой комплимент. Не совсем, вы не часто видите pearlвыход из дерева здесь.
mikeserv

1

другой вариант будет использовать cp с выводом из du:

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

Это все еще можно сделать в одной строке, но я разделил его, чтобы вы могли прочитать его


Разве вам не нужно что-то делать с $ IFS?
mikeserv

Да ... Я продолжаю предполагать, что ни у кого нет новых строк в именах файлов
Дэвид Уилкинс

1
Похоже, что это также не обрабатывает рекурсию через иерархию каталогов, описанную OP.
cpugeniusmv

1
@cpugeniusmv Правильно ... Я как-то пропустил рекурсивную часть .... Я мог бы изменить это для обработки рекурсии, но я думаю, что в этот момент другие ответы делают лучше. Я оставлю это здесь на случай, если это поможет тому, кто видит вопрос.
Дэвид Уилкинс

1
@DavidWilkins - это очень помогает.
Нбубис
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.