Базовые операции с файловой системой UNIX

Работа с каталогами

Воспользуйтесь удобными функциями readdir() и stat() для просмотра записей в каталоге. Из-за изобилия файлов и каталогов на UNIX-системе будет полезно знать, как просмотреть записи в каталоге при помощи функции readdir() и извлекать информацию об этих записях при помощи функции stat(). Знание этих двух функций может оказать значительное влияние на карьеру UNIX-программиста, позволяя легко осуществлять поиск и чтение файлов, каталогов, символических ссылок на UNIX-системе.

Крис Херборт, внештатный сотрудник, независимый писатель

Photo of Chris HerborthКрис Херборт (Chris Herborth) уже более 10 лет пишет об операционных системах и программировании. Он выигрывал награды как старший технический писатель. Если он не играет с сыном Алексом или просто проводит время с женой, Крис посвящает свое свободное время написанию статей и исследованию видео игр (то есть, игре).



05.06.2009

Введение

Утверждение все является файлом, составляющее философию UNIX®, означает, что работа с файлами и каталогами происходит постоянно, вне зависимости от того, с каким приложением в данный момент осуществляется работа. Все данные хранятся в файлах: от даты до конфигурационных файлов и данных об устройствах, и функции, которые содержатся в системном заголовочном файле stdio.h, станут вам знакомы уже через несколько часов программирования под UNIX.

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

В этой статье рассказывается, как использовать семейство функций, содержащихся в библиотечном файле dirent.h [opendir()/readdir()/closedir()] для просмотра записей каталога, и функцию stat() для интерпретации этой информации.


Введение

Учебный программный код, приведенный в этой статье (см. раздел Материалы для скачивания), был написан в среде Eclipse 3.1 при помощи расширения C/C++ Development Tools (CDT); проект readdir_demo project является проектом типа Managed Make, собранным с использованием правил генерации программ CDT. Makefile отсутствует в проекте, но он настолько простой, что если понадобится скомпилировать программу вне среды Eclipse, создать Makefile достаточно легко.

Среда Eclipse (см. раздел Ресурсы) – это отличная интегрированная среда разработки (integrated development environment, IDE), которая с каждой новой версией становится только лучше. Среда была создана разработчиками EMACS и Makefile. Если до сих пор вы не пользовались Eclipse, то обязательно попробуйте.


Получение содержимого каталога

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

Функции из заголовочного файла dirent.h: opendir(), readdir() и closedir() – это то, что надо в подобной ситуации. Их применение очень схоже с использованием функций open/read/close при работе с файлами, но с одним исключением: функция readdir() возвращает указатель на специальную структуру (тип struct dirent) для каждого элемента каталога. Пройти по содержимому каталога можно при помощи псевдокода из листинга 1.

Листинг 1. Чтение содержимого каталога
dir = opendir( "some/path/name" )
entry = readdir( dir )
while entry is not NULL:
    do_something_with( entry )
    entry = readdir( dir )
closedir( dir )

Функции opendir() и readdir() возвращают NULL, если возникла какая-то проблема, а в глобальную переменную errno записывается причина проблемы. Если readdir() возвращает NULL и errno равняется 0 (или, по-другому, EOK или ENOERROR), это значит, что в каталоге больше нет записей.

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

Заметим, что readdir() не является многопоточной функцией, поскольку возвращаемая структура является статической переменной, которая хранится в библиотеке функции. Большинство современных UNIX-систем поддерживают многопоточную функцию readdir_r(), которую можно использовать вместо того чтобы писать свой многопоточный код.


Что находится в структуре struct dirent?

Стандарт POSIX 1003.1 определяет только один необходимый элемент структуры struct dirent – массив элементов типа char с именем d_name. Это имя элемента каталога в виде стандартной NUL-завершенной строки. Все остальное в этой структуре зависит от конкретной UNIX-системы.

Все остальное, что находится в struct dirent, не является переносимым. Совместимые системы могут вообще не иметь других элементов в этой структуре. При создании программного кода, который использует дополнительные члены этой структуры, необходимо отмечать эти элементы как непереносимые, и в идеале включать в свой проект код, который делает те же самые операции, только без дополнительных элементов в struct dirent.

Например, многие UNIX-системы содержат поле d_type и несколько дополнительных констант, которые позволяют узнать тип элемента каталога без вызова функции stat(). Кроме избавления от необходимости лишний раз вызывать функцию это непереносимое расширение позволяет избежать занимающего много ресурсов запроса к файловой системе за подробными метаданными. Функция stat() на большинстве UNIX-систем работает медленно.


Получение информации о файле

Кроме имен файлов, находящихся в каталоге может понадобиться некоторая дополнительная информация, которая внесла бы ясность в то, что надо делать дальше. По крайней мере, нельзя только по имени отличить файл от каталога.

Функция stat() заполняет структуру struct stat информацией об определенном файле; если вместо имени файла имеется файловый дескриптор, то можно использовать его совместно с fstat(). Если также необходимо обнаруживать символические ссылки, то вместе с именем файла следует использовать lstat().

В отличие от struct dirent, которую возвращает readdir(), struct stat имеет довольно много обязательных стандартных полей:

  • st_mode – права доступа к файлу (пользователь, группа, остальные) и флаги.
  • st_ino – порядковый номер файла.
  • st_dev – устройство, на котором расположен файл.
  • st_nlink – счетчик числа связей.
  • st_uid – идентификатор пользователя-владельца файла.
  • st_gid – идентификатор группы-владельца.
  • st_size – размер файла в байтах (для файлов regular).
  • st_atime – время последнего доступа к файлу.
  • st_mtime – время последней модификации файла.
  • st_ctime – время создания файла.

Используя макрос S_*() для поля st_mode, можно определить тип файла:

  • S_ISBLK(mode) – специальный блочный файл? (обычно это блочное устройство).
  • S_ISCHR(mode) – специальный символьный файл? (обычно это символьное устройство).
  • S_ISDIR(mode) – каталог?
  • S_ISFIFO(mode) – UNIX-канал (pipe) или файл типа FIFO?
  • S_ISLNK(mode) – символическая ссылка?
  • S_ISREG(mode) – обычный файл?

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

Немного о символических ссылках

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

Некоторые приложения, такие как ls и программы для резервного копирования, должны быть способны отобразить информацию о самой символической ссылке, например, на какой файл она указывает. Подобного результата можно добиться, используя lstat() вместо stat(); этот способ пригодится на случай, если надо работать с самой символической ссылкой, а не с файлом, на который она указывает.


Совместное использование readdir() и stat()

Выше было показано, как при помощи readdir() и stat() получить информацию о содержимом каталога. Продемонстрируем программный код, который иллюстрирует применение этих функций.

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

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

Листинг 2. Заголовочные файлы
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <limits.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>

Функция process_directory() (которая начинает свою работу в листинге 3 и заканчивает в листинге 6) просматривает заданный каталог и выводит некоторую информацию о каждом элементе содержимого. Указатель DIR, возвращаемый функцией opendir(), идентичен указателю на файл (FILE), который возвращает функция fopen(); это зависимый от типа ОС объект, который используется для прослеживания потока каталога, содержимое указателя DIR следует игнорировать.

Листинг 3. Обработка каталога
unsigned process_directory( char *theDir )
{
    DIR *dir = NULL;
    struct dirent entry;
    struct dirent *entryPtr = NULL;
    int retval = 0;
    unsigned count = 0;
    char pathName[PATH_MAX + 1];

    /* открыть указанный каталог, если возможно. */
	dir = opendir( theDir );
    if( dir == NULL ) {
        printf( "Error opening %s: %s", theDir, strerror( errno ) );
        return 0;
    }

После открытия заданного каталога надо вызвать readdir_r() (листинг 4) для получения информации о первом элементе этого каталога; каждый последующий вызов readdir_r() возвращает следующий элемент до тех пор, пока не будет достигнут конец каталога и entryPtr не будет установлен в NULL. Также следует использовать strncmp(), чтобы игнорировать элементы ." и "..". Если нет необходимости пропускать обработку этих элементов, то обработка каталогов будет выполняться бесконечно (например, "theDir/./././././././././." и так далее).

Листинг 4. Получение отдельного элемента
    retval = readdir_r( dir, &entry, &entryPtr );
    while( entryPtr != NULL ) {
        struct stat entryInfo;
        
        if( ( strncmp( entry.d_name, ".", PATH_MAX ) == 0 ) ||
            ( strncmp( entry.d_name, "..", PATH_MAX ) == 0 ) ) {
            /* Short-circuit the . and .. entries. */
            retval = readdir_r( dir, &entry, &entryPtr );
            continue;
        }

Теперь, когда получено имя элемента каталога, для него надо собрать абсолютный путь (листинг 5), а затем вызвать функцию lstat() для получения информации об этом элементе. Поскольку символическим ссылкам требуется особая обработка, в данном случае следует использовать lstat(). Целевой файл символической ссылки можно узнать при помощи функции readlink().

Если элемент является каталогом, то для него выполняется рекурсивный вызов process_directory() и к общему числу обнаруженных элементов добавляются вновь найденные. Если элемент является файлом, выводятся его имя и размер (размер берется из поля st_size структуры struct stat).

Листинг 5. Обработка отдельного элемента
        (void)strncpy( pathName, theDir, PATH_MAX );
        (void)strncat( pathName, "/", PATH_MAX );
        (void)strncat( pathName, entry.d_name, PATH_MAX );
        
        if( lstat( pathName, &entryInfo ) == 0 ) {
            /* вызов stat() был успешным, так что продолжаем. */
            count++;
            
            if( S_ISDIR( entryInfo.st_mode ) ) {            
/* каталог */
                printf( "processing %s/\n", pathName );
                count += process_directory( pathName );
            } else if( S_ISREG( entryInfo.st_mode ) ) { 
/* обычный файл */
                printf( "\t%s has %lld bytes\n",
                    pathName, (long long)entryInfo.st_size );
            } else if( S_ISLNK( entryInfo.st_mode ) ) { 
/* символическая ссылка */
                char targetName[PATH_MAX + 1];
                if( readlink( pathName, targetName, PATH_MAX ) != -1 ) {
                    printf( "\t%s -> %s\n", pathName, targetName );
                } else {
                    printf( "\t%s -> (invalid symbolic link!)\n", pathName );
                }
            }
        } else {
            printf( "Error statting %s: %s\n", pathName, strerror( errno ) );
        }

В конце цикла while выполняются чтение и обработка еще одного элемента каталога (листинг 6). Если обработка элементов каталога закончена, то текущий рабочий каталог закрывается и возвращается число элементов, которые были обработаны.

Листинг 6. Чтение еще одного элемента
        retval = readdir_r( dir, &entry, &entryPtr );
    }
    
    /* закрытие каталога и возвращение числа элементов. */
    (void)closedir( dir );
    return count;
}

И, наконец, листинг 7 содержит функцию main() программы, которая вызывает функцию process_directory() для каждого аргумента, содержащегося в строке команды. В бизнес-приложении должен быть также предусмотрен вывод служебного сообщения на случай, если пользователь не задаст по крайней мере одного аргумента, но я оставлю этот аспект читателю для упражнения.

Листинг 7. Функция main
/* readdir_demo main()
 * 
 * проходит через указанные каталоги 
 * и передает их в метод process_directory().
 */
int main( int argc, char **argv )
{
    int idx = 0;
    unsigned count = 0;
    
    for( idx = 1; idx < argc; idx++ ) {
        count += process_directory( argv[idx] );
    }
    
    return EXIT_SUCCESS;
}

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


Заключение

Применяйте функции readdir() и stat() для просмотра содержимого каталога и определения дальнейших действий, которые необходимо выполнить над этими записями, и, возможно, для выполнения каких-то особенных, специфических задач во время обработки содержимого каталога. Хотя это весьма полезные функции, некоторые начинающие UNIX-разработчики считают их слишком сложными. Эта статья поможет таким UNIX-разработчикам быстро освоить readdir() и stat().


Загрузка

ОписаниеИмяРазмер
make проект для Eclipse 3.1au-readdir_demo.zip24KB

Ресурсы

Научиться

Получить продукты и технологии

  • IBM trial software: ознакомительные версии программного обеспечения для разработчиков, которые можно загрузить прямо со страницы сообщества developerWorks.(EN)

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=AIX и UNIX
ArticleID=394741
ArticleTitle=Базовые операции с файловой системой UNIX
publish-date=06052009