Come posso eseguire un'operazione di "copia se modificata"?


34

Vorrei copiare un set di file dalla directory A alla directory B, con l'avvertenza che se un file nella directory A è identico a un file nella directory B, quel file non dovrebbe essere copiato (e quindi il suo tempo di modifica non dovrebbe essere aggiornato). C'è un modo per farlo con gli strumenti esistenti, senza scrivere il mio script per farlo?

Per elaborare un po 'il mio caso d'uso: sto generando automaticamente un mucchio di .cfile in una directory temporanea (con un metodo che deve generarli tutti incondizionatamente), e quando li rigenero, vorrei solo copiare quelli che sono cambiati nella directory sorgente attuale, lasciando invariati quelli invariati (con i loro vecchi tempi di creazione) in modo che makesappiano che non è necessario ricompilarli. ( .cTuttavia, non tutti i file generati sono file, quindi ho bisogno di fare confronti binari piuttosto che confronti di testo.)

(Come nota: questo è nato dalla domanda che ho posto su https://stackoverflow.com/questions/8981552/speeding-up-file-comparions-with-cmp-on-cygwin/8981762#8981762 , dove stavo provando per velocizzare il file di script che stavo usando per fare questa operazione, ma mi viene in mente che dovrei davvero chiedere se c'è un modo migliore per farlo che scrivere il mio script - specialmente da qualsiasi modo semplice di farlo in una shell lo script invocherà qualcosa di simile cmpsu ogni coppia di file e l'avvio di tutti quei processi richiede troppo tempo.)


1
Puoi usare diff -qr dirA dirBper vedere quali file sono unici dirAe dirBripetutamente.

1
@ brooks-moses questo è davvero un lavoro adatto per ccache !
aculich,

3
@hesse se vuoi mostrare i file univoci puoi usare diff, ma se vuoi vedere esattamente cosa è cambiato, usa rsync -avnco la strada lunga rsync --archive --verbose --dry-run --checksum.
aculich,

Risposte:


29

rsync è probabilmente lo strumento migliore per questo. Ci sono molte opzioni su questo comando, quindi leggi la pagina man . Penso che tu voglia l'opzione --checksum o --ignore-times


Avrei dovuto notare che ci avevo già provato, senza successo. Entrambe queste opzioni influenzano solo se rsync esegue una copia, ma, anche quando non esegue una copia, aggiorna il tempo di modifica del file di destinazione allo stesso dell'origine (se l' -topzione è specificata) o al tempo di sincronizzazione (se -tnon specificato).
Brooks Moses,

4
@Brooks Moses: No. Almeno la mia versione di rsyncnon lo fa. Se lo faccio :, mkdir src dest; echo a>src/a; rsync -c src/* dest; sleep 5; touch src/a; rsync -c src/* destquindi stat dest/amostra i suoi mtime e ctime sono 5 secondi più vecchi di quelli di src/a.
angus,

@angus: Huh. Ok, hai ragione. La chiave sembra essere l' --checksumopzione, e sebbene linux.die.net/man/1/rsync non contenga assolutamente nulla che implichi che ciò influisca sul fatto che la data di modifica sia aggiornata, fa comunque in modo che rimanga la data di modifica della destinazione intatto. (D'altra parte, l' --ignore-timesopzione non ha questo effetto; con essa la data di modifica è ancora aggiornata.) Dato che questo sembra essere completamente privo di documenti, tuttavia, posso fare affidamento su di esso?
Brooks Moses,

2
@BrooksMoses: Penso che puoi fare affidamento su di esso: rsyncil flusso di lavoro è: 1) controlla se il file deve essere aggiornato; 2) in tal caso, aggiorna il file. L' --checksumopzione dice che non dovrebbe essere aggiornato, quindi rsyncnon dovrebbe procedere al passaggio 2).
enzotib,

2
@BrooksMoses: --ignore-timessenza --checksumcopiare tutti i file, e quindi anche aggiornare il timestamp, anche se i file sono identici.
enzotib,

13

Puoi usare l' -uinterruttore per fare cpcosì:

$ cp -u [source] [destination]

Dalla pagina man:

   -u, --update
       copy only when the SOURCE file is newer than the destination file or 
       when the destination file is missing

4
Ciao e benvenuto nel sito. Ci aspettiamo che le risposte siano un po 'più sostanziali qui. Ad esempio, avresti potuto includere una spiegazione di cosa fa la -ubandiera e di come funziona e di come ciò aiuterebbe l'OP. Tuttavia, in questo caso particolare, non aiuterebbe l'OP poiché copia i file identici se fossero più recenti e quindi cambierebbe i loro timestamp, che è esattamente ciò che l'OP vuole evitare.
terdon

1
Da un commento su una A simile che è già stato eliminato: "Questo non funzionerà poiché copierebbe anche file identici, se il timestamp di origine è più recente (e quindi aggiorna il timestamp di destinazione, rispetto alla richiesta OP)."
slm

Non risponde affatto alla domanda, ma l'ho ancora trovato utile.
user31389,

7

Mentre l'utilizzo rsync --checksumè un buon modo generale per "copiare se modificato", nel tuo caso particolare c'è una soluzione ancora migliore!

Se vuoi evitare di ricompilare inutilmente i file, dovresti usare ccache che è stato creato proprio per questo scopo! In effetti, non solo eviterà ricompilazioni inutili dei file generati automaticamente, ma accelererà anche le cose ogni volta che lo fai make cleane ricompilerai da zero.

Quindi sono sicuro che chiederai "È sicuro?" Bene, sì, come sottolinea il sito Web:

È sicuro?

Sì. L'aspetto più importante di una cache del compilatore è produrre sempre esattamente lo stesso output prodotto dal compilatore reale. Ciò include fornire esattamente gli stessi file oggetto e gli stessi avvisi del compilatore che verrebbero prodotti se si utilizza il compilatore reale. L'unico modo in cui dovresti essere in grado di dire che stai usando ccache è la velocità.

Ed è facile usarlo semplicemente aggiungendolo come prefisso nella CC=riga del tuo makefile (oppure puoi usare i symlink, ma il modo di makefile è probabilmente migliore).


1
Inizialmente ho frainteso e pensato che stavi suggerendo di usare ccache per fare parte della generazione, ma ora capisco - il tuo suggerimento è stato quello di copiare semplicemente tutti i file e quindi usare ccache nel processo di compilazione, evitando così di ricostruire quelli che non era cambiato. È una buona idea, ma nel mio caso non andrà bene: ho centinaia di file, di solito ne cambio uno o due alla volta e sto eseguendo Cygwin dove semplicemente avviando le centinaia di processi ccache per guardare il file richiederebbe alcuni minuti. Tuttavia, votato perché è una buona risposta per la maggior parte delle persone!
Brooks Moses,

No, non stavo suggerendo di copiare tutti i file, piuttosto puoi semplicemente generare automaticamente i tuoi file .c sul posto (rimuovi il passaggio di copia e scrivi direttamente a loro). E poi usa solo ccache. Non so cosa intendi avviando centinaia di processi di ccache ... è solo un involucro leggero attorno a gcc che è abbastanza veloce e accelererà la ricostruzione anche di altre parti del tuo progetto. Hai provato ad usarlo? Vorrei vedere un confronto tra i tempi tra l'utilizzo del metodo di copia e ccache. In effetti, potresti combinare i due metodi per ottenere i vantaggi di entrambi.
aculich,

1
Bene, ok, ora capisco della copia. Per chiarire, ciò che intendo è questo: se generi i file sul posto, devo quindi chiamare ccache file.c -o file.oo l'equivalente, diverse centinaia di volte perché ci sono diverse centinaia di file.cfile. Quando lo facevo con cmp, piuttosto che ccache, ci sono voluti diversi minuti - ed cmpè leggero come ccache. Il problema è che, su Cygwin, l' avvio di un processo richiede un tempo non trascurabile, anche per un processo completamente banale.
Brooks Moses,

1
Come punto dati, for f in src/*; do /bin/true.exe; doneimpiega 30 secondi, quindi sì. In ogni caso, preferisco il mio editor basato su Windows e, a parte questo tipo di problema di temporizzazione, Cygwin funziona abbastanza bene con il mio flusso di lavoro come luogo leggero per testare le cose localmente se non sto caricando sui server di compilazione. È utile avere la mia shell e il mio editor nello stesso sistema operativo. :)
Brooks Moses,

1
Se vuoi usare il tuo editor basato su Windows puoi farlo abbastanza facilmente con Cartelle condivise se installi Guest Additions ... ma hey, se Cygwin fa al caso tuo, allora chi sono io per dire qualcosa di diverso? Sembra un peccato dover saltare attraverso strani cerchi come questo ... e la compilazione in generale sarebbe più veloce anche in una VM.
aculich,

3

Questo dovrebbe fare quello che ti serve

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

Dove:

  • x è la tua cartella aggiornata / nuova
  • y è la destinazione in cui si desidera copiare
  • awk prenderà il secondo argomento di ogni riga dal comando diff (forse avrai bisogno di cose extra per i nomi di file con spazio - non puoi provarlo ora)
  • xargs -J% inserirà il nome del file in cp nella posizione corretta

1
-1 perché questo è eccessivamente complicato, non portatile ( -Jè specifico per bsd; con GNU xargs lo è -I), e non funziona correttamente se lo stesso set di file non esiste già in entrambe le posizioni (se touch x/boopoi grep mi dà Only in ./x: booche causa errori nella pipeline). Utilizzare uno strumento creato per il lavoro, ad esempio rsync --checksum.
aculich,

O meglio ancora, per questo caso specifico usare ccache .
aculich,

+1 perché è un insieme di comandi ben noti che posso interrompere per usare su compiti simili (è venuto qui per fare un diff), tuttavia rsync potrebbe essere migliore per questo particolare compito
NT

3

Mi piace usare l' unisono a favore rsyncperché supporta più master, avendo già impostato i miei tasti ssh e vpn separatamente.

Quindi nel mio crontab di un solo host li lascio sincronizzare ogni 15 minuti:

* / 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

Quindi potrò svilupparmi su entrambi i lati e le modifiche si propagheranno. In effetti per progetti importanti ho fino a 4 server che rispecchiano lo stesso albero (3 eseguono unisono da cron, indicando quello che non lo fa). In effetti, gli host Linux e Cygwin sono misti, tranne che non aspettatevi il senso dai soft link in win32 al di fuori dell'ambiente cygwin.

Se segui questa strada, crea il mirror iniziale sul lato vuoto senza -batch, ad es

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

Naturalmente esiste una configurazione per ignorare file di backup, archivi, ecc .:

 ~/.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

L'ho guardato, ma non sono riuscito a trovare unisonun'opzione che significa "non aggiornare le date dell'ultima modifica del file". Ce n'è uno? Altrimenti, questa è un'ottima risposta a un problema completamente diverso.
Brooks Moses,

1
-timeslo fa per me. Anche Unison ha una modalità di funzionamento a secco, penso io.
Marcos,

Bene, l'impostazione times=false(o l'interruzione -times) lo farebbe. Non so come l'ho perso nella documentazione prima. Grazie!
Brooks Moses,

Felice di aiutare. Sono un pignolo quando si tratta di preservare cose come orari, permessi e collegamenti soft. Spesso trascurato
Marcos

1

Mentre rsync --checksumè la risposta corretta, nota che questa opzione è incompatibile con --timese che --archiveinclude --times, quindi se vuoi rsync -a --checksum, devi davvero farlo rsync -a --no-times --checksum.


Cosa intendi dicendo "incompatibile"?
OV

Cosa intendi con "è la risposta corretta"?
thoni56
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.