skopiować najpierw najmniejsze pliki?

15

Mam duży katalog zawierający podkatalogi i pliki, które chcę kopiować rekurencyjnie.

Czy jest jakiś sposób, aby powiedzieć cp, że powinien wykonać operację kopiowania w kolejności wielkości pliku, aby najpierw skopiować najmniejsze pliki?

nbubis
źródło
1
Aby mieć pewność, że nie występuje problem XY , czy możesz wyjaśnić, dlaczego chcesz to zrobić?
złotowłosa
4
@ TAFKA'goldilocks '- Mam dużo plików wideo i chciałbym przetestować jakość każdego katalogu. Najmniejszy film da mi szybkie wskazanie, czy reszta plików też jest zła.
nbubis

Odpowiedzi:

10

Wykonuje to całą pracę za jednym razem - we wszystkich katalogach potomnych, wszystko w jednym strumieniu bez problemów z nazwami plików. Skopiuje od najmniejszego do największego każdego pliku, który masz. Będziesz musiał, mkdir ${DESTINATION}jeśli jeszcze nie istnieje.

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 -

Ale wiesz co? Nie robi to pustych katalogów potomnych. Mógłbym dokonać przekierowania przez ten rurociąg, ale to tylko warunek wyścigu, który czeka na to, aby się wydarzyć. Najprostszy jest prawdopodobnie najlepszy. Po prostu zrób to później:

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

Lub, ponieważ Gilles ma bardzo dobry punkt w swojej odpowiedzi, aby zachować uprawnienia do katalogu, powinienem również spróbować. Myślę, że to zrobi:

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

Byłbym skłonny założyć się, że to szybciej niż mkdir tak.

mikeserv
źródło
1
Cholera mikeserv! +1
złotowłosa
3
@ TAFKA'goldilocks 'Potraktuję to jako komplement. Dziękuję bardzo.
mikeserv
15

Oto szybka i brudna metoda rsync. W tym przykładzie uważam, że wszystko poniżej 10 MB jest „małe”.

Najpierw przenieś tylko małe pliki:

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

Następnie przenieś pozostałe pliki. Przeniesione wcześniej małe pliki nie zostaną ponownie skopiowane, chyba że zostaną zmodyfikowane.

rsync -a srcdir dstdir

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

Oczywiście kolejność przesyłania plików po pliku nie jest ściśle od najmniejszej do największej, ale myślę, że może to być najprostsze rozwiązanie, które spełnia ducha Twoich wymagań.

cpugeniusmv
źródło
Tutaj otrzymujesz 2 kopie twardych linków, a miękkie linki są przekształcane w rzeczywiste pliki dla dwóch kopii każdego z nich. Zrobiłbyś o wiele lepiej z --copy-dest=DIRi / lub --compare-dest=DIRmyślę. Wiem tylko, bo musiałem dodać --hard-dereferencesię do tarpo zaksięgowaniu własną odpowiedź, ponieważ brakowało mi linki. Wydaje mi się, że i rsynctak zachowuje się bardziej specyficznie dla lokalnych systemów plików z tymi innymi - użyłem go z kluczami USB i zalałoby to magistralę, chyba że ustawię limit przepustowości. Myślę, że powinienem był użyć jednego z tych innych.
mikeserv
1
+1 za „szybką i brudną metodę”. Prostsze jest zwykle lepsze przynajmniej do celów automatyzacji i przyszłej konserwacji. Myślę, że to jest całkiem czyste. „Elegancki” kontra „kludgy” i „solidny” kontra „niestabilny” mogą czasami kolidować jako cele projektowe, ale istnieje równowaga, którą można osiągnąć, i myślę, że jest elegancki i dość solidny.
Wildcard
4

Nie cpbezpośrednio, to znacznie przekracza jego możliwości. Ale możesz ustawić wywoływanie cpplików we właściwej kolejności.

Zsh wygodnie umożliwia sortowanie plików według rozmiaru za pomocą kwalifikatora glob . Oto fragment kodu zsh, który kopiuje pliki w kolejności rosnącej od dolnej /path/to/source-directorydo dolnej /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

Zamiast pętli możesz użyć zcpfunkcji. Najpierw jednak musisz utworzyć katalog docelowy, co można zrobić w tajemniczym oneliner.

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'

To nie chroni własności katalogów źródłowych. Jeśli chcesz, musisz zapisać odpowiedni program do kopiowania, taki jak cpiolub pax. Jeśli to zrobisz, nie musisz dzwonić cpani zcpdodatkowo.

cd /path/to/source-directory
print -rN **/*(^.) **/*(.oL) | cpio -0 -p /path/to/destination-directory
Gilles „SO- przestań być zły”
źródło
2

Nie wydaje mi się, żeby można to cp -rzrobić bezpośrednio. Ponieważ może upłynąć nieokreślony czas, zanim pojawi się kreator find/ awkrozwiązanie, oto krótki skrypt w perlu:

#!/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`;
    }
} 
  • Użyj tego: ./whatever.pl /src/path /dest/path

  • Argumenty powinny być ścieżkami bezwzględnymi ; ~lub cokolwiek innego, co powłoka rozwija na ścieżkę absolutną, jest w porządku.

  • Jeśli dodasz trzeci argument (cokolwiek, z wyjątkiem literału 0), zamiast go skopiować, wydrukuje standardowy raport o tym, co by zrobił, z rozmiarami plików w bajtach, np.

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

    Zauważ, że są w porządku rosnącym według rozmiaru.

  • cpKomenda na linii 34 jest poleceniem powłoki dosłowne, więc możesz robić, co chcesz z przełączników (tylko używane -a, aby zachować wszystkie cechy).

  • File::Findi File::Basenameoba są modułami podstawowymi, tzn. są dostępne we wszystkich instalacjach Perla.

Złotowłosa
źródło
prawdopodobnie jest to jedyna poprawna odpowiedź tutaj. Czy to był ... tytuł - właśnie się zmieniłem ...? Moje okno przeglądarki jest wywoływane, cp - copy smallest files first?ale tytuł postu jest po prostu. W copy smallest files first?każdym razie opcje nigdy nie zaszkodzą mojej filozofii, ale ty i David jesteście jedynymi, którzy skorzystali, cpa ty jesteś jedynym, który to zrobił.
mikeserv
@mikeserv Jedynym powodem, dla którego użyłem cpbyło to, że jest to najprostszy sposób na zachowanie właściwości pliku * nix w perlu (zorientowanym na wiele platform). Powodem, dla którego wyświetla się pasek przeglądarki, cp - jest funkcja SE (niemądra), w wyniku której najpopularniejszy z wybranych tagów pojawia się przed rzeczywistym tytułem.
goldilocks,
Ok, wycofuję komplement. Nie bardzo, często nie widzisz, pearljak wychodzi z tutejszej stolarki.
mikeserv
1

inną opcją byłoby użycie cp z wyjściem du:

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

Można to nadal zrobić w jednym wierszu, ale podzieliłem go, abyś mógł go przeczytać

David Wilkins
źródło
Nie musisz przynajmniej robić czegoś z $ IFS?
mikeserv
Tak ...
David Wilkins
1
To również nie wydaje się obsługiwać rekurencji przez hierarchię katalogów opisaną przez OP.
cpugeniusmv
1
@cugugususmv Prawidłowo ... W jakiś sposób przegapiłem część rekurencyjną ... Mógłbym to zmodyfikować, aby obsługiwać rekurencję, ale myślę, że w tym momencie inne odpowiedzi lepiej sobie radzą. Zostawię to tutaj na wypadek, gdyby pomogło to komuś, kto zobaczy pytanie.
David Wilkins,
1
@DavidWilkins - to bardzo pomaga.
nbubis