Главная » Программирование для UNIX » Файловая система UNIX: каталоги

0

Теперь  посмотрим, как перемещаться по иерархии каталогов. Для этого не потребуются  новые  системные вызовы, просто  используем  старые в новом  контексте. Поясним на примере функции spname, которая попытается справиться с неправильным именем файла. Функция

n = spname (имя, новое5имя);

ищет файл с именем, «достаточно похожим» на заданное параметром имя. Если таковое найдено, оно копируется в параметр новое5имя. Возвращаемое значение равно  –1, если ничего похожего не найдено, 0 – если найдено точное соответствие, и 1 – если потребовалось исправление.

Функция spname представляет собой удобное дополнение к программе p. Если  пользователь хочет  напечатать файл, но ошибся в написании его имени, программа p может попросить его уточнить, что имелось в виду:

$ p /urs/srx/ccmd/p/spnam.с       Абсолютно неправильное имя

"/usr/src/cmd/p/spname.с"? у     Предложенное исправление принято

/*  spname: возвратить  правильно написанное имя файла  */

Напишем функцию так, чтобы  она пыталась исправить в каждом элементе  имени файла следующие ошибки: одна  пропущенная или лишняя буква, одна буква неправильная, пара букв  перепутана местами – все эти варианты есть в примере. Такая программа будет просто находкой для невнимательной машинистки.

Перед  тем как начать программировать, совершим краткий экскурс в структуру файловой системы. Каталог – это файл, содержащий пере-

чень  имен  файлов с указанием их расположения. Под «расположением» здесь понимается индекс файла в другой таблице, называемой таб5 лицей индексных  дескрипторов. Запись в этой  таблице,  называемая индексным дескриптором (inode) файла, – это то место, где  хранится вся информация о нем,  за исключением имени. Таким образом, запись в каталоге состоит из двух элементов – номера индексного дескриптора и имени файла. Точная спецификация находится в файле sys/dir.h:

$ cat  /usr/include/sys/dir.h

#define DIRSIZ  14 /*  максимальная  длина имени файла  */

struct direct /*  структура записи в каталоге  */

{

ino_t d_ino;   /*  номер  индексного дескриптора  */ char  d_name[DIRSIZ];  /*  имя  файла */

};

$

«Тип» данных ino_t, определенный с помощью typedef, описывает запись  в таблице  индексных  дескрипторов. В системах PDP-11 и  VAX это тип unsigned  short, но в других системах он, скоре всего,  будет другим, не стоит полагаться на это в программах, поэтому и использован typedef. Полный  набор  «системных» типов   находится в  файле sys/ types.h, который должен быть включен до файла sys/dir.h.

Алгоритм работы spname достаточно прост. Предположим, надо обработать  имя  файла /d1/d2/f. Основная идея заключается в том, чтобы  отбросить первый элемент (/) и искать в корневом каталоге имя, максимально совпадающее со следующим элементом (d1), затем в найденном каталоге искать что-либо подобное d2 и так далее, пока  не будут найдены  соответствия для  всех  элементов. Если  в  очередном каталоге  не удалось найти подходящее имя, поиск прекращается.

Вся работа  разделена между тремя функциями: собственно spname отделяет элементы друг от друга  и составляет из них «наиболее вероятное» имя  файла. Функция mindist вызывается из spname и выполняет в заданном каталоге поиск файла с именем, максимально похожим на заданное, обращаясь при этом к функции spdist, вычисляющей «расстояние» между двумя именами.

/*  spname: возвращает правильно написанное имя файла  */

/*

*                                      spname(oldname,  newname)  char  *oldname,  *newname;

*                                         returns –1 if no reasonable match to  oldname,

*                                                                   0 if exact  match,

*                                                                   1 if corrected.

*                                         stores corrected name  in  newname.

*/

#include <sys/types.h>

#include <sys/dir.h>

spname(oldname, newname)

char  *oldname,  *newname;

{

char  *p,  guess[DIRSIZ+1],  best[DIRSIZ+1]; char  *new  =  newname, *old  =  oldname;

for  (;;) {

while (*old  == ‘/’)  /*  пропустить слэши  */

*new++  = *old++;

*new  = ‘\0′;

if (*old  == ‘\0′)    /*  правильно или  исправлено */ return  strcmp(oldname,newname)  !=  0;

p = guess;    /*  скопировать  следующий компонент   в guess  */ for ( ; *old  !=  ‘/’ &&   *old  !=  ‘\0′; old++)

if (p  < guess+DIRSIZ)

*p++ = *old;

*p = ‘\0′;

if  (mindist(newname,  guess,  best)  >=  3) return  –1;    /*  неудачно  */

for  (p  = best; *new = *p++;  ) /*  добавить в конец  */ new++;                                      /*  нового имени  */

}

}

mindist(dir,  guess,  best)    /*  просмотр dir в поиске guess  */ char  *dir,  *guess, *best;

{

/*  устанавливает  best,  возвращает расстояние 0..3  */ int d, nd,  fd;

struct {

ino_t ino;

char    name[DIRSIZ+1];      /*  на 1 больше, чем  в  dir.h */

} nbuf;

nbuf.name[DIRSIZ] = ‘\0′;   /*  +1  для  заключительного ‘\0′ */ if  (dir[0] == ‘\0′)     /*  текущий  каталог  */

dir = ".";

d = 3;    /*  минимальное  расстояние  */ if  ((fd=open(dir,  0)) == –1)

return d;

while  (read(fd,(char  *)  &nbuf,sizeof(struct  direct))  > 0) if  (nbuf.ino) {

nd =  spdist(nbuf.name,  guess); if  (nd  <=  d  &&  nd  !=  3)  {

strcpy(best,  nbuf.name); d  =  nd;

if (d  == 0)         /*  точное  совпадение  */ break;

}

}

close(fd); return d;

}

Если имя  каталога, переданное в функцию mindist, пусто, то поиск выполняется в текущем каталоге (.), за один проход обрабатывается один каталог. Обратите внимание, что в качестве буфера  для  чтения выступает  структура, а  не  символьный массив. Размер  определяется  посредством sizeof, а затем адрес преобразуется в указатель на тип char.

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

if (nd  <= d …)

а не

if (nd  < d …)

так что любой символ считается более подходящим, чем точка «.», которая всегда является первым элементом каталога.

/*  spdist: возвращает расстояние между  двумя именами  */

/*

*                                         очень грубый показатель  правильности написания:

*                                         0, если две строки идентичны

*                                         1, если два символа переставлены местами

*                                         2, если один символ добавлен,  удален или не  совпадает

*                                         3 – иначе

*/

#define  EQ(s,t)  (strcmp(s,t)  == 0) spdist(s,  t)

char  *s,  *t;

{

while  (*s++  == *t)

if (*t++  == ‘\0′)

return  0;             /*  точное совпадение  */ if (*––s) {

if (*t) {

if (s[1] &&   t[1] &&   *s  == t[1]

&&   *t  ==  s[1]  &&  EQ(s+2,  t+2)) return  1;      /*  перестановка  */

if (EQ(s+1,  t+1))

return 2;      /*  1 несовпадающий  символ  */

}

if (EQ(s+1, t))

return  2;             /*  лишний  символ */

}

if (*t &&   EQ(s,  t+1))

return 2;                     /*  недостающий  символ  */ return 3;

}

Теперь, когда у нас  есть  функция spname, очень просто  добавить проверку написания к команде p:

/*  p:  печатает  ввод порциями  (версия  4)  */

#include <stdio.h>

#define PAGESIZE         22

char    *progname; /*  имя программы  для сообщения  об  ошибке  */

main(argc,  argv) int  argc;

char  *argv[];

{

FILE *fp, *efopen();

int i, pagesize = PAGESIZE;

char  *p,  *getenv(), buf[BUFSIZ];

progname = argv[0];

if  ((p=getenv("PAGESIZE"))  !=  NULL) pagesize  =  atoi(p);

if  (argc >  1  &&  argv[1][0]  == ‘–’)  { pagesize  =  atoi(&argv[1][1]); argc––;

argv++;

}

if  (argc == 1)

print(stdin,  pagesize); else

for  (i = 1;  i < argc; i++)

switch  (spname(argv[i],  buf))  {

case  –1:        /*  совпадение  невозможно  */ fp  =  efopen(argv[i],  "r");

break;

case  1:         /*  исправлено  */ fprintf(stderr,  "\"%s\"?  ",  buf); if  (ttyin()  == ‘n’)

break; argv[i] =  buf;

/*  неуадчно…  */

case  0:  /*  точное  совпадение  */ fp  =  efopen(argv[i],  "r"); print(fp,  pagesize); fclose(fp);

} exit(0);

}

Автоматическое исправление имен  файлов надо использовать с определенной осторожностью. Оно хорошо работает в интерактивных программах, таких как p, но не подходит для программ, которые могут работать  без общения с пользователем.

Упражнение 7.5. Насколько можно было бы усовершенствовать эврис тический алгоритм  наилучшего совпадения в spname? Например,  нет смысла обрабатывать имя  файла как  каталог, а в данной версии это возможно. ~

Упражнение 7.6. Имя tx всегда  будет считаться подходящим, если  ка талог  заканчивается на tc, независимо от значения c. Можно ли улучшить определение расстояния? Напишите  программу и  посмотрите, как она понравится пользователям. ~

Упражнение 7.7.  Функция mindist читает каталог поэлементно. Мож но ли существенно ускорить программу p, выполняя чтение большими блоками? ~

Упражнение 7.8. Измените функцию spname так, чтобы она возвращала префикс  предполагаемого  имени, если  не  удалось  найти  достаточно близкое соответствие. Что делать, если  несколько имен  соответствуют этому префиксу? ~

Упражнение 7.9.  В каких еще  программах можно использовать spna– me?  Напишите  автономную программу,  исправляющую  полученные аргументы перед  тем,  как передать их в другую программу, например такую, как

$ fix  prog имена5файлов

Можно ли написать версию cd с использованием spname? Как  ее проинсталлировать? ~

Источник: Керниган Б., Пайк Р., UNIX. Программное окружение. – Пер. с англ. – СПб: Символ-Плюс, 2003. – 416 с., ил.

По теме:

  • Комментарии