salin file terkecil dulu?

15

Saya memiliki direktori besar yang berisi subdirektori dan file yang ingin saya salin secara rekursif.

Apakah ada cara untuk mengatakan cpbahwa ia harus melakukan operasi penyalinan sesuai ukuran file, sehingga file terkecil disalin terlebih dahulu?

nbubis
sumber
1
Hanya untuk memastikan tidak ada masalah XY yang terlibat, dapatkah Anda menjelaskan mengapa Anda ingin melakukan ini?
goldilocks
4
@ TAFKA'goldilocks '- Saya punya banyak file video, dan saya ingin menguji kualitas setiap direktori. Video terkecil akan memberi saya indikasi cepat jika sisa file buruk juga.
nbubis

Jawaban:

10

Ini melakukan seluruh pekerjaan dalam satu jalan - di semua direktori anak, semua dalam satu aliran tanpa masalah nama file. Ini akan menyalin dari terkecil ke terbesar setiap file yang Anda miliki. Anda harus melakukannya mkdir ${DESTINATION}jika belum ada.

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 -

Kamu tahu apa? Apa yang tidak dilakukan adalah direktori anak kosong . Saya bisa melakukan beberapa pengalihan atas pipa itu, tapi itu hanya kondisi perlombaan yang menunggu untuk terjadi. Sederhana mungkin yang terbaik. Jadi lakukan saja ini setelahnya:

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

Atau, karena Gilles membuat poin yang sangat bagus dalam jawabannya untuk menjaga izin direktori, saya harus mencoba juga. Saya pikir ini akan melakukannya:

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

Saya bersedia bertaruh itu lebih cepat daripada mkdiritu.

mikeserv
sumber
1
Sialan kau mikeserv! +1
goldilocks
3
@ TAFKA'goldilocks 'Saya akan menganggap itu sebagai pujian. Terima kasih banyak.
mikeserv
15

Berikut adalah metode cepat dan kotor menggunakan rsync. Untuk contoh ini saya menganggap apa pun di bawah 10 MB menjadi "kecil".

Transfer pertama hanya file kecil:

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

Kemudian transfer file yang tersisa. File kecil yang sebelumnya ditransfer tidak akan disalin ulang kecuali jika diubah.

rsync -a srcdir dstdir

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

Tentu saja, urutan transfer file per file tidak sepenuhnya terkecil hingga terbesar, tapi saya pikir itu mungkin solusi paling sederhana yang memenuhi semangat kebutuhan Anda.

cpugeniusmv
sumber
Di sini Anda mendapatkan 2 salinan tautan keras dan tautan lunak diubah menjadi file aktual untuk masing-masing dua salinan. Anda akan melakukan jauh lebih baik dengan --copy-dest=DIRdan / atau --compare-dest=DIRsaya pikir. Saya hanya tahu karena saya harus menambahkan --hard-dereferencediri tarsetelah memposting jawaban saya sendiri karena saya kehilangan tautan. Saya pikir rsyncsebenarnya berperilaku lebih spesifik untuk sistem file lokal dengan yang lain - saya dulu menggunakannya dengan kunci USB dan itu akan membanjiri bus kecuali saya menetapkan batas bandwidth. Saya pikir saya harus menggunakan salah satu dari yang lain sebagai gantinya.
mikeserv
1
+1 untuk "metode cepat dan kotor". Simpler biasanya lebih baik setidaknya untuk tujuan otomatisasi dan pemeliharaan masa depan. Saya pikir ini sebenarnya cukup bersih. "Elegan" vs "Kludgy" dan "kuat" vs "tidak stabil" kadang-kadang dapat bertentangan sebagai tujuan desain, tetapi ada keseimbangan yang baik yang dapat dicapai, dan saya pikir ini elegan dan cukup kuat.
Wildcard
4

Tidak cpsecara langsung, itu jauh di luar kemampuannya. Tetapi Anda dapat mengatur untuk memanggil cpfile dalam urutan yang benar.

Zsh dengan mudah memungkinkan pengurutan file berdasarkan ukuran dengan kualifikasi glob . Berikut cuplikan zsh yang menyalin file dengan urutan ukuran yang meningkat dari bawah /path/to/source-directoryke bawah /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

Alih-alih loop, Anda dapat menggunakan zcpfungsi ini. Namun Anda harus membuat direktori tujuan terlebih dahulu, yang dapat dilakukan di oneliner samar.

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'

Ini tidak mempertahankan kepemilikan direktori sumber. Jika Anda menginginkannya, Anda harus mendaftar program penyalinan yang sesuai seperti cpioatau pax. Jika Anda melakukannya, Anda tidak perlu menelepon cpatau zcpmenambahkan.

cd /path/to/source-directory
print -rN **/*(^.) **/*(.oL) | cpio -0 -p /path/to/destination-directory
Gilles 'SO- berhenti menjadi jahat'
sumber
2

Saya tidak berpikir ada cara cp -runtuk melakukan ini secara langsung. Karena ini mungkin periode waktu yang tidak ditentukan sebelum Anda mendapatkan wizardly find/ awksolusi, berikut ini skrip perl cepat:

#!/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`;
    }
} 
  • Gunakan ini: ./whatever.pl /src/path /dest/path

  • Argumen harus keduanya menjadi jalur absolut ; ~, atau apa pun yang shell mengembang ke jalur absolut baik-baik saja.

  • Jika Anda menambahkan argumen ketiga (apa pun, kecuali literal 0), alih-alih menyalinnya akan mencetak ke standar laporan apa yang akan dilakukan, dengan ukuran file dalam byte yang ditambahkan, misalnya,

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

    Perhatikan ini dalam urutan menurut ukuran.

  • The cpperintah pada baris 34 adalah perintah shell literal, sehingga Anda dapat melakukan apapun yang Anda inginkan dengan switch (saya hanya menggunakan -auntuk melestarikan semua sifat-sifat).

  • File::Finddan File::Basenamekeduanya adalah modul inti, yaitu mereka tersedia di semua instalasi perl.

goldilocks
sumber
bisa dibilang, ini adalah satu-satunya jawaban yang benar di sini. Atau itu ... judul - baru saja berubah ...? Jendela peramban saya dipanggil cp - copy smallest files first?tetapi judul postingannya hanya copy smallest files first?Pokoknya, opsi tidak pernah sakit adalah filosofi saya, tetapi tetap saja, Anda dan David adalah satu-satunya yang digunakan cpdan hanya Anda yang melakukannya.
mikeserv
@ mikeserv Satu-satunya alasan yang saya gunakan cpadalah karena ini adalah cara paling sederhana untuk mempertahankan karakteristik file * nix di perl (cross-platform oriented). Alasan bilah browser Anda mengatakan cp - karena fitur SE (IMO goofy) di mana tag yang paling populer muncul muncul diawali dengan judul sebenarnya.
goldilocks
Ok, maka saya menarik pujian saya. Tidak juga, Anda tidak sering melihat pearlkeluar dari kayu di sekitar sini.
mikeserv
1

Pilihan lain adalah menggunakan cp dengan output dari du:

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

Ini masih bisa dilakukan pada satu baris, tetapi saya membaginya sehingga Anda dapat membacanya

David Wilkins
sumber
Bukankah Anda setidaknya perlu melakukan sesuatu tentang $ IFS?
mikeserv
Ya ... Saya terus mengasumsikan tidak ada yang memiliki baris baru di nama file mereka
David Wilkins
1
Ini juga tampaknya tidak menangani rekursi melalui hierarki direktori yang dijelaskan OP.
cpugeniusmv
1
@ cpugeniusmv Benar ... Saya entah bagaimana melewatkan bagian rekursif .... Saya bisa memodifikasi ini untuk menangani rekursi, tapi saya pikir pada titik ini jawaban lain melakukan pekerjaan yang lebih baik. Saya akan meninggalkan ini di sini kalau-kalau itu membantu seseorang yang melihat pertanyaan.
David Wilkins
1
@ Davidvidil - ini sangat membantu.
nbubis