Видео ролики бесплатно онлайн

Смотреть жесткий видео

Официальный сайт filipoc 24/7/365

Смотреть видео бесплатно

06.02.10 03:24 kstep

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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

#!/bin/sh

if test "$1" = "help"; then
    cat <<EOD

Чинит все поломанные ссылки в заданном каталоге и всех его подкаталогах.

Вызов:
    fixlinks [path/to/dir]

Результат:
    Находит все поломанные ссылки в заданном каталоге и всех его полкаталогах
    (по умолчанию поиск из текущего каталога) и пытается угадать новое размещение
    файла, на который указывает каждая ссылка.
    Для каждой поломанной ссылки юзеру будет предложен нумерованный список найденных
    файлов, которые удалось обнаружить, и юзер сможет либо указать, номер файла
    для полинки ссылки, либо 0 (или любую другую последовательность нецифровых символов),
    чтобы отменить починку этой ссылки и продолжить поиск.

Требует нахождения скриптов fixlink и guess в одном с собой каталоге.

EOD

    exit
fi

FIXER="`dirname $0`"/fixlink
BASEDIR="$1"
test -n "$BASEDIR" || BASEDIR="."
test -d "$BASEDIR" || exit

find "$BASEDIR" -type l -exec "$FIXER" {} \;
 


2) Для заданной ссылки найти места, где может находится «потерянный» ссылкой файл.
Скрипт guess, сердце всего решения:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

#!/usr/bin/perl

use strict;

# 1. Получим само исходное имя
my $fullname = $ARGV[0];
unless ($fullname)
{
    print <<EOD

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

Вызов:
    guess /full/path/to/non/existing/file/name

Результат:
    Выводит нумерованный список найденных файлов на STDERR и спрашивает у юзера
    номер файла. Если юзер укажет номер файла, то выведет его имя на STDOUT,
    иначе (если юзер укажет 0 или любую нецифровую строку) не выведет ничего.

Удобно использовать в скриптах, например для починки поломанных ссылок.

EOD
;
    exit;
}

exit if -e $fullname;

# 2. Разобъём на имя файла и имя каталога
my ($basename) = ($fullname =~ /([^\/]+)$/);
my ($dirname) = ($fullname =~ /^(.+)\//);

# 3. Имя каталога очистим и разобъём на компоненты
$dirname =~ s/^\/+//;
$dirname =~ s/\/+$//;
my @dirparts = split /\//, $dirname;

# 4. Найдём первый несуществующий каталог в цепочке компонентов пути
my $existing_dir = "";
my @existing_dir = ();
foreach my $dirpart (@dirparts)
{
    last unless -d "$existing_dir/$dirpart";
    $existing_dir .= "/$dirpart";
    push @existing_dir, $dirpart;
}

# 5. Ищем данный файл в поддереве
my %guessed_dirs = ();
my @guessed_files = ();
my $num = 0;
for (; @existing_dir; pop @existing_dir)
{
    my $guessed_dir = "/" . join("/", @existing_dir);
    my $guessed_file = guess_file($guessed_dir, $basename, \%guessed_dirs);
    if ($guessed_file)
    {
        printf STDERR "%d) %s\n", ++$num, $guessed_file;
        $guessed_dirs{$guessed_dir} = 1;
        push @guessed_files, $guessed_file;
    }
}

if ($num > 0)
{
    my $chosen_file;
    do {
        print STDERR "Choose filename to fix, 0 to cancel: ";
        $chosen_file = 0+<STDIN>;
    } until ((0 <= $chosen_file) && ($chosen_file <= $num));
    exit unless $chosen_file;
    print $guessed_files[$chosen_file-1];
}

sub guess_file
{
    my ($dir, $file, $ignore_dirs) = @_;
    return "" if exists($ignore_dirs->{$dir});
    my $wildguess = "$dir/$file";
    return $wildguess if -e $wildguess;
    foreach my $subdir (glob("$dir/*"))
    {
        next unless -d $subdir;
        my $afile = guess_file($subdir, $file, $ignore_dirs);
        return $afile if $afile;
    }
    return "";
}
 


3) Починить ссылку, переписав её местоназначение.
Скрипт fixlink, промежуточная связка между fixlinks и guess:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

#!/bin/sh

if test -z "$1"; then
    cat <<EOD

Чинит заданную символьную ссылку, если файл, на который эта ссылка указывает,
уже куда-то перемещён.

Вызов:
    fixlink sym/link/name

Результат:
    Попытается угадать новое местоположение файла, на который указывает ссылка,
    и предлагает юзеру выбрать один из найденных файлов. Юзер может либо ввести
    номер найденного файла, чтобы починить ссылку, либо ввести 0 или другую
    нецифровую последовательность символов, чтобы отказаться от починки ссылки.

Утилита требует наличия перлового скрипта guess в одном с ней каталоге.

EOD

    exit
fi

LINKNAME="$1"
GUESSER="`dirname $0`/guess"
REALNAME="`readlink -m \"$LINKNAME\"`"

test -e "$REALNAME" && exit 0

echo; echo "Link $LINKNAME points to non-existing file $REALNAME, guessing..."
GUESS="`$GUESSER \"$REALNAME\"`"
test -n "$GUESS" && ln -sf "$GUESS" "$LINKNAME"
 


Резюме для начинающих программистов в спойлере.

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) Учимся документировать.

После написания каждого скрипта, к ним была написана справка по пользованию для
простого юзера. Это действительно важно, и не только если вы собираетесь
распространять скрипты другим людям, но и для себя. Предтавьте себя через
неделю, месяц, год... Вы уже забыли, что и как было написано, но вот встала
такая я же или похожая задача. Всё, что вы помните, это то, что когда-то для
решения этой задачи написали какие-то скрипты. А задачу надо решить быстро, и
нет времени вспоминать, как и что вы писали год назад и где эти скрипты лежат.
Если бы справки не было, вы бы даже если и нашли эти скрипты, то потратили бы
кучу времени, вспоминая, что и как было сделано и как это пользовать. Но вы
написали справку! И теперь эти скриптики мало того, что можно в крайнем случае
склероза найти полнотекстным поиском по файлам по ключевым словам «ссылки,
починить» (да тем же грепом, в конце концов), но и воспользоваться ими сможете,
что называется, с места в карьер: прочитали краткую справку и запустили
скрипты, не вдаваясь в подробности реализации.



booley 06.02.10 07:17 # +0
Плюсую. Особо понравились комментарии для начинающих программистов, может напишете что-нибудь еще в этом духе? Я бы точно прочел :)

Относительно скриптов, несколько неудобно, что первый fixlinks, а третий fixlink, можно легко перепутать. Может переименовать первый?

P.S. Хочу еще статей.
kstep 06.02.10 14:41 # +0
Можно и переименовать, дело вкуса.
kstep 06.02.10 16:01 # +0
Очень рекомендую «Совершенный код» Стива МакКоннела, если что ещё не читал/не знает (http://en.wikipedia.org/wiki/Code_Complete). Если постараться, можно найти нормальный скан, даже если шансов достать книгу в конкретном городе нет (увы, такое в Этой стране не редкость). Если бы её включили в обязательную программу обучения в айтишных вузах, качество кадров резко бы возрасло.
booley 06.02.10 18:11 # +0
http://www.ozon.ru/context/detail/id/3159814/ - оно?
kstep 06.02.10 21:15 # +0
Именно.
chemikadze 06.02.10 09:42 # +1
Вау, я впервые воткнул код на перле.
kstep 06.02.10 14:58 # +0
Не бывает некрасивых и непонятных языков, бывают программисты, пишущие человеконепонятный код. Даже на Лиспе можно написать очень изящно =)

Простенький пример на Scheme в доказательство с минимумом комментов:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

; Решение квадратного уравнения с коеффициентами 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)))
 

Фтыкайте =)

Перл очень выразителен и красив, главное на нём не бубнить, а ясно выражаться ясно и просто =)
chemikadze 07.02.10 16:56 # +0
Просто обычно программы на перле, которые я встречал, представляли из себя пару-тройку строк из непонятных символов =)
booley 07.02.10 18:28 # +0
Вы уверены, что это был не патч Бармина? :)
chemikadze 07.02.10 18:48 # +0
В том числе.
digiwhite 06.02.10 12:01 # +0
Вопрос: а можно было избежать кода на перле?
cppmm 06.02.10 12:29 # +0
Да. За основу можно взять скрипт-пример из ABS Guide и немного доработать.

Но в любом случае, топикстартеру респект за проделанную работу.
kstep 06.02.10 14:47 # +0
Конечно можно. Равно как можно было и избежать кода на шелле (к чему я более склонен, потому что перл мне всё же ближе). Но в данном случае решение задачи очень уж чётко предстало в моей голове в виде кода на перле, так что лично мне оказалось проще просто записать этот образ, чем извращаться с чистым шеллом...

Лучшие блоги (все 107)
Топ пользователей Топ блогов
Топ пользователей Топ блогов
Элита (все 2128 из 160 городов)
Топ пользователей Топ блогов
В сети: mirivlad, openphantom

Новенькие: korovann, blaw, off220, troyane, arts
welinux.ru

Смотреть онлайн бесплатно

Онлайн видео бесплатно


Смотреть русское с разговорами видео

Online video HD

Видео скачать на телефон

Русские фильмы бесплатно

Full HD video online

Смотреть видео онлайн

Смотреть HD видео бесплатно

School смотреть онлайн