Как я могу сделать операцию «копировать, если изменено»?

34

Я хотел бы скопировать набор файлов из каталога A в каталог B с оговоркой, что если файл в каталоге A идентичен файлу в каталоге B, этот файл не должен копироваться (и, следовательно, время его модификации не должно быть обновлено). Есть ли способ сделать это с помощью существующих инструментов, без написания собственного сценария?

Чтобы немного рассказать о моем сценарии использования: я автоматически .cгенерирую группу файлов во временном каталоге (методом, который должен генерировать все из них безоговорочно), и когда я их заново генерирую, я хотел бы только скопировать те, которые превратились в фактический исходный каталог, оставляя неизменными (с их старым временем создания), чтобы makeзнать, что не нужно их перекомпилировать. (Однако не все сгенерированные файлы являются .cфайлами, поэтому мне нужно делать двоичные сравнения, а не текстовые сравнения.)

(Примечание: это выросло из вопроса, который я задал на https://stackoverflow.com/questions/8981552/speeding-up-file-comparions-with-cmp-on-cygwin/8981762#8981762 , где я пытался чтобы ускорить файл сценария, который я использовал для выполнения этой операции, но мне пришло в голову, что я действительно должен спросить, есть ли лучший способ сделать это, чем написание собственного сценария - тем более, что любой простой способ сделать это в оболочке скрипт будет вызывать что-то вроде cmpкаждой пары файлов, и запуск всех этих процессов занимает слишком много времени.)

Брукс Моисей
источник
1
Вы можете использовать, diff -qr dirA dirBчтобы увидеть, какие файлы являются уникальными dirAи dirB, соответственно.
1
@ brooks-moses - это действительно работа, подходящая для ccache !
aculich
3
@hesse, если вы хотите показать уникальные файлы, которые вы можете использовать diff, но если вы хотите увидеть только то, что изменилось, используйте rsync -avncили длинный путь rsync --archive --verbose --dry-run --checksum.
aculich

Ответы:

29

rsync, вероятно, лучший инструмент для этого. У этой команды много опций, поэтому прочитайте man-страницу . Я думаю, что вы хотите параметр --checksum или --ignore-times

Адам Террей
источник
Я должен был отметить, что я уже попробовал это, но безуспешно. Обе эти опции влияют только на то, выполняет ли rsync копирование, но даже если он не выполняет копирование, он либо обновляет время модификации целевого файла в соответствии с исходным (если -tуказана опция), либо со временем синхронизации (если -tне указано).
Брукс Моисей
4
@ Брукс Моисей: Это не так. По крайней мере, моя версия этого rsyncне делает. Если я делаю это:, mkdir src dest; echo a>src/a; rsync -c src/* dest; sleep 5; touch src/a; rsync -c src/* destто stat dest/aпоказывает, что его mtime и ctime на 5 секунд старше, чем у src/a.
ангус
@angus: Да. Хорошо, ты прав. Кажется, что ключом является --checksumопция, и хотя linux.die.net/man/1/rsync не содержит абсолютно ничего, что могло бы повлиять на то, обновляется ли дата модификации, тем не менее, это приводит к тому, что конечная дата модификации будет оставлена нетронутым. (С другой стороны, --ignore-timesопция не имеет такого эффекта; с ней дата модификации все еще обновляется.) Однако, учитывая, что это кажется совершенно недокументированным, могу ли я на это положиться?
Брукс Моисей
2
@BrooksMoses: я думаю, что вы можете положиться на него: rsyncрабочий процесс: 1) проверить, нужно ли обновлять файл; 2) если так, обновите файл. --checksumВариант сказать , что это не должно обновляться, поэтому rsyncне следует перейти к шагу 2).
энзотиб
2
@BrooksMoses: --ignore-timesбез --checksumкопирует каждый файл, а также обновляет метку времени, даже если файлы идентичны.
энзотиб
13

Вы можете использовать -uпереключатель, чтобы cpпонравиться так:

$ cp -u [source] [destination]

Со страницы руководства:

   -u, --update
       copy only when the SOURCE file is newer than the destination file or 
       when the destination file is missing
GU1
источник
4
Привет и добро пожаловать на сайт. Мы ожидаем, что ответы будут немного более существенными здесь. Например, вы могли бы включить объяснение того, что -uделает флаг и как он работает, и как это могло бы помочь ОП. Однако в данном конкретном случае это не помогло бы OP, поскольку оно скопировало бы идентичные файлы, если бы они были более новыми, и поэтому изменило бы свои метки времени, чего именно и хочет избежать OP.
Terdon
1
Из комментария к аналогичному A, который уже был удален: «Это не будет работать, поскольку он будет копировать также идентичные файлы, если временная метка источника новее (и, таким образом, обновлять временную метку назначения по сравнению с запросом OP)».
SLM
Совсем не отвечает на вопрос, но я все же нашел его полезным.
user31389
7

В то время как использование rsync --checksumявляется хорошим общим способом «копировать, если изменено», в вашем конкретном случае есть еще лучшее решение!

Если вы хотите избежать ненужной перекомпиляции файлов, вы должны использовать ccache, который был создан именно для этой цели! Фактически, это не только позволит избежать ненужных перекомпиляций ваших автоматически сгенерированных файлов, но также ускорит процесс, когда вы это сделаете, make cleanи перекомпилируете с нуля.

Затем я уверен, что вы спросите: "Это безопасно?" Ну да, как указывает сайт:

Это безопасно?

Да. Самый важный аспект кэша компилятора - всегда выводить один и тот же вывод, что и настоящий компилятор. Это включает в себя предоставление точно таких же объектных файлов и точно таких же предупреждений компилятора, которые будут выдаваться, если вы используете настоящий компилятор. Единственный способ узнать, что вы используете ccache - это скорость.

И его легко использовать , просто добавив его в качестве префикса в CC=строке вашего make-файла (или вы можете использовать символические ссылки, но способ make-файла, вероятно, лучше).

aculich
источник
1
Сначала я неправильно понял и подумал, что вы предлагаете мне использовать ccache для выполнения части генерации, но теперь я понимаю - ваше предложение состояло в том, чтобы я просто скопировал все файлы, а затем использовал ccache в процессе сборки, избегая, таким образом, восстановления тех, которые не изменился. Это хорошая идея, но в моем случае это не очень хорошо - у меня есть сотни файлов, обычно меняются только один или два за раз, и я работаю в Cygwin, где просто запускаю сотни процессов ccache для просмотра каждого файл займет несколько минут. Тем не менее, проголосовали, потому что это хороший ответ для большинства людей!
Брукс Моисей
Нет, я не предлагал вам копировать все файлы, скорее вы можете просто сгенерировать ваши файлы .c на месте (удалите шаг копирования и напишите в них напрямую). А потом просто используйте ccache. Я не знаю, что вы имеете в виду, когда запускаете сотни процессов ccache ... это всего лишь легкая оболочка вокруг gcc, которая работает довольно быстро и ускоряет перестройку других частей вашего проекта. Вы пробовали использовать это? Я хотел бы видеть сравнение времени между использованием вашего метода копирования против ccache. Фактически, вы можете объединить два метода, чтобы получить преимущества обоих.
aculich
1
Хорошо, хорошо, теперь я понимаю о копировании. Чтобы уточнить, я имею в виду следующее: если я сгенерирую файлы на месте, я должен затем позвонить ccache file.c -o file.oили эквивалентный, несколько сотен раз, потому что есть несколько сотен file.cфайлов. Когда я делал это с cmp, а не ccache, это заняло несколько минут - и cmpтак же легко, как ccache. Проблема в том, что на Cygwin запуск процесса занимает немалое время, даже для совершенно тривиального процесса.
Брукс Моисей
1
Как дата назначения, for f in src/*; do /bin/true.exe; doneзанимает 30 секунд, так что да. В любом случае, я предпочитаю свой редактор на базе Windows, и помимо такого рода проблем с синхронизацией Cygwin довольно хорошо работает с моим рабочим процессом как легкое место для локального тестирования, если я не загружаю их на серверы сборки. Полезно, чтобы моя оболочка и мой редактор были в одной ОС. :)
Брукс Моисей
1
Если вы хотите использовать свой редактор на базе Windows, вы можете сделать это довольно просто с помощью общих папок, если вы устанавливаете гостевые дополнения ... но, эй, если вам подходит Cygwin, то кто я такой, чтобы сказать что-то другое? Просто стыдно прыгать через странные обручи, подобные этой ... и компиляция в целом также будет быстрее в виртуальной машине.
aculich
3

Это должно делать то, что вам нужно

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

Где:

  • х ваша обновленная / новая папка
  • y - место назначения, в которое вы хотите скопировать
  • awk будет принимать второй аргумент каждой строки из команды diff (может быть, вам понадобятся некоторые дополнительные вещи для имен файлов с пробелом - не можете попробовать сейчас)
  • xargs -J% вставит имя файла в cp в нужном месте
Паткос Чаба
источник
1
-1, потому что это слишком сложно, -Jнепереносимо ( специфично для bsd; с GNU xargs это так -I) и не работает правильно, если один и тот же набор файлов уже не существует в обоих местах (если я touch x/booтогда grep дает мне Only in ./x: booчто вызывает ошибки в конвейере). Используйте инструмент, созданный для работы, как rsync --checksum.
aculich
Или еще лучше, для этого конкретного случая используйте ccache .
aculich
+1, потому что это набор хорошо известных команд, которые я могу использовать для выполнения аналогичных задач (пришел сюда для выполнения
различий),
3

Мне нравится использовать унисон в пользу, rsyncпотому что он поддерживает несколько мастеров, уже настроив мои ключи ssh и vpn отдельно.

Так что в моем crontab только одного хоста я позволяю им синхронизироваться каждые 15 минут:

* / 15 * * * * [-z "$ (pidof unison)"] && (время ожидания 25м в унисон -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

Тогда я могу развиваться с любой стороны, и изменения будут распространяться. Фактически для важных проектов у меня есть до 4 серверов, отражающих одно и то же дерево (3 запускают унисон из cron, указывая на тот, который не работает). На самом деле, Linux и Cygwin хосты смешаны - за исключением того, что не ожидают смысла в мягких ссылках в win32 вне среды cygwin.

Если вы идете по этому пути, сделайте начальное зеркало на пустой стороне без -batch, т.е.

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

Конечно, есть конфигурация для игнорирования файлов резервных копий, архивов и т.д .:

 ~/.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
источник
Я посмотрел на это, но не смог найти unisonопцию, которая означает «не обновлять даты последнего изменения файла». Есть один? В противном случае, это отличный ответ на совершенно другую проблему.
Брукс Моисей
1
-timesделает это для меня. У меня в Unison тоже есть пробный режим, мне кажется.
Маркос
Ну, настройка times=false(или отключение -times) сделает это. Я не знаю, как я пропустил это в документации раньше. Благодарность!
Брукс Моисей
Рад помочь. Я сторонник, когда дело доходит до сохранения таких вещей, как моды, разрешения и программные ссылки. Часто упускается из виду
Маркос
1

Хотя rsync --checksumэто правильный ответ, обратите внимание, что эта опция несовместима --timesи --archiveвключает в себя --times, так что если вы хотите rsync -a --checksum, вам действительно нужно rsync -a --no-times --checksum.

Владимир Корнеа
источник
Что вы имеете в виду, говоря «несовместимый»?
OV
Что вы подразумеваете под «правильный ответ»?
thoni56