Como posso fazer uma operação "copiar se alterada"?

34

Gostaria de copiar um conjunto de arquivos do diretório A para o diretório B, com a ressalva de que, se um arquivo no diretório A for idêntico a um arquivo no diretório B, esse arquivo não deverá ser copiado (e, portanto, seu tempo de modificação não deverá ser Atualizada). Existe uma maneira de fazer isso com as ferramentas existentes, sem escrever meu próprio script para fazer isso?

Para elaborar um pouco do meu caso de uso: estou gerando automaticamente vários .carquivos em um diretório temporário (por um método que precisa gerar todos eles incondicionalmente) e, quando eu os gerar novamente, gostaria de copiar apenas os que foram alterados para o diretório de origem real, deixando os inalterados intocados (com seus antigos tempos de criação), para que makesaibam que não é necessário recompilá-los. ( .cPorém, nem todos os arquivos gerados são arquivos, portanto, preciso fazer comparações binárias em vez de comparações de texto.)

(Como uma observação: isso surgiu da pergunta que fiz em https://stackoverflow.com/questions/8981552/speeding-up-file-comparions-with-cmp-on-cygwin/8981762#8981762 , onde estava tentando para acelerar o arquivo de script que eu estava usando para fazer essa operação, mas me ocorre que eu realmente deveria perguntar se há uma maneira melhor de fazer isso do que escrever meu próprio script - especialmente porque existe uma maneira simples de fazer isso em um shell O script invocará algo como cmpem cada par de arquivos e iniciar todos esses processos leva muito tempo.)

Brooks Moses
fonte
11
Você pode usar diff -qr dirA dirBpara ver quais arquivos são exclusivos dirAe dirB, respectivamente.
11
@ Brooks-Moses, este é realmente um trabalho adequado para o ccache !
aculich 31/01
3
@esseesse, se você quiser mostrar os arquivos exclusivos, poderá usar o diff, mas se quiser ver exatamente o que mudou, use rsync -avncou o longo caminho rsync --archive --verbose --dry-run --checksum.
aculich 31/01

Respostas:

29

O rsync é provavelmente a melhor ferramenta para isso. Existem muitas opções nesse comando, então leia a página de manual . Eu acho que você quer a opção --checksum ou o --ignore-times

Adam Terrey
fonte
Eu deveria ter notado que eu já tentei isso, sem sucesso. Essas duas opções afetam apenas se o rsync faz uma cópia - mas, mesmo quando não faz uma cópia, atualiza o tempo de modificação do arquivo de destino para o mesmo que a origem (se a -topção for especificada) ou para o tempo de sincronização (se -tnão for especificado).
Brooks Moses
4
@Brooks Moses: Não. Pelo menos a minha versão do rsyncnão. Se eu fizer isso :, mkdir src dest; echo a>src/a; rsync -c src/* dest; sleep 5; touch src/a; rsync -c src/* destentão stat dest/amostra que mtime e ctime são 5 segundos mais antigos que os de src/a.
angus
@angus: Hein. Ok, você está certo. A chave parece ser a --checksumopção e, embora linux.die.net/man/1/rsync não contenha absolutamente nada que implique que isso tenha algum efeito sobre a atualização da data da modificação, ainda assim deixa a data de modificação do destino intocado. (Por outro lado, a --ignore-timesopção não tem esse efeito; com ela a data da modificação ainda é atualizada.) Dado que isso parece ser totalmente indocumentado, posso confiar nisso?
Brooks Moses
2
@BrooksMoses: Eu acho que você pode confiar nele: rsynco fluxo de trabalho é: 1) verifique se o arquivo precisa ser atualizado; 2) se sim, atualize o arquivo. A --checksumopção diz que não deve ser atualizado; portanto rsync, não prossiga para a etapa 2).
enzotib
2
@BrooksMoses: --ignore-timeswithout --checksumcopia todos os arquivos e também atualiza o registro de data e hora, mesmo que os arquivos sejam idênticos.
enzotib
13

Você pode usar o -ucomutador para cp:

$ cp -u [source] [destination]

Na página do manual:

   -u, --update
       copy only when the SOURCE file is newer than the destination file or 
       when the destination file is missing
gu1
fonte
4
Olá e bem-vindo ao site! Esperamos que as respostas sejam um pouco mais substanciais aqui. Por exemplo, você poderia ter incluído uma explicação sobre o que a -ubandeira faz e como funciona e como isso ajudaria o OP. No entanto, nesse caso em particular, isso não ajudaria o OP, pois copiaria arquivos idênticos se eles fossem mais novos e, portanto, alteraria seu registro de data e hora, que é precisamente o que o OP deseja evitar.
terdon
11
De um comentário em um A semelhante que já foi excluído: "Isso não funcionará, pois copia também arquivos idênticos, se o carimbo de data / hora da fonte for mais recente (e, portanto, atualize o carimbo de data / hora do destino, com relação à solicitação do OP)".
slm
Não responde à pergunta, mas ainda a achei útil.
user31389
7

Embora o uso rsync --checksumseja uma boa maneira geral de "copiar se alterado", no seu caso específico, existe uma solução ainda melhor!

Se você deseja evitar a recompilação desnecessária de arquivos, use o ccache, que foi criado exatamente para esse fim! De fato, ele não apenas evitará recompilações desnecessárias dos arquivos gerados automaticamente, mas também acelerará as coisas sempre que você fizer make cleane recompilar do zero.

Em seguida, tenho certeza que você perguntará: "É seguro?" Bem, sim, como o site aponta:

É seguro?

Sim. O aspecto mais importante de um cache do compilador é sempre produzir exatamente a mesma saída que o compilador real produziria. Isso inclui fornecer exatamente os mesmos arquivos de objeto e exatamente os mesmos avisos do compilador que seriam produzidos se você usar o compilador real. A única maneira de você saber que está usando o ccache é a velocidade.

E é fácil usá- lo apenas adicionando-o como um prefixo na CC=linha do seu makefile (ou você pode usar links simbólicos, mas a maneira do makefile provavelmente é melhor).

aculich
fonte
11
Inicialmente eu entendi errado e pensei que você estava sugerindo que eu usasse o ccache para fazer parte da geração, mas agora eu entendo - sua sugestão era que eu simplesmente copiasse todos os arquivos e depois usasse o ccache no processo de compilação, evitando assim reconstruir os que não tinha mudado. É uma boa ideia, mas não vai funcionar bem no meu caso - eu tenho centenas de arquivos, geralmente apenas altero um ou dois por vez, e estou executando o Cygwin, onde basta iniciar as centenas de processos ccache para examinar cada arquivo levaria alguns minutos. No entanto, votado porque é uma boa resposta para a maioria das pessoas!
Brooks Moses
Não, eu não estava sugerindo que você copiasse todos os arquivos. Em vez disso, basta gerar automaticamente seus arquivos .c no local (remova a etapa de cópia e grave diretamente neles). E então apenas use ccache. Não sei o que você quer dizer com iniciar centenas de processos ccache ... é apenas um invólucro leve em torno do gcc que é bastante rápido e agiliza a reconstrução de outras partes do seu projeto. Você já tentou usá-lo? Gostaria de ver uma comparação do tempo entre o uso do método de cópia e o ccache. De fato, você pode combinar os dois métodos para obter os benefícios de ambos.
aculich
11
Certo, entendo agora sobre a cópia. Para esclarecer, o que quero dizer é o seguinte: se eu gerar os arquivos no local, tenho que ligar ccache file.c -o file.oou equivalente, várias centenas de vezes, porque existem várias centenas de file.carquivos. Quando eu estava fazendo isso com cmp, em vez de ccache, levou vários minutos - e cmpé tão leve quanto ccache. O problema é que, no Cygwin, iniciar um processo leva um tempo não desprezível, mesmo para um processo completamente trivial.
Brooks Moses
11
Como um ponto de dados, for f in src/*; do /bin/true.exe; doneleva 30 segundos, então sim. Enfim, prefiro meu editor baseado no Windows e, além desse tipo de problema de tempo, o Cygwin funciona muito bem com meu fluxo de trabalho como o local mais leve para testar as coisas localmente, se não estiver carregando nos servidores de compilação. É útil ter meu shell e meu editor no mesmo sistema operacional. :)
Brooks Moses
11
Se você quiser usar o editor baseado no Windows, poderá fazê-lo facilmente com as Pastas Compartilhadas se instalar o Guest Additions ... mas ei, se o Cygwin combina com você, quem sou eu para dizer algo diferente? Parece uma pena ter que pular por aros estranhos como este ... e a compilação em geral também seria mais rápida em uma VM.
Aculich #
3

Isso deve fazer o que você precisa

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

Onde:

  • x é sua pasta atualizada / nova
  • y é o destino para o qual você deseja copiar
  • O awk usará o segundo argumento de cada linha no comando diff (talvez você precise de algumas coisas extras para nomes de arquivos com espaço - não pode tentar agora)
  • xargs -J% inserirá o nome do arquivo no cp no local apropriado
Patkos Csaba
fonte
11
-1 porque isso é muito complicado, não portátil ( -Jé específico para bsd; com GNU xargs é -I) e não funciona corretamente se o mesmo conjunto de arquivos já não existir nos dois locais (se eu touch x/booentão grep me fornecer Only in ./x: booque causa erros no pipeline). Use uma ferramenta criada para o trabalho, como rsync --checksum.
Aculich
Ou melhor ainda, para este caso específico, use ccache .
aculich 31/01
+1 porque é um conjunto de comandos conhecidos que posso interromper para usar em tarefas semelhantes (vim aqui para fazer um diff), mas o rsync ainda pode ser melhor para essa tarefa em particular
ntg
3

Eu gosto de usar o uníssono a favor rsyncporque suporta múltiplos mestres, já tendo configurado minhas chaves ssh e vpn separadamente.

Portanto, no crontab de apenas um host, eu os deixo sincronizar a cada 15 minutos:

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

Então eu posso estar desenvolvendo dos dois lados e as mudanças serão propagadas. De fato, para projetos importantes, tenho até 4 servidores espelhando a mesma árvore (3 são executados em uníssono no cron, apontando para o que não é). De fato, os hosts Linux e Cygwin são mistos - exceto que não esperam senso de links suaves no win32 fora do ambiente cygwin.

Se você seguir esta rota, faça o espelho inicial do lado vazio sem o -batch, ou seja,

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

Claro que há uma configuração para ignorar arquivos de backup, arquivos, etc .:

 ~/.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
fonte
Eu olhei para isso, mas não consegui encontrar uma unisonopção que significa "não atualizar datas da última modificação do arquivo". Existe um? Caso contrário, essa é uma ótima resposta para um problema completamente diferente.
Brooks Moses
11
-timesfaz isso por mim. O Unison também tem um modo de funcionamento a seco, eu acho.
Marcos
Bem, definir times=false(ou deixar de fora -times) faria isso. Não sei como perdi isso na documentação antes. Obrigado!
Brooks Moses
Feliz em ajudar. Eu sou um defensor quando se trata de preservar coisas como horários, permissões e links flexíveis. Muitas vezes esquecido
Marcos
1

Embora rsync --checksumseja a resposta correta, observe que esta opção é incompatível com --times, e isso --archiveinclui --times, portanto, se você quiser rsync -a --checksum, realmente precisará rsync -a --no-times --checksum.

Vladimir Kornea
fonte
O que você quer dizer com 'incompatível'?
ov
O que você quer dizer com "é a resposta correta"?
thoni56 3/02