网站移动排名,龙岗公司的网站制作,烟台市科技局网站,深圳网站建设服务哪家有文件和目录
获取文件属性
通过 ls 命令可以查看到文件的很多属性内容#xff0c;这些文件属性的内容可以通过以下几个函数获取:
#include sys/types.h
#include sys/stat.h
#include unistd.hint stat(const char *pathname, struct stat *statbuf…文件和目录
获取文件属性
通过 ls 命令可以查看到文件的很多属性内容这些文件属性的内容可以通过以下几个函数获取:
#include sys/types.h
#include sys/stat.h
#include unistd.hint stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);这些函数在 statbuf 指向的缓冲区中返回有关文件的信息。文件本身不需要任何权限但在 stat()、fstatat() 和 lstat() 的情况下需要对通向该文件的路径名中的所有目录具有执行搜索权限。
lstat() 与 stat() 类似只是如果路径名是符号链接则 lstat() 返回有关链接本身的信息而不是它引用的文件。
fstat() 与 stat() 类似只是 fstat() 要检索其信息的文件由文件描述符 fd 指定。
stat 结构的基本形式如下所示(ubuntu20.04 man 手册):
struct stat {dev_t st_dev; /* ID of device containing file */ino_t st_ino; /* Inode number */mode_t st_mode; /* File type and mode */nlink_t st_nlink; /* Number of hard links */uid_t st_uid; /* User ID of owner */gid_t st_gid; /* Group ID of owner */dev_t st_rdev; /* Device ID (if special file) */off_t st_size; /* Total size, in bytes */blksize_t st_blksize; /* Block size for filesystem I/O */blkcnt_t st_blocks; /* Number of 512B blocks allocated *//* Since Linux 2.6, the kernel supports nanosecondprecision for the following timestamp fields.For the details before Linux 2.6, see NOTES. */struct timespec st_atim; /* Time of last access */struct timespec st_mtim; /* Time of last modification */struct timespec st_ctim; /* Time of last status change */#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};timespec 结构类型按照秒和纳秒定义了时间至少包括下面两个字段:
time_t tv_sec;
long tv_nsec;通过 stat 函数获取文件大小
#include stdio.h
#include stdlib.h
#include sys/stat.h
#include sys/types.h
#include unistd.hstatic off_t file_size(const char *pathname);int main(int argc, const char *argv[]) {if (2 ! argc) {fprintf(stderr, Usage: %s pathname\n, argv[0]);exit(EXIT_FAILURE);}off_t size file_size(argv[1]);printf(%s size is %ld\n, argv[1], size);return 0;
}static off_t file_size(const char *pathname) {struct stat stat_buf;if (-1 stat(pathname, stat_buf)) {perror(get file stat error);exit(EXIT_FAILURE);}return stat_buf.st_size;
}使用 stat 函数最多的地方就是 ls -l 命令因此在学完此函数可以自己初步实现一个 myls 程序。除此之外在 Linux 中使用 stat 命令可以获取文件的详细信息一个文件在磁盘中占用的实际内存大小是看 st_blocks一个块一般占用大小是 st_blksize所有一个文件的实际内存大小是 st_blksize * st_blocks关于文件系统存储方式可以阅读相关资料理解。 文件类型
Unix 中总共 7 种文件类型如下所示:
普通文件(regular file): 这是最常用的文件类型这种文件包含了某种形式的数据至于这个数据是文本还是二进制对于 Unix 内核而言并无区别。目录文件(directory file): 这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针。块特殊文件(block special file): 这种类型的文件提供对设备(如磁盘)带缓冲的访问每次访问以固定长度为单位进行。字符特殊文件(character special file): 这种类型的文件提供对设备不带缓冲的访问每次访问长度可变。FIFO: 这种类型的文件用于进程间通信有时也被称为命名管道。套接字(socket): 这种类型的文件用于进程间的网络通信也可用于一台宿主机上进程间的非网络通信。符号链接(symbolic link): 这种类型的文件指向另一个文件。
文件类型相关的信息保存在结构体 stat 的 st_mode 成员中st_mode 是一个 16 位的位图用于表示文件类型文件访问权限以及特殊权限位。它的类型是 mode_t其实就是普通的 unsigned int但是只是用了低 16 位如下图所示: POSIX 将掩码 S_IFMT 和 stat.st_mode 进行位与运算所对应的位称为文件类型为文件类型定义以下的掩码值:
#define S_IFMT 0170000 // bit mask for the file type bit field#define S_IFSOCK 0140000 // socket
#define S_IFLNK 0120000 // symbolic link
#define S_IFREG 0100000 // regular file
#define S_IFBLK 0060000 // block device
#define S_IFDIR 0040000 // directory
#define S_IFCHR 0020000 // character device
#define S_IFIFO 0010000 // FIFO因此可以使用以下方式判断文件类型:
stat(pathname, sb);
if ((sb.st_mode S_IFMT) S_IFREG) {/* Handle regular file */
}由于上述形式的测试很常见因此 POSIX 定义了额外的宏以便可以更简洁地编写 st_mode 中文件类型的测试定义的宏如下所示:
#dedfine S_ISREG(m) (((m) S_IFMT) S_IFREG) // is it a regular file?
#dedfine S_ISDIR(m) (((m) S_IFMT) S_IFDIR) // directory?
#dedfine S_ISCHR(m) (((m) S_IFMT) S_IFCHR) // character device?
#dedfine S_ISBLK(m) (((m) S_IFMT) S_IFBLK) // block device?
#dedfine S_ISFIFO(m) (((m) S_IFMT) S_IFIFO) // FIFO (named pipe)?
#dedfine S_ISLNK(m) (((m) S_IFMT) S_IFLNK) // symbolic link? (Not in POSIX.1-1996.)
#dedfine S_ISSOCK(m) (((m) S_IFMT) S_IFSOCK) // socket? (Not in POSIX.1-1996.)使用这些宏来判断文件类型的方式:
stat(pathname, sb);
if (S_ISREG(sb.st_mode)) {/* Handle regular file */
}获取文件类型
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/stat.h
#include sys/types.hstatic char file_type(const char *pathname);int main(int argc, char *argv[]) {if (2 ! argc) {fprintf(stderr, Usage: %s filename\n, argv[0]);exit(EXIT_FAILURE);}char ch file_type(argv[1]);switch (ch) {case -:printf(%s is regular file\n, argv[1]);break;case l:printf(%s is symbolic link\n, argv[1]);break;case d:printf(%s is directory\n, argv[1]);break;case c:printf(%s is character device\n, argv[1]);break;case b:printf(%s is block device\n, argv[1]);break;case p:printf(%s is FIFO\n, argv[1]);break;case s:printf(%s is socket\n, argv[1]);break;}return 0;
}static char file_type(const char *pathname) {struct stat stat_buf;if (-1 stat(pathname, stat_buf)) {perror(stat() error);exit(EXIT_FAILURE);}if (S_ISREG(stat_buf.st_mode))return -;else if (S_ISDIR(stat_buf.st_mode))return d;else if (S_ISCHR(stat_buf.st_mode))return c;else if (S_ISBLK(stat_buf.st_mode))return b;else if (S_ISLNK(stat_buf.st_mode))return l;else if (S_ISFIFO(stat_buf.st_mode))return p;else if (S_ISSOCK(stat_buf.st_mode))return s;
}文件访问权限
了解文件访问权限前先理解用户 ID 和组 ID。与一个进程相关联的 ID 有 6 个或更多如下所示 其中实际用户 ID 和实际组 ID 是在登录时取自口令文件中的登录项一般在登录会话期间不会改变这些值用来标识我们是谁。有效用户 ID、有效组 ID 和附属组 ID 决定了我们的访问权限跟我们设置的访问权限有关。保存的设置用户 ID 和保存的设置组 ID 是执行一个程序时包含了有效用户 ID 和有效组 ID 的副本。
通常来说有效用户 ID 就是实际用户 ID有效组 ID 就是实际组 ID。但是可以在文件模式字(st_mode)中设置一个特殊标志其含义是“当执行此文件时将进程的有效用户 ID 设置为文件所有者的用户 ID(st_uid)”。与此相类似在文件模式字中可以设置另一位它将执行此文件的进程的有效组 ID 设置为文件的组所有者 ID(st_gid)。在文件模式字中的这两位被称为设置用户 ID(set-user-ID)位和设置组 ID(set-group-ID)位。
所有文件类型都有访问权限(access permission)每个文件都有 9 个访问权限位并分成 3 个类别: 用户权限、组权限以及其他权限这些权限信息都位于 st_mode 中与上面的类似也有一些宏进行位“与”运算来判断文件的权限属性具体如下:
#define S_IRWXU 00700 // owner has read, write, and execute permission
#define S_IRUSR 00400 // owner has read permission
#define S_IWUSR 00200 // owner has write permission
#define S_IXUSR 00100 // owner has execute permission#define S_IRWXG 00070 // group has read, write, and execute permission
#define S_IRGRP 00040 // group has read permission
#define S_IWGRP 00020 // group has write permission
#define S_IXGRP 00010 // group has execute permission#define S_IRWXO 00007 // others (not in group) have read, write, and execute permission
#define S_IROTH 00004 // others have read permission
#define S_IWOTH 00002 // others have write permission
#define S_IXOTH 00001 // others have execute permission只有对目录有执行权限才能进入目录如果对目录只有读权限那么只能获得该目录所有文件名的列表对目录具有写权限则可以在目录中进行新建、删除、修改以及移动文件等权限。 对文件具有读权限可以决定我们能否打开文件进行读操作对文件具有写权限决定我们能否打开文件进行写操作。在 open 函数中对一个文件指定 O_TRUNC 标志必须对该文件具有写操作。 进程每打开、创建或删除一个文件时内核就进行文件访问权限测试而这种测试可能涉及文件的所有者(set_uid 和 set_gid)、进程的有效 ID以及进程的附属组 ID。两个所有者 ID 是文件的性质而两个有效 ID 和附属组 ID 则是进程的性质。内核进行测试具体如下
若进程的有效用户 ID 是 0(超级用户)则允许访问。这给予超级用户对整个文件系统进行处理的最充分的自由。若进程的有效用户 ID 等于文件的所有者 ID(也就是进程拥有此文件)那么如果所有者适当的访问权限位被设置则允许访问否则拒绝访问。适当的访问权限位指的是若进程为读而打开该文件则用户读位应为 1若进程为写而打开该文件则用户写位应为 1若进程将执行该文件则用户执行位应为 1。若进程的有效组 ID 或进程的附属组 ID 之一等于文件的组 ID那么如果组适当的访问权限被设置则允许访问否则拒绝访问。若其他用户适当的访问权限位被设置则允许访问否则拒绝访问。
函数 umask
umask 函数原型:
#include sys/types.h
#include sys/stat.hmode_t umask(mode_t mask);umask 函数为进程设置文件模式创建屏蔽字并返回之前的值。mask 通常是上述权限中的若干个按位“或”构成的。Unix 系统的大多数用户从不处理他们的 umask 值通常在登录时由 shell 的启动文件设置一次然后再不改变。
在进程创建一个新的文件或目录时就一定会使用文件模式创建屏蔽字如调用 open 函数创建一个新文件新文件的实际存取权限是 mode 与 umask 按照 mode ~umask 运算以后的结果。umask 函数用来修改进程的 umask作用是防止出现权限过松的文件。
文件权限更改和管理
文件的权限可以通过系统命令 chmod 进行修改而此命令是通过 chmod 系统函数实现函数原型如下:
#include sys/stat.hint chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);为了改变一个文件的权限位进程的有效用户 ID 必须等于文件的所有者 ID或者该进程必须具有超级用户权限。参数 mode 是下面的常量进行按位或 粘着位
在 Unix 尚未使用请求分页技术的早期版本中S_ISVTX 位被称为粘着位(sticky bit)。如果一个可执行程序文件的这一位被设置了那么当该程序第一次被执行在其终止时程序正文部分的一个副本仍被保存在交换区(程序的正文部分是机器指令)这使得下次执行该程序时能较快地将请其载入内存。其原因是: 通常的 Unix 文件系统中文件的各数据块很可能是随机存放的相比较而言交换区是被作为一个连续文件来处理的。对于通用的应用程序如文本编辑程序和 C 语言编译器我们常常设置它们所在文件的粘着位。自然地对于在交换区中可以同时存放的设置了粘着位的文件数是有限制的以免过多占用交换区空间但无论如何这是一个有用的技术。因为在系统再次自举前文件的正文部分总是在交换区中这正是名字中“粘着”的由来。后来的 UNIX 版本称它为保存正文位saved-text bit因此也就有了常量 S_ISVTX。现今较新的 UNIX 系统大多数都配置了虚拟存储系统以及快速文件系统所以不再需要使用这种技术。
现今的系统扩展了粘着位的使用范围Single UNIX Specification 允许针对目录设置粘着位。如果对一个目录设置了粘着位只有对该目录具有写权限的用户并且满足下列条件之一才能删除或重命名该目录下的文件:
拥有此文件拥有此目录是超级用户
目录 /tmp 和 /var/tmp 是设置粘着位的典型候选者 —— 任何用户都可在这两个目录中创建文件。任一用户用户、组和其他对这两个目录的权限通常都是读、写和执行。但是用户不应能删除或重命名属于其他人的文件为此在这两个目录的文件模式中都设置了粘着位。
文件长度
stat 结构成员 st_size 表示以字节位单位的文件的长度此字段只对普通文件、目录文件和符号链接有意义。对普通文件其文件长度可以是 0在开始读这种文件时将得到文件结束指示。对于目录文件长度通常是一个数的整倍数(如 512)对于符号链接文件长度是在文件名中的实际字节数。
文件中的空洞
在描述文件属性的结构体 stat 中有以下三个描述文件大小的成员:
struct stat {off_t st_size; /* 文件大小以字节为单位 */blksize_t st_blksize; /* 文件系统的 I/O 块大小 */blkcnt_t st_blocks; /* 块数 */
};其中块大小一般位 4096 字节即 4KB(一个块位连续 8 个扇区每个扇区位 512B)块数为该文件占用的块数。 st_size ! st_blksize * st_blocks或者说st_size 是文件的逻辑大小而 st_blksize * st_blocks 是文件的物理大小。
创建一个 5GB 大小的空洞文件:
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h#define FILENAME /tmp/out
#define FIVEG 5LL*1024LL*1024LL*1024LLint main() {int fd open(FILENAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);if (-1 fd) {perror(open() error);exit(EXIT_FAILURE);}lseek(fd, FIVEG-1, SEEK_SET);write(fd, , 1);close(fd);return 0;
}执行结果:
$ gcc creat_big_file.c
$ ./a.out
$ stat /tmp/outFile: /tmp/outSize: 5368709121 Blocks: 8 IO Block: 4096 regular file
Device: 820h/2080d Inode: 108418 Links: 1
Access: (0600/-rw-------) Uid: ( 1000/ junxu) Gid: ( 1000/ junxu)
Access: 2024-06-09 13:18:39.407738424 0800
Modify: 2024-06-09 23:01:34.667103130 0800
Change: 2024-06-09 23:01:34.667103130 0800Birth: -从输出结果可以看出大小为 5368709121B但是占用的块数却为 8即实际占用的物理大小为 4KB*832KB因此对该文件进行拷贝操作也会很快。cp 命令是支持空洞文件的拷贝操作拷贝空洞文件的流程: 首先判断读取的字符是否为空字符如果是则累加空字符的个数直到遇到非空字符直接调用 lseek 偏移空字符个数。
空洞文件的好处空洞文件对多线程共同操作文件是很有用的。因为我们在创建一个很大文件的时候我们就把一个文件分成很多的段然后采用多线程的方式让每个线程负责写入其中的某一段的数据。这样的话比我们用单个线程写入是快很多的。
例如
在使用迅雷下载文件时还未下载完成就发现该文件已经占据了全部文件大小的空间这也是空洞文件下载时如果没有空洞文件多线程下载时文件就只能从一个地方写入这就不能发挥多线程的作用了如果有了空洞文件可以从不同的地址同时写入就达到了多线程的优势在创建虚拟机时你给虚拟机分配了 100G 的磁盘空间但其实系统安装完成之后开始也不过只用了 3、4G 的磁盘空间如果一开始就把 100G 分配出去资源是很大的浪费。
文件截断
在文件末尾处阶段一些数据以缩短文件在打开文件时使用 O_TRUNC 标志可以做到这一点也可以利用系统函数做到如下:
#include unistd.h
#include sys/types.hint truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);这两个函数将一个现有文件长度截断为 length如果该文件以前的长度大于 length则超过 length 以外的数据就不能访问。如果以前的长度小于 length文件长度将增加在以前的文件尾端和新的文件尾端之间的数据将读作 0(也就是可能在文件中创建一个空洞)。
文件系统
磁盘结构 一个磁盘如一个 1T 的机械硬盘由多个盘片如下图中的 0 号盘片叠加而成。
盘片的表面涂有磁性物质这些磁性物质用来记录二进制数据。因为正反两面都可涂上磁性物质故一个盘片可能会有两个盘面。 每个盘片被划分为一个个磁道一个一个半径不同的同心圆环每个磁道又划分为一个个扇区磁道上的一个弧段。扇区是磁盘的最小组成单元通常是 512 字节。如下图 其中最内侧磁道上的扇区面积最小因此数据密度最大。
每个盘面对应一个磁头。所有的磁头都是连在同一个磁臂上的因此所有磁头只能“共进退”。所有盘面中半径相同的磁道组成柱面。如下图 磁盘容量的计算方式是: 存储容量 磁头数 * 磁道数 * 每道扇区数 * 每扇区字节数。
可用柱面号盘面号扇区号来定位任意一个“磁盘块”这里的“磁盘块”实质上就是一个扇区。
可根据该地址读取一个“块”操作如下
根据“柱面号”前后移动磁臂让磁头指向指定柱面旋转磁盘让磁头抵达待读的起始扇区。激活指定盘面对应的磁头旋转磁盘指定的扇区会从磁头下面划过这样就完成了对指定扇区的读/写。
磁盘块/簇虚拟出来的块是操作系统中最小的逻辑存储单位。操作系统与磁盘打交道的最小单位是磁盘块。在 Windows 下如 NTFS 等文件系统中叫做簇在 Linux 下如 Ext4 等文件系统中叫做块block。一般来说一个块block包含连续的 8 个扇区每个扇区 512B因此一个块大小为 4096KB。每个簇或者块可以包括 2、4、8、16、32、64…2 的 n 次方个扇区。
UFS
此处以传统的基于 BSD 的 Unix 文件系统(UFS)为例我们可以将一个磁盘分成一个或多个分区每个分区可以包含一个文件系统如下所示: i 节点即为 inode 结构体的数组数据块一般被分成了大小为 4KB 的块i 节点图是用来判断 inode 的空闲与占用情况块位图是用来判断数据块的占用和空闲情况
其中 i 节点是固定长度的记录项它包含有关文件的大部分信息仔细观察一个柱面组的 i 节点和数据块部分则可以看到下图示例: i 节点结构体中通常包含 15 个指针来指向数据块最后 3 个指针为一二三级指针用于扩充文件的大小图中的 i 节点指向了三个数据块。在图中有两个目录项两个不同文件名的文件但是 inode 编号相同指向同一个 i 节点此时称为硬链接即目录项就是硬链接的同义词。每个 i 节点中都有一个硬链接计数 st_nlink其值是指向该 i 节点的目录项数。只有当链接计数减少至 0 时才可删除该文件也就是可以释放该文件占用的数据块。i 节点包含了文件有关的所有信息∶文件类型、文件访问权限位、文件长度和指向文件数据块的指针等。stat 结构中的大多数信息都取自 i 节点。只有两项重要数据存放在目录项中∶文件名和 i 节点编号。 因为目录项中的 i 节点编号指向同一文件系统的相应 i 节点一个目录项不能指向另一个文件系统的 i 节点。这就是为什么 ln(1) 命令不能跨越文件系统的原因。 当在不更换文件系统的情况下为一个文件重命名时该文件的实际内容并未移动只需构造一个指向现有 i 节点的新目录项并删除老的目录项。链接计数不会改变。例如为将文件 /usr/lib/foo 重命名为 /usr/foo如果目录 /usr/lib 和 /usr 在同一文件系统中则文件 foo 的内容无需移动。这就是 mv(1) 命令的通常操作方式。 目录也是一种文件它的属性也需要 inode 结构体存储它的物理存储也需要通过 inode 中的指针来指向数据块来存储。目录块存储的内容非常的简单有目录项组成每个目录项有包含的文件名以及该文件名对应的 inode 编号如下图所示: 编号为 2549 的 i 节点testdir其类型字段 st_mode 表示它是一个目录因此它指向一个特殊的数据块——目录块链接计数为 2。任何一个叶目录不包含任何其他目录的目录的链接计数总是 2数值 2 来自于命名该目录testdir的目录项以及在该目录中的 . 项。编号为 1267 的 i 节点其类型字段 st_mode 表示它是一个目录链接计数大于或等于 3。它大于或等于 3 的原因是至少有 3 个目录项指向它: 一个是命名它的目录项第二个是在该目录中的 . 项第三个是在其子目录 testdir 中的 .. 项。注意在父目录中的每一个子目录都使该父目录的链接计数增加 1。
链接
硬链接
链接分为硬链接和符号链接创建链接的命令如下:
ln src dest # 创建 src 的硬链接为 dest
ln -s src dest # 创建 src 的符号链接为 dest硬链接与目录项是同义词建立硬链接有限制不能给分区和目录建立符号链接可以给分区和目录建立。 链接相关的操作函数如下:
#include unistd.hint link(const char *oldpath, const char *newpath);
int unlink(const char *pathname);unlink 函数删除目录项并将由 pathname 所引用文件的链接计数减 1。如果对该文件还有其他链接则仍可通过其他链接访问该文件的数据。如果出错则不对该文件做任何更改。解除对文件的链接必须具备下面三个条件
拥有该文件拥有该目录具有超级用权限
只有当链接计数达到 0 时该我呢见的内容才可被删除。另一个条件也会阻止删除文件的内容——只要有进程打开了该文件其内容也不能删除。关闭一个文件时内核首先检查打开该文件的进程个数如果这个计数到达 0内核再去检查其链接计数如果计数也是 0那么就删除该文件的内容。
unlink 的这种特性经常被用来确保即使是在程序崩溃时它所创建的临时文件也不会遗留下来。进程 open 或 creat 创建一个文件然后立即调用 unlink因为该文件仍旧是打开的所以不会将其内容删除。只有当进程关闭该文件或终止式该文件的内容才被删除。如果 pathname 时符号链接那么 unlink 删除该符号链接而不是删除由该链接所引用的文件。
除此之外我们还可以调用 remove 函数解除对一个文件或目录的链接。对文件remove 的功能与 unlink 相同。对于目录remove 的功能与 rmdir 相同。
#include stdio.hint remove(const char *pathname);符号链接
符号链接式对一个文件的间接指针它与上面的硬链接有所不同硬链接直接指向文件的 i 节点。引入符号链接的原因是为了避开硬链接的一些限制
硬链接通常要求链接和文件位于同一文件系统中只有超级用户才能创建指向目录的硬链接
对符号链接以及它指向何种对象并无任何文件系统限制任何用户都可以创建指向目录的符号链接。符号链接一般用于将一个文件或整个目录结构移到系统中另一个位置。
之前使用的系统调用参数如果使用的是符号链接则会直接随此链接到达所指定的文件。如果此文件不存在就会出错。创建和读取符号链接的函数原型如下
#include unistd.h// 成功返回 0失败返回 -1
int symlink(const char *target, const char *linkpath);// 成功将数据保存到 buf 中失败返回 -1
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);文件的时间
修改时间(st_mtim)和状态更改的时间(st_ctim)之间的区别修改时间是文件内容最后一次被修改的时间。状态更改时间是该文件的 i 节点最后一次被修改的时间影响 i 节点的操作有很多如更改文件的访问权限、更改用户 ID、更改链接计数等但它没有更改文件的实际内容。因为 i 节点中的所有信息和文件的实际内容是分开存放的所以除了要记录文件数据修改时间以外还要记录状态更改时间。
可以使用 utime 来更改时间
#include sys/types.h
#include utime.hint utime(const char *filename, const struct utimbuf *times);目录操作
目录的创建和销毁
Linux 中创建目录的命令 mkdir 和删除目录的命令 rmdir 是基于下面的函数实现的。
#include sys/stat.h
#include sys/types.hint mkdir(const char *pathname, mode_t mode);#include unistd.hint rmdir(const char *pathname);mkdir 是创建一个新的空目录其中.. 和 . 目录是自动创建的所指定的文件访问权限 mode 由进程的文件模式创建屏蔽字修改。
常见的错误是指定与文件相同的 mode(只指定读、写权限)但是对于目录通常至少要设置一个执行权限位以允许访问该目录种的文件名。
如果调用此函数是目录的链接计数成为 0并且没有其他进程打开此目录则释放由此目录占用的空间。如果在链接计数达到 0 时有一个或多个进程打开此目录则在此函数返回前删除最后一个链接及 . 和 .. 项。另外在此目录中不能在创建新文件。但是在最后一个进程关闭它之前并不释放此目录(即使另一些进程打开该目录它们在此目录下也能执行其他操作。这样处理的原因时为了使 rmdir 函数成功执行该目录必是虚空的)。
读目录
对某个目录具有访问权限的任一用户都可以读该目录但是为了防止文件系统产生混乱只有内核才能写目录。一个目录的写权限位和执行权限位决定了在该目录中能否创建新文件以及删除文件它们并不表示能否写目录本身。在 Linux 中读取目录数据如同读取文件一样将其当作一个流操作目录的相关函数如下
// 打开目录
#include sys/types.h
#include dirent.hDIR *opendir(const char *name);
DIR *fdopendir(int fd);// 读取目录
#include dirent.hstruct dirent *readdir(DIR *dirp);// 目录定位流
#include sys/types.h
#include dirent.hlong telldir(DIR *dirp);
void seekdir(DIR *dirp, long loc);
void rewinddir(DIR *dirp);// 关闭目录
#include sys/types.h
#include dirent.hint closedir(DIR *dirp);如果文件读写操作目录操作中 DIR 结构会贯穿始终其中 DIR 结构的具体形式如下:
struct dirent {ino_t d_ino; /* Inode number */off_t d_off; /* Not an offset; see below */unsigned short d_reclen; /* Length of this record */unsigned char d_type; /* Type of file; not supportedby all filesystem types */char d_name[256]; /* Null-terminated filename */
};使用目录操作函数实现读取目录程序
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include dirent.h#define BUFFERSIZE 4096static int is_directory(const char *pathname);
static void read_directory(const char *pathname);int main(int argc, char *argv[]) {if (2 ! argc) {fprintf(stderr, Usage: %s dirname\n, argv[0]);exit(EXIT_FAILURE);}// 如果不是目录则直接退出if (-1 is_directory(argv[1])) {fprintf(stderr, %s isnt directory\n, argv[1]);exit(EXIT_FAILURE);}// 开始递归遍历目录read_directory(argv[1]);return 0;
}static int is_directory(const char *pathname) {struct stat stat_buf;if (-1 stat(pathname, stat_buf)) {perror(stat() error);return -1;}if (S_ISDIR(stat_buf.st_mode))return 0;elsereturn -1;
}static void read_directory(const char *pathname) {// 先打开目录DIR *dir opendir(pathname);if (NULL dir) {perror(opendir() error);exit(EXIT_FAILURE);}// 开始读取目录struct dirent *rbuf NULL;while (NULL ! (rbuf readdir(dir))) {// 记录此时的完整路径char path[BUFFERSIZE] {0};strncat(path, pathname, strlen(pathname));strcat(path, /);strncat(path, rbuf-d_name, strlen(rbuf-d_name));// 如果此路径是目录开始递归if (0 is_directory(path)) {//如果是 . 或 .. 则跳过if (0 strcmp(rbuf-d_name, .) || 0 strcmp(rbuf-d_name, ..))continue;read_directory(path);} else {// 不是目录则打印路径puts(path);}}closedir(dir);
}函数 chdir、fchdir 和 getcwd
每个进程都有一个当前工作目录此目录是搜索所有相对路径名的起点。当用户登录到 Unix 系统时其当前工作目录通常是口令文件(/etc/passwd)中该用户登录项的第 6 字段——用户的起始目录。当前工作目录是进程的一个属性起始目录则是登录名的一个属性。
#include unistd.hint chdir(const char *path);
int fchdir(int fd);因为当前工作目录是进程的一个属性所以它只影响调用 chdir 的进程本身而不影响其他进程。当我们执行一个程序时会在一个独立的进程中运行不会改变 shell 的当前工作目录。
pwd 命令可以获取当前工作目录的绝对路径其实现函数是 getcwd
#include unistd.hchar *getcwd(char *buf, size_t size);函数 glob
当我们通过 main 函数获取命令行参数时命令行参数中的通配符是如何处理的如下程序所示:
#include stdio.hint main(int argc, char const *argv[]) {printf(argc %d\n, argc);for (int i 0; i argc; i)puts(argv[i]);return 0;
}输入如下的命令运行此程序:
$ ./a.out /etc/a*.conf最后的输出结果如下:
argc 4
./a.out
/etc/adduser.conf
/etc/apg.conf
/etc/appstream.conf命令行参数中如果出现模式匹配也会被拆解成一个一个单独的参数。glob 函数在 Unix 系统中用于路径名模式匹配能够根据指定的模式搜索匹配的文件名。它使用起来相对简单并且提供了多种控制匹配行为的标志适合在 C 语言程序中进行文件名的通配符匹配。
#include glob.hint glob(const char *pattern, int flags,int (*errfunc) (const char *epath, int eerrno),glob_t *pglob);
void globfree(glob_t *pglob);pattern要匹配的路径名模式例如 “*.c” 匹配所有 .c 文件。flags控制匹配行为的标志常见的标志包括 GLOB_ERR遇到读取错误时停止。GLOB_MARK在目录名后面加上斜杠 /。GLOB_NOSORT不对结果进行排序。GLOB_NOCHECK如果没有匹配到结果就返回模式自身。GLOB_APPEND将结果追加到现有的 pglob 结构中。GLOB_NOESCAPE反斜杠字符不转义。GLOB_PERIOD允许模式中的前导点匹配文件名中的前导点。 errfunc指向一个错误处理函数的指针如果为 NULL 则忽略错误。pglob指向 glob_t 结构的指针用于存储匹配的结果。
glob_t 结构的具体形式为
typedef struct {size_t gl_pathc; /* Count of paths matched so far */char **gl_pathv; /* List of matched pathnames. */size_t gl_offs; /* Slots to reserve in gl_pathv. */
} glob_t;使用 glob 实现获取 /etc 目录下的所有 .conf 文件:
#include glob.h
#include stdio.h
#include stdlib.h#define PATTERN /etc/a*.confint main(int argc, const char *argv[]) {glob_t pattern_glob;glob(PATTERN, 0, NULL, pattern_glob);for (int i 0; i pattern_glob.gl_pathc; i) puts(pattern_glob.gl_pathv[i]);globfree(pattern_glob);return 0;
}使用目录操作函数实现 mydu 程序
使用目录操作函数:
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include dirent.h#define BUFFERSIZE 4096
#define CURPATH .static blkcnt_t mydu(const char *pathname);
static int is_directory(const char *pathname);int main(int argc, const char *argv[]) {if (1 argc) {printf(%-6ld %s\n, mydu(CURPATH)/2, CURPATH);} else {for (int i 1; i argc; i)printf(%-6ld %s\n, mydu(argv[i])/2, argv[i]);}return 0;
}static blkcnt_t mydu(const char *pathname) {// 首先判断此文件的类型如果是文件则直接显示如果是目录则递归分析struct stat stat_buf;if (-1 lstat(pathname, stat_buf)) {perror(stat() error);return -1;}// 如果不是目录则返回其块大小if (!S_ISDIR(stat_buf.st_mode))return stat_buf.st_blocks;// 如果是目录则递归读取目录DIR *dir opendir(pathname);if (NULL dir) {perror(opendir error);exit(EXIT_FAILURE);}// 首先记录此目录的块大小blkcnt_t total_blocks stat_buf.st_blocks;char path[BUFFERSIZE] {0};// 递归计算目录中每个文件和子目录的大小struct dirent *dir_buf NULL;while (NULL ! (dir_buf readdir(dir))) {memset(path, 0, BUFFERSIZE);if (0 strcmp(dir_buf-d_name, .) || 0 strcmp(dir_buf-d_name, ..))continue;// 路径拼接开始递归strncpy(path, pathname, BUFFERSIZE);strcat(path, /);strncat(path, dir_buf-d_name, strlen(dir_buf-d_name));total_blocks mydu(path);}closedir(dir);printf(%-6ld %s\n, total_blocks/2, pathname);return total_blocks;
}使用 glob 函数
#include fcntl.h
#include glob.h
#include stdbool.h
#include stdio.h
#include stdlib.h
#include string.h
#include sys/stat.h
#include sys/types.h
#include unistd.h#define BUFFERSIZE 4096
#define CURPATH .static blkcnt_t mydu(const char *pathname);
static int is_directory(const char *pathname);
bool is_hidedir(const char *pathname);int main(int argc, const char *argv[]) {if (1 argc) {printf(%-6ld %s\n, mydu(CURPATH)/2, CURPATH);} else {for (int i 1; i argc; i)printf(%-6ld %s\n, mydu(argv[i])/2, argv[i]);}return 0;
}static blkcnt_t mydu(const char *pathname) {// 首先判断此文件的类型如果是文件则直接显示如果是目录则递归分析struct stat stat_buf;if (-1 lstat(pathname, stat_buf)) {perror(stat() error);return -1;}// 如果不是目录则返回其块大小if (!S_ISDIR(stat_buf.st_mode))return stat_buf.st_blocks;// 如果是目录则在文件名后追加 /* 通配符char pattern[BUFFERSIZE] {0};strncpy(pattern, pathname, BUFFERSIZE);strncat(pattern, /*, sizeof(/*));glob_t glob_buf;if (0 ! glob(pattern, 0, NULL, glob_buf)) {perror(glob() error);exit(EXIT_FAILURE);}// 递归所有的隐藏文件char hide_pattern[BUFFERSIZE];strncpy(hide_pattern, pathname, BUFFERSIZE);strncat(hide_pattern, /.*, sizeof(/.*));if (glob(hide_pattern, GLOB_APPEND, NULL, glob_buf) ! 0) {perror(glob() error);exit(EXIT_FAILURE);}// 首先记录此目录的块大小blkcnt_t total_blocks stat_buf.st_blocks;// 递归计算目录种每个文件和子目录的大小for (int i 0; i glob_buf.gl_pathc; i) {if (!is_hidedir(glob_buf.gl_pathv[i]))total_blocks mydu(glob_buf.gl_pathv[i]);}globfree(glob_buf);printf(%-6ld %s\n, total_blocks/2, pathname);return total_blocks;
}bool is_hidedir(const char *pathname) {// 取出最后一个 / 后的字符char *str strrchr(pathname, /);if (NULL str)exit(EXIT_FAILURE);// 判断文件是否为 . 或 ..if (strcmp(str1, .) 0 || strcmp(str1, ..) 0)return true;elsereturn false;
}分享一个学习资料仓库