Linux - Kopiuj tylko nowe i większe pliki

2

Mam dwa katalogi z tysiącami plików, które zawierają mniej więcej te same pliki.

Jak mogę skopiować wszystkie pliki z katalogu dirA do katalogu dirB, które nie znajdują się w katalogu dirB lub jeśli plik istnieje w katalogu dirB, nadpisuje go tylko wtedy, gdy jest mniejszy.

Wiem, że istnieje wiele przykładów różnych znaczników czasu lub różnych rozmiarów plików, ale chcę je zastąpić tylko wtedy, gdy plik docelowy jest mniejszy i w żadnym wypadku nie jest większy.

Tło mojego problemu:
Wyrenderowałem dynmapę na moim serwerze Minecraft, ale niektórych kafelków brakuje lub są one uszkodzone. Następnie wykonałem renderowanie ponownie na innym komputerze z szybszym procesorem i skopiowałem wszystkie nowe renderowane pliki (~ 50 GB / 6.000.000 ~ 4-10 KB PNG) na mój serwer. Potem zauważyłem, że w moim nowym renderowaniu są również uszkodzone pliki.

left: stary render, prawo: nowy render

stary 1 uszkodzony nowy 1

stary 2 nowe 2 uszkodzone

Dlatego nie chcę nadpisywać wszystkich plików, ale tylko te, które są większe (uszkodzone przenoszą mniej danych i są mniejsze).

das Keks
źródło
Używaj cpz kombinacją cmppoleceń lub lepiej korzystaj z rsyncwszystkich dostępnych opcji
Alex
Jakiej opcji muszę używać z rsync? Nie znalazłem niczego dla większych plików, tylko nowszych lub o innym rozmiarze. Dlatego spytałem.
das Keks,
Użyj statna plikach w obu lokalizacjach, aby uzyskać rozmiar pliku, a następnie skopiuj, jeśli spełnia twoje warunki
Alex
Cóż, jest to wyzwanie, szukałem rsyncopcji, których potrzebujesz, ale nie znalazłem właściwej, więc poszedłem w prosty sposób
Alex

Odpowiedzi:

2

Może to brudny sposób, ale mam nadzieję, że tego właśnie szukasz

#!/bin/bash

### Purpose:
# Copy huge amount of files from source to destination directory only if
# destination file is smaller in size than in source directory
###

src='./d1' # Source directory
dst='./d2' # Destination directory

icp() {
  f="${1}";
  [ -d "$f" ] && {
    [ ! -d "${dst}${f#$src}" ] && mkdir -p "${dst}${f#$src}";
    return
  }

  [ ! -f "${dst}/${f#$src/}" ] && { cp -a "${f}" "${dst}/${f#$src/}"; return; }
  fsizeSrc=$( stat -c %s "$f" )
  fsizeDst=$( stat -c %s "${dst}/${f#$src/}" )
  [ ${fsizeDst} -lt ${fsizeSrc} ] && cp -a "${f}" "${dst}/${f#$src/}"
}

export -f icp
export src
export dst

find ${src} -exec bash -c 'icp "$0"' {} \;
Alex
źródło
Dzięki. Przetestowałem to z pewnymi danymi testowymi i działa tak, jak tego potrzebuję. Ale gdy chcę go wykonać na moje prawdziwe dane mam problem ponieważ katalog zawiera zbyt wiele plików (około 6.000.000): ls argument list too long)
das Keks
Jest to limit systemu operacyjnego (możesz go uzyskać dla swojego systemu jako getconf ARG_MAX:). Prawdopodobnie masz tam dość długie nazwy plików lub bardzo głęboką strukturę katalogów, więc przy findpodawaniu lstakich nazw przekracza maksymalną dozwoloną długość dla wiersza poleceń. Zmodyfikowałem mały skrypt, aby wyeliminować lspolecenie, możesz wypróbować tę nową wersję.
Alex
Jeśli skrypt ponownie zadławi się, możesz spróbować zmniejszyć pełną ścieżkę, instalując ją na krótkiej ścieżce. Na przykład sudo mkdir -m 777 /anastępnie zamontuj katalog źródłowy, /atak jak sudo mount --bind /pretty/long/prefix/to/source/directory /awtedy użyj /aw moim skrypcie. Po zakończeniu odmontuj /awedług polecenia wydania:sudo umount /a
Alex
Myślę, że nie jest to długość ścieżki, ponieważ najdłuższa ścieżka (łącznie z nazwą pliku) ma około 80 znaków. Czy może to być lista, która jest przekazywana do każdego, która jest za długa? Myślę, że to pytanie dotyczy czegoś podobnego: unix.stackexchange.com/questions/128559/…
das Keks
Może być diff --brief -r dir1/ dir2/dobrym podejściem, a następnie zrób coś dla każdego wiersza wyniku. Spróbuję wieczorem zbudować coś takiego.
das Keks,
1

Możesz użyć polecenia rsync

Składnia:

-a = archive mode
-v = increase verbosity
-z = compress file data during the transfer
--progress = show progress during transfer

rsync -avz --progress <source path> <destination path>

możesz użyć --deletedo usunięcia obcych plików z katalogu docelowego

rsync -avz --delete --progress <source path> <destination path>

więc twoje polecenie będzie:

rsync -avz --delete --progress dirA dirB
Pankaj Jackson
źródło
1
Czy flaga -a kopiuje wszystkie pliki, które mają nowszą sygnaturę czasową lub inny rozmiar pliku? Ważne jest, aby zastąpić tylko mniejsze pliki.
das Keks,
to polecenie niczego nie zastąpi, spowoduje to skopiowanie tylko zmienionego pliku i nowego pliku, który nie jest dostępny w aplikacji Destination Director.
Pankaj Jackson
Zmienione pliki zostaną zastąpione w miejscu docelowym. Bez względu na rozmiar pliku docelowego. Przetestowałem to z pewnymi danymi, a opcja -a nie jest tym, czego potrzebuję.
das Keks
0

Mój problem był podobny. Chciałem zsynchronizować pliki z folderu zdalnego do lokalnego, ale skopiuj tylko pliki zdalne, które były większe niż odpowiednie pliki lokalne.

Moje obejście z rsync było takie, co w rzeczywistości było jednoznaczne:

for x in $(ls -1 home/me/local/folder/*)
do
    eachsize=$(stat -c "%s")
    rsync -avz --progress --max-size=${eachsize} remote:/home/you/folder/${x} .
done

Myślę, że można zrozumieć, ponieważ nazwy plików są takie same między dwoma folderami, przeglądam każdy z nich w folderze lokalnym i zachowuję jego rozmiar, a następnie ustalam, czy rsync powinien kopiować, czy nie, plik zdalny ta sama nazwa, ale inny rozmiar.

użytkownik32916
źródło
Nie używaj w lsten sposób; po prostu zrób for x in home/me/local/folder/*.
G-Man,
Masz rację; ale tylko po to, aby o tym powiedzieć.
user32916
0

Zmodyfikowałem to do czegoś takiego:

# Copy src to destination if the src is larger.
function copy_if_larger() {
  local src="$1"
  local dest="$2"

  [ ! -f "$1" ] return
  [ ! -f "$2" ] return

  local srcSize=$( stat -c %s "$1")
  local dstSize=$( stat -c %s "$2")

  [ ${dstSize} -lt ${srcSize} ] && {
    cp -a "$1" "$2"
  }
  return
}

Następnie napisałem inną metodę dostosowania plików, które chcę skopiować, i podaję je do funkcji copy_if_larger.

function do_copy_if_larger() {
  # trim prefix
  local suffix=$(echo "$1" | cut -c 10-)
  copy_if_larger "$1" "/dest/path/$suffix"
}

# make the functions visible to the subshell.
export -f copy_if_larger
export -f do_copy_if_larger

# copy all larger jpeg files over /dest/path
find . -name '*jpg' | xargs -n 1 bash -c 'do_copy_if_larger "$@"' {}
Gibon
źródło