Скрипт для рекурсивного сравнения директорий

    В этом посте хочу поделиться с вами простым, но весьма полезным скриптом на питоне, который я написал для сравнения директорий тестового и рабочего проектов Django.


    Проблема


    На работе для решения внутренних задач я использую Django. Как результат — написан софт, с которым через браузер работает большая часть сотрудников — тех. поддержка, операторы, техническая служба нашей компании (сфера деятельности — телеком).

    Довольно часто необходимо вносить изменения, но делать это сразу в рабочем проекте нельзя, т.к. простои вследствие возможных ошибок очень нежелательны. Поэтому была создана копия проекта, в который сначала и вносятся все изменения, а после тестирования измененные файлы копируются в рабочий проект.

    Проблема состоит в том, что трудно вспомнить, какие файлы были изменены или добавлены, а записывать каждый раз в блокнот, как я раньше делал, неэффективно.

    Решение


    Было решено написать скрипт, который сравнивает директории тестового и рабочего проектов и выводит на экран консоли список измененных или добавленных файлов. Например, у нас тестовая директория называется myproject, а рабочий проект находится в директории intranet. Запускаем наш скрипт и видим на экране:

    /var/django_projects/myproject$ ./cmp.py

    [*] /var/django_projects/myproject/templates/base.html
    [-] /var/django_projects/myproject/templates/calls/call_add.html
    [*] /var/django_projects/myproject/templates/calls/call_edit.html
    [*] /var/django_projects/myproject/site_media/main.css


    Измененные файлы — [*], новые (которых нет в рабочем проекте) — [-]. Очень удобно :)

    Исходный код скрипта cmp.py

    1. #!/usr/bin/env python
    2. #------------------------------------------------------------
    3. # Compare directories 'myproject' and 'intranet' recursively
    4. #------------------------------------------------------------
    5.  
    6. import os, filecmp
    7.  
    8. # путь к тестовому проекту - с этой директории будем начинать обход
    9. dir_src = '/var/django_projects/myproject'
    10.  
    11. # будем сравнивать только файлы и директории из этого списка
    12. check_list = (
    13.   '/var/django_projects/myproject/apps/',
    14.   '/var/django_projects/myproject/templates/',
    15.   '/var/django_projects/myproject/scripts/',
    16.   '/var/django_projects/myproject/site_media/main.css',
    17. )
    18.  
    19. for root, dirs, files in os.walk(dir_src):
    20.   for name in files:
    21.     f_src = os.path.join(root, name)
    22.     need_check = f_src.startswith(check_list)
    23.     if need_check and not f_src.endswith('.pyc'):
    24.       f_dst = f_src.replace("myproject", "intranet")
    25.       if not os.path.exists(f_dst):
    26.         print "[-] ", f_src
    27.       elif not filecmp.cmp(f_src, f_dst):
    28.         print "[*] ", f_src


    Как видно из кода, основную работу делает метод os.walk, а для проверки на существование и сравнения файлов используются соответственно os.path.exists и filecmp.cmp.

    Вот и все, надеюсь, кому-нибудь пригодится :)

    UPD: Прежде чем написать очередной комментарий о системах контроля версий, обратите, пожалуйста, внимание на название блога :) Тем не менее, спасибо всем тем, кто уже затронул эту тему, думаю, для полноты картины будет полезно.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 27
    • +11
      Система контроля/управления версиями вам не подходит?
      • –3
        Задача заключается не столько в контроле версий, сколько просто в обеспечении работоспособности кода в рабочем проекте. В команде кроме меня еще один человек, и каждый в основном редактирует только свой код (разные приложения). На всякий случай предусмотрен ежедневный бэкап, но пока он, к счастью, ни разу не пригодился :)
        • +5
          Контроль версий это не просто контролирование версий. Это и тот самый бекап, потому что если вы что то в своём коде сломаете, то всегда можно откатится. Это и возможность посмотреть — а почему оно три дня назад работало, а сейчас нет. Причем легким движением руки вы именно увидите различия. А уж про синхронизацию наработок с напарником я вообще молчу.

          Вот только svn уже не мейнстрим, сейчас в моде распределенные системы — git, mercurial, bazaar и тп. Обязательно прочтите про них, прямо сейчас — сэкономите много времени и сил на ненужные движения.

          А скрипт это всегда хорошо — развивает мышление, хоть и велосипед. Но где то я читал, что велосипеды обязательно надо писать — начинаешь понимать, в чем собственно сложность прикрутить вот ту фичу, которая уже год в багзиле висит.
          • 0
            Имел дело только с svn, но тоже много хорошего слышал про git, mercurial и bazaar. Обязательно почитаю. Хотя это немного выходит за рамки темы поста, может напишите, какой из этих систем вы отдаете предпочтение?
            • +1
              mercurial — для меня проще использовать, freehg.org
              git несколько сложнее, но мне нравится пользоваться git-hub
      • +1
        Нужно только смотреть измененные/добавленные файлы или обновлять сервет тоже нужно?
        Не проще ли для этих нужд использовать тот же SVN — в режиме Dry Run при выполнении Merge он показывает ту же инфу что и ваш скрипт.
        Если же нужно просто обновлять, то стандартный rsync -av [src] [dest] вполне пригоден.
        • 0
          Не уверен, что проще, но спасибо за полезную информацию.
        • +5
          $ diff -r intranet myproject| grep ^Only
          $ diff -r intranet myproject| grep ^diff

          не то же самое делает?

          • 0
            Век живи — век учись :)
            Правда в скрипте еще есть список файлов/директорий, по котором производить сравнение + вывод более читабельный. Конечно, можно реализовать то же самое на shell, но это дело вкуса :)
          • 0
            Я использую Araxis Merge для сравнения каталогов и файлов.
            • 0
              В этом случае лучше свой велосипед — есть гораздо круче вещи за так.
            • +4
              скарипт написан неаккуратно.

              1. f_src.find('.pyc') -почему find а не enswith
              2. почему неьзя было написать вместо
              for item in check_list:
                    if f_src.find(item) != -1:
                      need_check = True
                      break;
              


              просто f_str.startswith(check_list)

              3. Зачем вообще обходить все дерево, если нам нужны только несколько папок?

              сожно написать что-то типа:
              for root, dirs, files in sum(map(os.walk, check_list), [])
              или просто добавить еще один for

              4. f_src.replace(«myproject», «intranet») — что будет делать при наличии файла myproject.py
              • 0
                1, 2 — исправил, 3,4 — чуть позже проверю.
                Спасибо за отличный комментарий! Все-таки блог называется «Язык программирования Python» :)
                • +1
                  теперь == -1 — лишнее

                  я бы еще подумал как присопособить filecmp.dircmp для ваших нужд например можно взять за основу
                      def report_full_closure(self): # Report on self and subdirs recursively
                          self.report()
                          for sd in self.subdirs.values():
                              print()
                              sd.report_full_closure()
                  


                  из C:\Python30\Lib\filecmp.py
                  • 0
                    4:
                    f_src.replace("myproject", "intranet", 1)
                    для замены 1 раз.
                • 0
                  Если в копии проекта будет удален файл, Ваш скрипт об этом не узнает.
                  • 0
                    Предвижу следующую статью: «Рекурсивное перенесение изменений на рабочий хост» :)
                    Потратье лучше немного времени и изучите SVN — крайне полезная вещь даже если работаете в одиночку.
                    • 0
                      И почему большая часть комментариев про системы контроля версий? :) Поймите меня правильно, не имею ничего против SVN, тем более, что доводилось работать с этой системой. Но ведь не случайно данный пост помещен в блог о программировании на Python.

                      Мне показалось интересным, что данную задачу можно решить так просто и красиво, вот и все. Воспринимайте это как небольшую демонстрацию, которая может быть интересной тем, кто изучает Python. В последнее время все чаще стали появляться статьи об этом замечательном языке. Это радует, но хотелось бы видеть больше практических примеров, в которых решаются какие-нибудь пусть несложные, но реальные задачи, поэтому я и выбрал такую тему для статьи.
                      • +1
                        Я не против вашего решения. Вообще решений может быть масса. Все зависит от того, что именно вы решаете: практическую задачу контроля файлов или учебно-теоретическую по работе с файлами в питон.

                        Потому что на практике возникнет масса нюансов — например, как отслеживать переименование/удаление файла? Иногда помимо обнаружения изменений, надо посмотреть историю, откатить на несколько шагов. Как быть когда откат должен совершиться сразу в двух файлах? Например, вы изменили библиотеку и изменили функцию, которая ее использует. Надо проверить все файлы где она есть, иначе «сломается». То есть не все проблемы практики можно решить простым скриптом.

                        А в качестве демонстрации работы с питоном — отличная задача!
                        • 0
                          Откат изменений, сделанных в нескольких файлах, это действительно удобная штука, согласен с вами. Странно, но теперь я и сам чудесным образом начинаю верить, что мне необходима система контроля версий — осталось только определиться с выбором. А пока выбираю, этот скрипт еще послужит, все-таки люблю я простые решения, от которых пользы много, а затрат почти никаких :)
                    • 0
                      Если использовать filecmp, то задачу можно решить проще, используя filecmp.dircmp. Он за вас всю работу сделает, вам понадобится только отфильтровать (что бы не показывать .pyc) left_only или right_only и diff_files.
                      • 0
                        Похоже dircmp мощный инструмент, но мне кажется, что он не такой гибкий и вывод формировать не очень удобно.
                      • 0
                        a) unison.
                        b) bzr.
                        c) waf/scons

                        любой из этих инструментов позволяет сделать все куда грамотнее. А вообще по идее на «боевом» сервере никогда в жизни не надо менять код. Поэтому намного эффективнее сделать бранч в базаре для него, скинуть на сервак и пулить периодически. А уж использовать систему билдинга (waf/scons) и просто делать install — верх совершенства, который может ещё и тесты прогнать автоматом перед инсталлингом изменённых файлов.
                        • 0
                          ауч. прочитал что вы раньше имена изменённых файлов в блокнот писали.

                          простите за то непонятное что я написал комментом выше. Пока рано)
                          • 0
                            Использую bzr на работе и subversion на google code для одного своего проекта )
                            Правда получилось так, что в итоге использую bzr как svn, даже более того — только локально. Рабочий код — lightweight checkout. Вы пишете о том, что надо сделать бранч, какие преимущества это может дать в моем случае?
                            • +1
                              trunk — там самое основное
                              prod — сюда мержится из транка только то что «оттестировано» и надо вливать на серв

                              на сервере bzr pull prod — и потом только пулить хоть каждые пять минут ).

                              собсна trunk ->(ручной мерж + проверки) -> prod -> (автоматический мерж/или ручной — не важно) -> production_server. При этом в транке можете экспериментировать сколько душе угодно, а вот prod — уже стейбл должен быть.

                              1. творите в trunk
                              2. рано или поздно думаете «ну вот вроде прекрасно все»
                              3. мержите транк в prod (тока скорее пулите, ибо в prod по идее своих изменений не должно быть)
                              4. идёте на серв и творите bzr pull.

                              всё. =) 4ю операцию можно автоматизировать, если хочется — т.е. автоматом пулить на сервере при закидывании в prod. Можно ваще работать в транке, потом мержить прямо на сервер o_0. Чтобы тут все гладко прошло — надо ставить плугин который bzr up после мержа удалённо запустит. Ну или самому его дергать через ssh.
                              • 0
                                Супер, спасибо за такое подробное описание.

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.