分类: LINUX
2008-11-20 10:45:31
Linux系统上一个常见问题就是对目录进行扫描,也就是确定一个特定目录下存放的文件。在shell程序设计中,这很容易做到——只需让shell做一次表达式的通配符扩展。过去,UNIX操作系统的各种变体都允许用户通过编程访问底层文件系统结构。我们仍然可以把目录当作一个普通文件那样打开,并直接读取目录数据项,但不同的文件系统结构及其实现方法已经使这种办法没什么可移植性了。现在,一整套标准的库函数已经被开发出来,使得目录的扫描工作变得简单多了。
与目录操作有关的函数在dirent.h头文件中声明。它们把一个名为DIR的结构作为目录操作的基础。被称为“目录流”的指向这个结构的指针(DIR *)被用来完成各种目录操作,其使用方法与文件流(FILE *)非常相似。目录数据项本身在dirent结构中返回,该结构也是在dirent.h头文件里声明的,用户绝不要直接改动DIR结构中的数据字段。
我们将介绍下面这几个函数:
opendir、closedir
readdir
telldir
seekdir
*
opendir 函数
opendir函数的作用是打开一个目录并建立一个目录流。如果成功,它返回一个指向DIR结构的指针,该指针用于读取目录数据项。
#include
#include
DIR *opendir(const char *name);
opendir在失败时会返回一个空指针。注意,目录流用一个底层文件描述符来访问目录本身,所以如果打开的文件过多,opendir可能会失败。
*
readdir 函数
readdir函数将返回一个指针,指针指向的结构里保存着目录流drip中下一个目录项的有关资料。后续的readdir调用将返回后续的目录项。如果发生错误或者到达目录尾,readdir将返回NULL。POSIX兼容的系统在到达目录尾时会返回NULL,但并不改变errno的值,在发生错误时才会设置errno。
#include
#include
struct dirent *readdir(DIR *dirp);
注意,如果在readdir函数扫描目录的同时还有其他进程在该目录里创建或删除文件,readdir将不保证能够列出该目录里的所有文件(和子目录)。
dirent结构中包含的目录数据项内容包括以下部分:
ino_t
d_ino——文件的inode节点号。
char
d_name[]——文件的名字。
要想进一步了解目录中某个文件的详细资料,则需要使用在本章前面介绍过的stat调用。
*
telldir 函数
telldir函数的返回值记录着一个目录流里的当前位置。你可以在随后的seekdir调用中利用这个值来重置目录扫描到当前位置。
#include
#include
long int telldir(DIR *dirp);
*
seekdir 函数
seekdir函数的作用是设置目录流dirp的目录项指针。loc的值用来设置指针位置,它应该通过前一个telldir调用获得。
#include
#include
void seekdir(DIR *dirp, long int loc);
*
closedir 函数
closedir函数关闭一个目录流并释放与之关联的资源。它在执行成功时返同0,发生错误时返回-1。
#include
#include
int closedir(DIR *dirp);
在下面的printdir.c程序中,我们把许多文件处理函数集中在一起实现一个简单的目录列表功能。目录中的每个文件单独列在一行上。每个子目录会在它的名字后面加上一个斜线字符/,子目录中的文件在缩进四个空格后依次排列。
程序会逐个切换到每个下级子目录里,这样使它找到的文件都有一个可用的名字,也就是说,它们都可以被直接传递到opendir函数里去。如果目录的嵌套结构太深,程序执行就会失败,因为对允许打开的目录流数目是有限制的。
我们当然可以采取一个更通用的做法,让程序能够通过一个命令行参数来指定起点(从哪个目录开始)。请查阅有关工具程序(如ls和find)的Linux源代码来找到实现更通用程序的方法。
实验:一个目录扫描程序
# cat printdir.c
/* We start with the appropriate headers and
then a function, printdir,
which prints out the current directory.
It will recurse for subdirectories, using
the depth parameter is used for indentation.
*/
#include
#include
#include
#include
#include
#include
void printdir(char *dir, int
depth)
{
DIR *dp;
struct dirent *entry;
struct stat statbuf;
if((dp = opendir(dir)) == NULL) {
fprintf(stderr,"cannot open
directory: %s\n", dir);
return;
}
chdir(dir);
while((entry = readdir(dp)) != NULL) {
lstat(entry->d_name,&statbuf);
if(S_ISDIR(statbuf.st_mode)) {
/* Found a directory, but ignore .
and .. */
if(strcmp(".",entry->d_name) == 0 ||
strcmp("..",entry->d_name) == 0)
continue;
printf("%*s%s/\n",depth,"",entry->d_name);
/* Recurse at a new indent level */
printdir(entry->d_name,depth+4);
}
else
printf("%*s%s\n",depth,"",entry->d_name);
}
chdir("..");
closedir(dp);
}
/* Now we move onto the main function. */
int main()
{
printf("Directory scan of
/home:\n");
printdir("/home",0);
printf("done.\n");
exit(0);
}
cat printdir2.c
/* We start with the appropriate headers and
then a function, printdir,
which prints out the current directory.
It will recurse for subdirectories, using
the depth parameter is used for indentation.
*/
#include
#include
#include
#include
#include
#include
void printdir(char *dir, int depth)
{
DIR *dp;
struct dirent *entry;
struct stat statbuf;
if((dp = opendir(dir)) == NULL) {
fprintf(stderr,"cannot open
directory: %s\n", dir);
return;
}
chdir(dir);
while((entry = readdir(dp)) != NULL) {
lstat(entry->d_name,&statbuf);
if(S_ISDIR(statbuf.st_mode)) {
/* Found a directory, but ignore .
and .. */
if(strcmp(".",entry->d_name) == 0 ||
strcmp("..",entry->d_name) == 0)
continue;
printf("%*s%s/\n",depth,"",entry->d_name);
/* Recurse at a new indent level */
printdir(entry->d_name,depth+4);
}
else
printf("%*s%s\n",depth,"",entry->d_name);
}
chdir("..");
closedir(dp);
}
/* Now we move onto the main function. */
int main(int argc, char* argv[])
{
char *topdir, pwd[2]=".";
if (argc != 2)
topdir=pwd;
else
topdir=argv[1];
printf("Directory scan of
%s\n",topdir);
printdir(topdir,0);
printf("done.\n");
exit(0);
}