【UNIX/Linux】文件和目录【Part 3】

本文是笔者拜读《UNIX环境高级编程》第4章(文件和目录)的学习笔记。本文的主要内容包括link、rename、符号链接、文件的时间、读目录和一个统计文件类型的实例。文中不仅包含书中的知识点,也包括笔者的理解。

link、linkat、unlink、unlinkat和remove

可以有多个目录项指向任一文件的i节点。使用linklinkat可以创建一个指向现有文件的链接。
在这里插入图片描述
成功返回0,失败返回-1.
这两个函数创建了一个新目录项newpath,它引用现有文件oldpath。如果newoath已存在则报错。只创建newpath中的最后一个分量,路径中的其他部分应当已经存在。

对于linkat,引用的现有文件是通过olddirfdoldpath确定的,新的路径名是通过newdirfdnewpath确定的。olddirfdnewdirfd是某个打开目录的文件描述符。

当现有文件是符号链接时,如果flag中参数设置了AT_SYMLINK_FOLLOW,就创建指向符号链接所引用的文件的链接,否则创建指向符号链接本身的链接。

创建新目录项和增加链接计数是一个原子操作
大多数的链接实现,要求现有的和新建的文件在同一文件系统中。
只有超级用户才能创建一个指向目录的硬链接,因为这样可能会形成循环。很多文件系统实现不允许对目录的硬链接。

使用unlink能删除现有的目录项。
在这里插入图片描述
成功返回0,失败返回-1.
这两个函数删除目录项,并将pathname所引用的文件链接计数减1。如果函数调用失败,则不对文件做任何更改。

为了解除对文件的链接,必须对包含该目录项的目录具有写和执行权限。

只有当链接计数为0时,该文件的内容才被删除。但只要有进程打开了该文件,也不能删除其内容。关闭一个文件时,内核首先检查打开该文件的进程个数,如果是0,内核再去检查其链接计数,如果也是0,才删除该文件。

如果unlinkat函数的flag参数被设置为AT_REMOVEDIR,该函数可以类似于rmdir删除目录。

进程用opencreat创建一个文件,然后立即调用unlink,因为有进程打开了该文件,所以并不会立即删除该文件。只有当进程关闭该文件或进程终止时,文件被删除。

如果pathname是符号链接,那么unlink删除符号链接,而不是它所引用的文件。

也可以用remove函数解除对一个文件或目录的链接。对于文件remove == unlink,对于目录remove == rmdir
在这里插入图片描述

rename、renameat

文件或目录可以用renamerenameat进行重命名。
在这里插入图片描述
成功返回0,失败返回-1·
根据oldpath是文件、目录还是符号链接,有以下情况:
(1)如果oldpath指的是文件,而newpath已存在(不是目录),则先将该目录项删除,然后将oldpath重命名为newpath
(2)如果oldpath指的是目录,而newpath已存在(是空目录),则将其删除,然后将oldpath重命名为newpathnewpath不能包含oldpath作为其路径前缀。
(3)如果oldpathnewpath引用符号链接,则处理的是符号链接本身。
(4)不能对...重命名。
(5)如果oldpath等于newpath,函数不做任何更改并成功返回。
调用进程会删除oldpath目录项,创建newpath目录项,需要对包含两者的目录有写和执行权限。

符号链接

符号链接是对一个文件的间接指针,而硬链接直接指向了文件的i节点。
对符号链接以及它指向何种对象并无任何文件系统限制,任何用户都可以创建指向目录的符号链接。
当使用以名字引用文件的函数时,应当了解该函数是否处理符号链接。如果路径名是符号链接,mkdirmkinfomknodrmdir这些函数会出错返回。

函数是否跟随符号链接
access
chdir
chmod
chown
creat
exec
lchown
link
lstat
open
opendir
pathconf
readlink
remove
rename
stat
truncate
unlink

读取和创建符号链接

在这里插入图片描述
函数创建了一个指向target的新目录项linkpath
target可以不存在,targetlinkpath可以不在同一文件系统中。
使用readlink可以打开链接本身,并读该链接中的名字。
在这里插入图片描述
函数执行成功,返回读入buf的字节数,在buf中返回的符号链接的内容不以null终止。失败返回-1.

文件的时间

stat结构中,每个文件维护3个时间字段。

字段说明例子ls选项
st_atim文件数据的最后访问时间read-u
st_mtim文件数据的最后修改时间write默认
st_ctimi节点状态的最后更改时间chmod、chown-c

修改时间是文件内容最后一次被修改的时间,状态更改时间是该文件的i节点最后一次被修改的时间。更改文件的访问权限、用户ID、链接数等会影响i节点。系统不维护对i节点的最后一次访问。

futimens、utimensat和utimes

一个文件的访问和修改时间可以用以下几个函数更改。futimensutimensat可以指定纳秒级精度的时间戳。
在这里插入图片描述
这两个函数的times数组参数的第一个元素是访问时间,第二个是修改时间。用秒和纳秒表示时间。这是自特定时间(1970年1月1日00:00:00)以来所经过的秒数,不足一秒的部分用纳秒表示。
utimes函数对路径名进行操作。
在这里插入图片描述

mkdir、mkdirat和rmdir

在这里插入图片描述
mkdirmkdirat用于创建目录。成功返回0,失败返回-1.
对目录指定的权限要大一些,至少要设置执行权限位,允许访问该目录中的文件名。
在这里插入图片描述
rmdir函数可以删除一个空目录(只包含./../)。

读目录

在这里插入图片描述
成功返回指针,失败返回NULL.
fdopendir可以把打开文件描述符转换成目录处理函数需要的DIR结构。
在这里插入图片描述
成功返回指针,失败或在目录尾返回NULL
dirent结构至少包含以下两个成员:

ino_t d_ino; // i节点编号
char d_name[]; // 文件名,以'\0'为结尾

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
目录中各目录项的顺序与实现有关,它们通常并不按字母顺序排列。

看得让人头大。别试图理解书中所有的东西,有个大概印象即可。
主要靠项目实践和做题巩固知识。
感觉书中有些地方翻译得不太行。

实例

输入是起点路径名,从该点开始递归降序遍历文件层次结构,最终得到各类型的文件计数。
先简单描述一下思路:

/* 定义所需的全局变量,统计各类型的文件数量 */

/* 根据非目录文件的类型更新计数值 */
void FileCnt();

/* 以pathname作为起点路径名,统计数据 */
int FileTree(const char *pathname) { 
	if (/* 获取文件状态失败 */) {
		return -1;
	}
	int ret = 0;
	if (/* pathname是个目录 */) {
		// 更新目录文件计数
		while (/*在pathname中能读到目录项*/) {
			ret = FileTree(/* 目录项 */);
		}
	}
	else {
		// 更新相应的文件计数
		FileCnt();
	}
	return ret;
}
// 
int main(int argc, char **argv) {
	FileTree(argv[1]);
	/* 打印统计结果 */
	return 0;
}

具体实现:

// count.c
#include <stdio.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int nTtl; // 文件总数
int nReg; // 普通文件个数
int nDir; // 目录文件个数
int nFifo; // 命名管道个数
int nChr; // 字符特殊文件个数
int nBlk; // 块特殊文件个数
int nSlink; // 符号链接文件个数
int nSock; // 套接字个数
struct stat statBuf;

void FileCnt() {
	if (S_ISREG(statBuf.st_mode)) {
		++nReg;
	}
	else if (S_ISCHR(statBuf.st_mode)) {
		++nChr;
	}
	else if (S_ISBLK(statBuf.st_mode)) {
		++nBlk;
	}
	else if (S_ISFIFO(statBuf.st_mode)) {
		++nFifo;
	}
	else if (S_ISLNK(statBuf.st_mode)) {
		++nSlink;
	}
	else if (S_ISSOCK(statBuf.st_mode)) {
		++nSock;
	}
}

int FileTree(const char *path) {
	int ret = 0;
	if (lstat(path, &statBuf) < 0) {
		fprintf(stderr, "%s: ", path);
		perror("lstat error");
		return -1;
	}
	if (S_ISDIR(statBuf.st_mode)) {
		++nDir;
		DIR *dir = opendir(path);
		struct dirent *psd;
		while (psd = readdir(dir)) {
			if (strcmp(psd->d_name, ".") == 0 || 
				strcmp(psd->d_name, "..") == 0) {
				continue;
			}
			char str[300];
			sprintf(str, "%s/%s", path, psd->d_name);
			ret = FileTree(str);
		}
	}
	else {
		FileCnt();
	}
	return ret;
}

int main(int argc, char **argv) {
	if (argc != 2) {
		printf("invalid input\n");
		return -1;
	}
	int ret = FileTree(argv[1]);
	nTtl = nReg + nDir + nChr + nBlk + nSlink + nFifo + nSock;
	nTtl = (nTtl == 0) ? 1 : nTtl;
	printf("普通文件数量:%2d, 占比:%4.2f%%\n", nReg, nReg * 100.0 / nTtl);	
	printf("目录文件数量:%2d, 占比:%4.2f%%\n", nDir, nDir * 100.0 / nTtl);	
	printf("字符特殊文件数量:%2d, 占比:%4.2f%%\n", nChr, nChr * 100.0 / nTtl);	
	printf("块特殊文件数量:%2d, 占比:%4.2f%%\n", nBlk, nBlk * 100.0 / nTtl);	
	printf("命名管道文件数量:%2d, 占比:%4.2f%%\n", nFifo, nFifo * 100.0 / nTtl);	
	printf("符号链接文件数量:%2d, 占比:%4.2f%%\n", nSlink, nSlink * 100.0 / nTtl);	
	printf("套接字文件数量:%2d, 占比:%4.2f%%\n", nSock, nSock * 100.0 / nTtl);	
	return 0;
}

运行结果如下:
在这里插入图片描述

该程序是笔者根据自己的理解写的,虽然不如书上的实例通用,但大概能实现期望的功能(文件数量过多时,程序会出现段错误)。
该程序所统计的目录文件数比期望的多1,因为程序把main函数的参数argv[1]也算作了一个目录。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值