Jak mogę wykonać operację „kopiuj, jeśli zmieniono”?

34

Chciałbym skopiować zestaw plików z katalogu A do katalogu B, z zastrzeżeniem, że jeśli plik w katalogu A jest identyczny z plikiem w katalogu B, plik ten nie powinien być kopiowany (a zatem jego czas modyfikacji nie powinien być zaktualizowane). Czy można to zrobić za pomocą istniejących narzędzi, bez pisania własnego skryptu?

Aby nieco rozwinąć mój przypadek użycia: automatycznie generuję kilka .cplików w katalogu tymczasowym (metodą, która musi bezwarunkowo wygenerować je wszystkie), a kiedy je ponownie generuję, chciałbym tylko skopiować te, które zmieniły się w rzeczywisty katalog źródłowy, pozostawiając te niezmienione bez zmian (ze starymi czasami tworzenia), aby makewiedzieć, że nie trzeba ich ponownie kompilować. ( .cJednak nie wszystkie generowane pliki są plikami, więc muszę porównywać binarnie, a nie porównywać tekst).

(Uwaga: wynikało to z pytania, które zadałem na https://stackoverflow.com/questions/8981552/speeding-up-file-comparions-with-cmp-on-cygwin/8981762#8981762 , gdzie próbowałem aby przyspieszyć plik skryptu, którego używałem do wykonania tej operacji, ale przychodzi mi do głowy, że naprawdę powinienem zapytać, czy istnieje lepszy sposób na zrobienie tego niż pisanie własnego skryptu - zwłaszcza, że ​​jakikolwiek prosty sposób to zrobić w powłoce skrypt wywoła coś podobnego cmpdo każdej pary plików, a uruchomienie wszystkich tych procesów trwa zbyt długo).

Brooks Moses
źródło
1
Możesz użyć, diff -qr dirA dirBaby zobaczyć, które pliki są unikatowe dirAi dirB, odpowiednio.
1
@ Brooks-Moses to naprawdę praca nadająca się na ccache !
aculich
3
@hesse, jeśli chcesz pokazać unikalne pliki, możesz użyć diff, ale jeśli chcesz zobaczyć, co się zmieniło, użyj rsync -avnclub daleko rsync --archive --verbose --dry-run --checksum.
aculich

Odpowiedzi:

29

rsync jest prawdopodobnie najlepszym narzędziem do tego. Polecenie zawiera wiele opcji, więc przeczytaj stronę podręcznika . Myślę, że chcesz opcję --checksum lub --ignore-times

Adam Terrey
źródło
Powinienem zauważyć, że już tego próbowałem, bezskutecznie. Obie te opcje wpływają tylko na to, czy rsync wykonuje kopię - ale nawet jeśli nie robi kopii, aktualizuje czas modyfikacji pliku docelowego na taki sam jak źródło (jeśli -tpodano opcję) lub czas synchronizacji (jeśli -tnie jest określony).
Brooks Moses
4
@Brooks Moses: Nie. Przynajmniej moja wersja rsyncnie. Jeśli to zrobię :, mkdir src dest; echo a>src/a; rsync -c src/* dest; sleep 5; touch src/a; rsync -c src/* destto stat dest/apokazuje, że mtime i ctime są o 5 sekund starsze niż te src/a.
angus
@angus: Huh. OK, masz rację. Wydaje się, że kluczem jest --checksumopcja i chociaż linux.die.net/man/1/rsync nie zawiera absolutnie nic , co sugerowałoby, że ma on wpływ na to, czy data modyfikacji jest aktualizowana, powoduje jednak pozostawienie daty modyfikacji miejsca docelowego nietknięty. (Z drugiej strony --ignore-timesopcja nie ma tego efektu; wraz z nią data modyfikacji jest wciąż aktualizowana). Biorąc pod uwagę, że wydaje się to całkowicie nieudokumentowane, czy mogę na tym polegać?
Brooks Moses
2
@BrooksMoses: Myślę, że możesz na nim polegać: rsyncprzepływ pracy jest następujący: 1) sprawdź, czy plik wymaga aktualizacji; 2) jeśli tak, zaktualizuj plik. --checksumOpcja powiedzieć, że nie powinny być aktualizowane, więc rsyncnie powinien przejść do kroku 2).
enzotib
2
@BrooksMoses: --ignore-timesbez --checksumkopiowałby każdy plik, a więc także aktualizował znacznik czasu, nawet jeśli pliki są identyczne.
enzotib
13

Możesz użyć -uprzełącznika, aby cp:

$ cp -u [source] [destination]

Ze strony podręcznika:

   -u, --update
       copy only when the SOURCE file is newer than the destination file or 
       when the destination file is missing
gu1
źródło
4
Witam i witam na stronie. Oczekujemy, że odpowiedzi będą tutaj nieco bardziej znaczące. Na przykład możesz podać wyjaśnienie, co -urobi flaga i jak ona działa oraz w jaki sposób pomogłoby to PO. Jednak w tym konkretnym przypadku nie pomogłoby to PO, ponieważ kopiowałoby identyczne pliki, gdyby były nowsze, i dlatego zmieniał ich znaczniki czasu, czego dokładnie chce OP.
terdon
1
Z komentarza do podobnego A, który został już usunięty: „To nie zadziała, ponieważ skopiuje również identyczne pliki, jeśli znacznik czasu źródła jest nowszy (i dlatego zaktualizuj znacznik czasu przeznaczenia, w stosunku do żądania OP)”.
slm
W ogóle nie odpowiada na pytanie, ale nadal uważam je za przydatne.
użytkownik31389
7

Chociaż używanie rsync --checksumto dobry ogólny sposób na „skopiowanie, jeśli zmieniono”, w twoim przypadku istnieje jeszcze lepsze rozwiązanie!

Jeśli chcesz uniknąć niepotrzebnej ponownej kompilacji plików, powinieneś użyć ccache, który został zbudowany właśnie w tym celu! W rzeczywistości nie tylko pozwoli uniknąć niepotrzebnych make cleanponownych kompilacji automatycznie generowanych plików, ale także przyspieszy wszystko, gdy to zrobisz, i ponownie skompiluje od zera.

Następnie jestem pewien, że zapytasz: „Czy to jest bezpieczne?” Tak, jak wskazuje strona internetowa:

Czy to jest bezpieczne?

Tak. Najważniejszym aspektem pamięci podręcznej kompilatora jest zawsze wytwarzanie dokładnie takich samych danych wyjściowych, jakie dałby prawdziwy kompilator. Obejmuje to dostarczenie dokładnie tych samych plików obiektowych i dokładnie takich samych ostrzeżeń kompilatora, które zostałyby wygenerowane, jeśli użyjesz prawdziwego kompilatora. Jedynym sposobem, w jaki powinieneś być w stanie stwierdzić, że korzystasz z ccache, jest szybkość.

I łatwo go użyć , po prostu dodając go jako przedrostek w CC=linii twojego makefile (lub możesz użyć dowiązań symbolicznych, ale sposób makefile jest prawdopodobnie lepszy).

aculich
źródło
1
Początkowo źle zrozumiałem i pomyślałem, że sugerujesz użycie ccache do części generowania, ale teraz rozumiem - twoja sugestia była taka, że ​​po prostu kopiuję wszystkie pliki, a następnie używam ccache w procesie kompilacji, unikając w ten sposób odbudowania tych, które nie zmienił się. To dobry pomysł, ale w moim przypadku nie będzie dobrze - mam setki plików, zwykle zmieniam tylko jeden lub dwa na raz i działam pod Cygwin, gdzie po prostu uruchamiam setki procesów ccache, aby zobaczyć każdy z nich plik zajmie kilka minut. Niemniej jednak głosowano, ponieważ jest to dobra odpowiedź dla większości ludzi!
Brooks Moses
Nie, nie sugerowałem, że skopiujesz wszystkie pliki, raczej możesz po prostu automatycznie wygenerować swoje pliki .c w miejscu (usuń krok kopiowania i napisz bezpośrednio do nich). A potem po prostu użyj ccache. Nie wiem, co masz na myśli, rozpoczynając setki procesów ccache ... to tylko lekkie opakowanie wokół gcc, które jest dość szybkie i przyspieszy odbudowę również innych części twojego projektu. Próbowałeś go użyć? Chciałbym zobaczyć porównanie czasu pomiędzy użyciem metody kopiowania a ccache. W rzeczywistości można połączyć dwie metody, aby uzyskać korzyści z obu.
aculich
1
Racja, rozumiem teraz o kopiowaniu. Wyjaśniając, mam na myśli to: jeśli generuję pliki w miejscu, muszę zadzwonić ccache file.c -o file.olub równowartość kilkaset razy, ponieważ istnieje kilkaset file.cplików. Kiedy robiłem to cmpraczej ccache, zajęło mi to kilka minut - i cmpjest tak lekkie jak ccache. Problem polega na tym, że w Cygwin rozpoczęcie procesu zajmuje niemały czas, nawet w przypadku całkowicie trywialnego procesu.
Brooks Moses
1
Jako punkt danych for f in src/*; do /bin/true.exe; donezajmuje 30 sekund, więc tak. W każdym razie wolę mój edytor oparty na systemie Windows, a oprócz tego rodzaju problemów dotyczących czasu Cygwin działa całkiem dobrze z moim przepływem pracy jako lekkim miejscem do testowania rzeczy lokalnie, jeśli nie przesyłam na serwery kompilacji. Przydatne jest posiadanie mojej powłoki i edytora w tym samym systemie operacyjnym. :)
Brooks Moses
1
Jeśli chcesz korzystać z edytora opartego na systemie Windows, możesz to zrobić dość łatwo za pomocą folderów współdzielonych, jeśli zainstalujesz dodatki dla gości ... ale hej, jeśli Cygwin Ci odpowiada, to kogo mam powiedzieć inaczej? Szkoda, że ​​trzeba skakać przez takie dziwne obręcze ... a kompilacja w ogóle byłaby szybsza również na maszynie wirtualnej.
aculich
3

To powinno zrobić to, czego potrzebujesz

diff -qr ./x ./y | awk '{print $2}' | xargs -n1 -J% cp % ./y/

Gdzie:

  • x to twój zaktualizowany / nowy folder
  • y jest miejscem docelowym, do którego chcesz skopiować
  • awk weźmie drugi argument każdej linii z polecenia diff (być może będziesz potrzebować dodatkowych rzeczy dla nazw plików ze spacją - nie możesz tego teraz wypróbować)
  • xargs -J% wstawi nazwę pliku do cp w odpowiednim miejscu
Patkos Csaba
źródło
1
-1, ponieważ jest to zbyt skomplikowane, nieprzenośne ( -Jjest specyficzne dla bsd; w GNU xargs jest -I) i nie działa poprawnie, jeśli ten sam zestaw plików już nie istnieje w obu lokalizacjach (jeśli touch x/boowtedy grep daje mi Only in ./x: booco powoduje błędy w potoku). Użyj narzędzia zbudowanego do pracy, np rsync --checksum.
aculich
Lub jeszcze lepiej, w tym konkretnym przypadku użyj ccache .
aculich
+1, ponieważ jest to zestaw dobrze znanych poleceń, które mogę złamać, aby użyć do podobnych zadań (przyszedł tutaj, aby wykonać różnicę), ale nadal rsync może być lepszy do tego konkretnego zadania
ntg
3

Lubię używać unison na rzecz, rsyncponieważ obsługuje wielu mistrzów, ponieważ już skonfigurowałem moje klucze ssh i VPN osobno.

Dlatego w moim crontabie tylko jednego hosta pozwalam im synchronizować co 15 minut:

* / 15 * * * * [-z "$ (pidof unison)"] && (timeout 25m unison -sortbysize -ui text -batch -times / home / master ssh: //192.168.1.12//home/master -path dev -logfile /tmp/sync.master.dev.log) &> /tmp/sync.master.dev.log

Wtedy mogę się rozwijać po obu stronach, a zmiany będą się rozprzestrzeniać. W rzeczywistości w przypadku ważnych projektów mam do 4 serwerów dublujących to samo drzewo (3 działają jednocześnie z cron, wskazując na ten, który tego nie robi). W rzeczywistości hosty Linux i Cygwin są mieszane - z wyjątkiem tego, że nie oczekuj wyczucia miękkich linków w win32 poza środowiskiem cygwin.

Jeśli pójdziesz tą drogą, zrób początkowe lustro po pustej stronie bez -batch, tj

unison -ui text  -times /home/master ssh://192.168.1.12//home/master -path dev

Oczywiście istnieje konfiguracja ignorująca pliki kopii zapasowych, archiwa itp .:

 ~/.unison/default.prf :
# Unison preferences file
ignore = Name {,.}*{.sh~}
ignore = Name {,.}*{.rb~}
ignore = Name {,.}*{.bak}
ignore = Name {,.}*{.tmp}
ignore = Name {,.}*{.txt~}
ignore = Name {,.}*{.pl~}
ignore = Name {.unison.}*
ignore = Name {,.}*{.zip}

    # Use this command for displaying diffs
    diff = diff -y -W 79 --suppress-common-lines

    ignore = Name *~
    ignore = Name .*~
    ignore = Path */pilot/backup/Archive_*
    ignore = Name *.o
Marcos
źródło
Patrzyłem na to, ale nie mogłem znaleźć unisonopcji oznaczającej „nie aktualizuj dat ostatniej modyfikacji pliku”. Czy jest jeden W przeciwnym razie jest to świetna odpowiedź na zupełnie inny problem.
Brooks Moses
1
-timesrobi to dla mnie Myślę, że Unison ma również tryb pracy na sucho.
Marcos
Cóż, ustawienie times=false(lub rezygnacja -times) by to zrobiło. Wcześniej nie wiem, jak mi tego brakowało. Dzięki!
Brooks Moses
Miło, że mogłem pomóc. Jestem zagorzałym, jeśli chodzi o zachowanie takich rzeczy, jak modtimes, uprawnienia i miękkie linki. Często pomijane
Marcos
1

Chociaż rsync --checksumjest to poprawna odpowiedź, należy pamiętać, że ta opcja jest niezgodna z --times, i --archiveobejmuje to --times, więc jeśli chcesz rsync -a --checksum, naprawdę musisz rsync -a --no-times --checksum.

Vladimir Kornea
źródło
Co masz na myśli mówiąc „niekompatybilny”?
ov
Co rozumiesz przez „poprawna odpowiedź”?
thoni56