kstep 06.02.2010 03:24
Tips & tricks — Починка битых символьных ссылок (утилита для юзера — урок для начинающего программера)
У меня иногда так бывает, что после разбора завалов в домашнем каталоге иперемещения файлов на новые более логичные для них места, требуется
восстановить поломанные ссылки на эти перемещённые файлы.
Чтобы не делать этого ручками, набросал три скрипта: один на перле и два на
шелле.
Статья написана в том числе для начинающий программистов, с подробными
объяснениями алгоритмов, принятых и применённых решений в спойлерах.
Итак, декомпозиция задачи на три:
1) Найти все битые ссылки,
2) Для заданной ссылки найти места, где может находится «потерянный» ссылкой файл,
3) Починить ссылку, переписав её местоназначение.
Программистские подробности об алгоритмах и тонкостях реализации в спойлере,
если кому интересно:
Самый сложный шаг №2. Для него был разработан такой алгоритм (и воплощён в
перле, т.к. на шелле такое сделать довольно сложно):
1) Разбить полный путь к «потерянному» (ныне несуществующему) файлу на имя
файла (basename) и каталог (путь к файлу без самого файла, dirname),
2) Разбить путь к файлу на компоненты-каталоги и найти первый каталог в этой
цепочке, который реально отсутствует, таким образом все каталоги до этого
каталога существуют в системе. Это та точка, из которой будет вестись поиск,
назовём её guessed_dir.
3) Найти все файлы с заданным именем (basename) рекурсивно в подкаталогах
guessed_dir.
4) Если файлов в шаге 3 не найдено, поднимемся на каталог выше guessed_dir
(guessed_dir = guessed_dir/..) и повторим шаг 3.
5) Останов.
В результате выполнения этого алгоритма находится файл с заданным именем, если
он был перемещён практически в любое место файловой системы, причём поиск будет
очень быстрым, т.к. в первую очередь будет щупаться самая узкая область:
предполагается, что файл был перемещён в пределах самого узкого существующего
каталога, а расширяться область поиска будет только в том случае, если файл был
перемещён куда-то выше.
На деле я использую небольшую модификацию этого алгоритма:
1) я оптимизирую поиск при поднятии на каталог выше, исключая из поиска уже
проверенные более «узкие» каталоги,
2) я не останавливаюсь на первом найденном файле, стараюсь найти как можно
больше вариантов и даю юзеру (то есть себе, любимому =) как можно больше
вариантов для выбора и корректного восстановления файлов.
Теперь к делу.
1) Найти все битые ссылки.
Скрипт fixlinks:
2) Для заданной ссылки найти места, где может находится «потерянный» ссылкой файл.
Скрипт guess, сердце всего решения:
3) Починить ссылку, переписав её местоназначение.
Скрипт fixlink, промежуточная связка между fixlinks и guess:
Резюме для начинающих программистов в спойлере.
1) Учимся декомпозировать задачу и выбирать средства реализации.
Часто оказывается, что мелкие простые подзадачи легче всего решаются на
разных языках. В этом примере части, связанные с поиском файлов и перепрошитием
ссылок, легче всего решить шелловскими утилитами вроде find и ln, поэтому они
реализованы на shell, в то время, как задача по поиску и «догадыванию» нового
имени файла достаточно сложная и её проще было решить на более сложном и полном
языке; в моём случае я выбрал perl.
2) Учимся упрощать.
Скрипту на perl передаются очищенные данные: полностью корректный
полный путь к несуществующему файлу, полученный с помощью «readlink -m».
Сделать то же самое средствами чистого перл было бы сложнее, поэтому я этой сложности
избежал, водрузив эту работу на инструменты, изначально для этого предназначенные.
This is a true unix-way!
3) Учимся расширять знания на основе уже имещихся.
Раньше я не знал, что у readlink есть ключ «-m», который преобразует
относительный путь, вычитанный в символьной ссылке, в полный корректный путь,
разворачивая «.» и «..», без наложения необходимости существования на каждый
элемент пути. Однако я знал о существовании команды readlink, и знал, что мне
нужно получить полное имя файла по ссылке на него. Дальнейшее решение было
получено чтением мануалов по readlink. Если бы этой опции не было, пошёл бы
копать в сторону sed/awk/basename/dirname, в гугл пошёл бы в конце концов.
Учитесь опираться на существующие знания и получать новые для достижения чётко
поставленной цели: чем чётче цель, тем больше шансов найти решение за краткие
сроки! А иногда пять минут чтения мануалов избавляют от получасового написания
и отладки программы на том же perl/sed/awk.
4) Учимся документировать.
После написания каждого скрипта, к ним была написана справка по пользованию для
простого юзера. Это действительно важно, и не только если вы собираетесь
распространять скрипты другим людям, но и для себя. Предтавьте себя через
неделю, месяц, год... Вы уже забыли, что и как было написано, но вот встала
такая я же или похожая задача. Всё, что вы помните, это то, что когда-то для
решения этой задачи написали какие-то скрипты. А задачу надо решить быстро, и
нет времени вспоминать, как и что вы писали год назад и где эти скрипты лежат.
Если бы справки не было, вы бы даже если и нашли эти скрипты, то потратили бы
кучу времени, вспоминая, что и как было сделано и как это пользовать. Но вы
написали справку! И теперь эти скриптики мало того, что можно в крайнем случае
склероза найти полнотекстным поиском по файлам по ключевым словам «ссылки,
починить» (да тем же грепом, в конце концов), но и воспользоваться ими сможете,
что называется, с места в карьер: прочитали краткую справку и запустили
скрипты, не вдаваясь в подробности реализации.
Очень рекомендую «Совершенный код» Стива МакКоннела, если что ещё не читал/не знает (http://en.wikipedia.org/wiki/Code_Complete). Если постараться, можно найти нормальный скан, даже если шансов достать книгу в конкретном городе нет (увы, такое в Этой стране не редкость). Если бы её включили в обязательную программу обучения в айтишных вузах, качество кадров резко бы возрасло.
Вау, я впервые воткнул код на перле.
Не бывает некрасивых и непонятных языков, бывают программисты, пишущие человеконепонятный код. Даже на Лиспе можно написать очень изящно =)
Простенький пример на Scheme в доказательство с минимумом комментов:
Фтыкайте =)
Перл очень выразителен и красив, главное на нём не бубнить, а ясно выражаться ясно и просто =)
Простенький пример на Scheme в доказательство с минимумом комментов:
; Решение квадратного уравнения с коеффициентами a, b, c:
; ax^2 + bx + c = 0
(define (solve-sq-eq a b c)
(let ((drt (sqrt (- (sqr b) (* 4 a c)))) ; корень из дискриминанта
(-b (- b)) (2a (* 2 a)))
(values (/ (+ -b drt) 2a) (/ (- -b drt) 2a))))
; Проверка решения: являются ли корнями уравнения ax^2 + bx + c = 0 числа r1 и r2
(define (test-sq-eq a b c r1 r2)
(let ((sqeq (lambda (a b c x) (+ (* a x x) (* b x) c)))) ; сама формула уравнения
(and (= (sqeq a b c r1) 0) (= (sqeq a b c r2) 0))))
(call-with-values test-sq-eq (values 5 4 8 (solve-sq-eq 5 4 8)))
(call-with-values test-sq-eq (values 23 2 -345 (solve-sq-eq 23 2 -345)))
Фтыкайте =)
Перл очень выразителен и красив, главное на нём не бубнить, а ясно выражаться ясно и просто =)
Просто обычно программы на перле, которые я встречал, представляли из себя пару-тройку строк из непонятных символов =)
Да. За основу можно взять скрипт-пример из ABS Guide и немного доработать.
Но в любом случае, топикстартеру респект за проделанную работу.
Но в любом случае, топикстартеру респект за проделанную работу.
Конечно можно. Равно как можно было и избежать кода на шелле (к чему я более склонен, потому что перл мне всё же ближе). Но в данном случае решение задачи очень уж чётко предстало в моей голове в виде кода на перле, так что лично мне оказалось проще просто записать этот образ, чем извращаться с чистым шеллом...
Относительно скриптов, несколько неудобно, что первый fixlinks, а третий fixlink, можно легко перепутать. Может переименовать первый?
P.S. Хочу еще статей.