Kleinste Dateien zuerst kopieren?


15

Ich habe ein großes Verzeichnis mit Unterverzeichnissen und Dateien, die ich rekursiv kopieren möchte.

Gibt es eine Möglichkeit, anzugeben, cpdass der Kopiervorgang in der Reihenfolge der Dateigröße ausgeführt werden soll, damit die kleinsten Dateien zuerst kopiert werden?


1
Können Sie erklären, warum Sie dies tun möchten , um sicherzugehen, dass kein XY-Problem vorliegt ?
Goldlöckchen

4
@ TAFKA'goldilocks '- Ich habe viele Videodateien und möchte jedes Verzeichnis auf Qualität testen. Das kleinste Video gibt mir einen schnellen Hinweis darauf, ob die restlichen Dateien ebenfalls fehlerhaft sind.
nbubis

Antworten:


10

Dies erledigt den gesamten Job auf einmal - in allen untergeordneten Verzeichnissen, alle in einem einzigen Stream ohne Dateinamenprobleme. Jede Datei, die Sie haben, wird vom kleinsten zum größten kopiert. Sie müssen, mkdir ${DESTINATION}wenn es noch nicht existiert.

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 -

Weißt du was? Was dies nicht tut, sind leere untergeordnete Verzeichnisse. Ich könnte eine Umleitung über diese Pipeline vornehmen, aber es ist nur eine Rennbedingung, die darauf wartet, dass sie eintritt. Am einfachsten ist es wahrscheinlich am besten. Also mach das einfach nachher:

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

Oder ich sollte es auch versuchen, da Gilles in seiner Antwort auf die Beibehaltung von Verzeichnisberechtigungen einen sehr guten Standpunkt vertritt. Ich denke das wird es schaffen:

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

Ich wäre bereit zu wetten, dass das schneller ist als überhaupt mkdir.


1
Verdammt du mikeserv! +1
Goldlöckchen

3
@Tafka'goldilocks 'Ich nehme das als Kompliment. Vielen Dank.
mikeserv

15

Hier ist eine schnelle und schmutzige Methode mit rsync. In diesem Beispiel halte ich alles unter 10 MB für "klein".

Übertragen Sie zunächst nur die kleinen Dateien:

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

Übertragen Sie dann die restlichen Dateien. Die zuvor übertragenen kleinen Dateien werden erst dann erneut kopiert, wenn sie geändert wurden.

rsync -a srcdir dstdir

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

Natürlich ist die Reihenfolge der Übertragung von Datei zu Datei nicht unbedingt die kleinste bis größte, aber ich denke, es ist die einfachste Lösung, die dem Geist Ihrer Anforderungen entspricht.


Hier erhalten Sie 2 Kopien von Hardlinks und Softlinks werden zu jeweils zwei Kopien in tatsächliche Dateien umgewandelt. Du würdest es viel besser machen --copy-dest=DIRund / oder --compare-dest=DIRich denke. Ich weiß es nur, weil ich --hard-dereferencemich selbst hinzufügen musste , tarnachdem ich meine eigene Antwort gepostet hatte, weil mir die Links fehlten. Ich denke, rsynctatsächlich verhält es sich sowieso spezifischer für lokale Dateisysteme als für diese anderen - ich habe es früher mit USB-Sticks verwendet und es würde den Bus überfluten, wenn ich kein Bandbreitenlimit festlege. Ich denke, ich hätte stattdessen einen dieser anderen verwenden sollen.
mikeserv

1
+1 für die "schnelle und schmutzige Methode". Einfacher ist in der Regel besser, zumindest aus Gründen der Automatisierung und der zukünftigen Wartbarkeit. Ich finde das eigentlich ziemlich sauber. "Elegant" vs "kludgy" und "robust" vs "unstable" können manchmal als Designziele in Konflikt geraten, aber es gibt eine gute Balance, die getroffen werden kann, und ich denke, das ist elegant und ziemlich robust.
Wildcard

4

Nicht cp direkt, das geht weit über seine Fähigkeiten hinaus. Sie können cpdie Dateien jedoch in der richtigen Reihenfolge aufrufen .

Mit Zsh können Sie Dateien bequem mit einem Glob-Qualifier nach Größe sortieren . Hier ist ein zsh-Snippet, das Dateien in aufsteigender Reihenfolge von unter /path/to/source-directorynach unter kopiert /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

Anstelle einer Schleife können Sie die zcpFunktion verwenden. Sie müssen jedoch zuerst die Zielverzeichnisse erstellen, was in einem kryptischen Oneliner erfolgen kann.

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'

Dadurch bleibt der Besitz der Quellverzeichnisse nicht erhalten. Wenn Sie das möchten, müssen Sie ein geeignetes Kopierprogramm wie cpiooder eintragen pax. Wenn Sie das tun, müssen Sie nicht anrufen cpoder zcpzusätzlich.

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

2

Ich glaube nicht, dass es eine Möglichkeit gibt, cp -rdies direkt zu tun. Da es eine unbestimmte Zeit dauern kann, bis Sie eine Wizard find/ awkLösung erhalten, folgt ein kurzes Perl-Skript:

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

  • Die Argumente sollten beide absolute Pfade sein ; ~, oder alles andere, was die Shell zu einem absoluten Pfad erweitert, ist in Ordnung.

  • Wenn Sie ein drittes Argument (außer einem Literal 0) hinzufügen , wird anstelle des Kopierens ein Bericht mit vorangestellten Dateigrößen in Bytes ausgegeben, z

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

    Beachten Sie, dass diese nach Größe aufsteigend sortiert sind.

  • Der cpBefehl in Zeile 34 ist ein wörtlicher Shell-Befehl, sodass Sie mit den Schaltern alles tun können, was Sie wollen (ich habe gerade -aalle Merkmale beibehalten).

  • File::Findund File::Basenamesind beide Kernmodule, dh sie sind in allen Installationen von Perl verfügbar.


Das ist wohl die einzig richtige Antwort. Oder war es ... der Titel - gerade geändert ...? Mein Browserfenster heißt, cp - copy smallest files first?aber der Titel des Beitrags ist auf jeden copy smallest files first?Fall, Optionen, die nie schaden, sind meine Philosophie, aber trotzdem sind Sie und David die einzigen, die verwendet werden cpund Sie sind die einzigen, die es geschafft haben.
mikeserv

@mikeserv Der einzige Grund, den ich benutzte, cpwar, dass es der einfachste Weg ist, * nix-Dateieigenschaften in der (plattformübergreifenden) Perl zu erhalten. Der Grund, den Ihre Browserleiste angibt, cp - ist eine (IMO goofy) SE-Funktion, bei der das beliebteste der ausgewählten Tags vor dem eigentlichen Titel angezeigt wird.
Goldlöckchen

Ok, dann ziehe ich mein Kompliment zurück. Nicht wirklich, man sieht pearlhier nicht oft Holzarbeiten.
mikeserv

1

Eine andere Möglichkeit wäre, cp mit der Ausgabe von du zu verwenden:

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

Dies könnte immer noch in einer Zeile geschehen, aber ich habe es aufgeteilt, damit Sie es lesen können


Müssen Sie nicht wenigstens etwas gegen $ IFS unternehmen?
mikeserv

Ja ... Ich gehe immer davon aus, dass niemand Zeilenumbrüche in seinen Dateinamen hat
David Wilkins

1
Dies scheint auch die Rekursion durch die vom OP beschriebene Verzeichnishierarchie nicht zu handhaben.
Cpugeniusmv

1
@cpugeniusmv Richtig ... Ich habe den rekursiven Teil irgendwie verpasst .... Ich könnte dies ändern, um die Rekursion zu handhaben, aber ich denke, an diesem Punkt machen andere Antworten einen besseren Job. Ich lasse das hier, falls es jemandem hilft, der die Frage sieht.
David Wilkins

1
@DavidWilkins - das hilft sehr.
Nbubis
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.