скопіювати найменші файли спочатку?


15

У мене є великий каталог, що містить підкаталоги та файли, які я хочу копіювати рекурсивно.

Чи є спосіб сказати, cpщо він повинен виконувати операцію копіювання в порядку розміру файлу, щоб найменші файли були скопійовані спочатку?


1
Щоб переконатися, що проблема XY не пов'язана, ви можете пояснити, чому ви хочете це зробити?
goldilocks

4
@ TAFKA'goldilocks '- у мене багато відеофайлів, і я хотів би перевірити якість кожного каталогу. Найменше відео дасть мені швидку вказівку, якщо решта файлів також погані.
nbubis

Відповіді:


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
Чорт ти, мікесерв! +1
goldilocks

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 за "швидкий і брудний метод". Простіше, як правило, краще принаймні для цілей автоматизації та майбутнього ремонту. Я думаю, що це насправді досить чисто. "Елегантний" проти "безглуздий" і "надійний" проти "нестабільний" іноді можуть конфліктувати як цілі дизайну, але є хороший баланс, який можна досягти, і я думаю, що це елегантно і досить міцно.
Wildcard

4

Не cpбезпосередньо, це далеко за її можливостями. Але ви можете домовитись зателефонувати cpпо файлам у потрібному порядку.

Zsh зручно дозволяє сортувати файли за розміром за допомогою глобального класифікатора . Ось фрагмент 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 характеристики файлу в (per-platform-орієнтованому) perl. Причина, яку говорить ваш рядок веб-переглядача cp - , пов’язана з функцією SE (goofy IMO) SE, згідно з якою найпопулярніший із вибраних тегів відображається з префіксом до власного заголовка.
goldilocks

Гаразд, тоді я відкликаю комплімент. Насправді, ви не часто бачите, 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
Це також, здається, не справляється з рекурсією через ієрархію каталогів, описану в ОП.
cpugeniusmv

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

1
@DavidWilkins - це дуже допомагає.
nbubis
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.