linux中各种文件类型
在 Linux 系统中,文件类型决定了文件的性质和用途。内核通过文件类型管理不同的资源访问方式,
常见的文件类型包括:
普通文件(Regular File)
定义:存储数据的常规文件,分为文本文件和二进制文件。
特点:可读写,具有确定的文件大小,支持随机访问。
示例:.txt文本文件、.c源代码文件、.so共享库文件。
文件描述符操作:通过open函数获取文件描述符后,使用read/write进行读写,与其他文件类型的操作方式一致。
目录文件(Directory)
定义:用于组织文件系统层级结构的特殊文件。
特点:包含其他文件和子目录的元数据,不可直接写入数据。
示例:/home、/etc等目录。
文件描述符操作:通过opendir函数打开目录获取DIR指针(非文件描述符),使用readdir遍历条目。若需对目录本身操作(如创建 / 删除),需通过mkdir/rmdir等系统调用。
符号链接(Symbolic Link)
定义:指向其他文件或目录的快捷方式。
特点:内容为目标路径字符串,大小通常为目标路径长度。
示例:ln -s target.txt link.txt创建的链接文件。
文件描述符操作:通过open打开符号链接时,内核自动解析为目标文件的文件描述符。若需操作符号链接本身(如删除),需使用unlink函数。
设备文件(Device File)
定义:代表硬件设备的文件,分为块设备和字符设备。
特点:
块设备:以块为单位读写(如磁盘),支持随机访问。
字符设备:按字符流读写(如串口),通常不支持随机访问。
示例:/dev/sda(磁盘)、/dev/tty(终端)。
文件描述符操作:通过open获取文件描述符后,使用read/write直接与硬件交互。例如,向/dev/null写入数据会被丢弃。
套接字文件(Socket)
定义:用于进程间通信(IPC)或网络通信的特殊文件。
特点:不存储数据,仅作为通信端点。
示例:网络套接字(AF_INET)、本地套接字(AF_UNIX)。
文件描述符操作:通过socket函数创建套接字文件描述符,使用bind、listen、accept等函数进行网络编程。
命名管道(FIFO)
定义:进程间通信的管道文件,支持单向数据流。
特点:数据先进先出,仅在内存中存在。
示例:mkfifo myfifo创建的管道文件。
文件描述符操作:通过open打开 FIFO 文件描述符后,可像普通文件一样读写,但需注意阻塞行为(默认情况下,读操作会等待写端打开)。
识别文件类型的方法
命令行工具:
ls -l:通过第一个字符识别类型(如-为普通文件,d为目录,l为符号链接)。
file:自动检测文件类型(如file test.txt显示 “ASCII text”)。
编程方法:
使用stat系统调用获取文件元数据,通过st_mode字段判断类型:
#include <sys/stat.h>
#include <stdio.h>
int main() {
struct stat file_info;
if (stat("test.txt", &file_info) == -1) {
perror("stat");
return 1;
}
if (S_ISREG(file_info.st_mode)) {
printf("普通文件\n");
} else if (S_ISDIR(file_info.st_mode)) {
printf("目录\n");
}
return 0;
}
权限管理基础
Linux 文件权限针对三类用户角色:
属主(Owner):文件的所有者。
属组(Group):文件所属的用户组。
其他用户(Others):不属于上述两类的用户。
每个角色拥有三种基本权限:
读(r):允许查看文件内容(对目录为列出文件列表)。
写(w):允许修改或删除文件内容(对目录为创建、移动或删除文件)。
执行(x):允许运行程序或进入目录(对目录为使用cd命令进入)。
权限信息通过ls -l命令查看,以-rwxr-xr–为例:
第一位-表示普通文件(d为目录,l为符号链接)。
后续九位分为三组,依次对应属主、属组、其他用户的权限。
文件与目录的权限差异
文件
默认权限:创建时为rw-(属主可读可写,其他无权限)。
执行权限:仅对二进制文件或脚本有效,允许内核加载并执行。
目录
默认权限:创建时为rwx(所有用户可读、写、执行)。
特殊逻辑:
读权限:允许列出目录内容,但无法查看文件详细信息(如ls -l)。
写权限:允许创建、删除文件,但删除操作实际依赖文件的属主权限。
执行权限:允许进入目录(cd)或查看文件详细信息。
权限修改命令
chmod:修改文件或目录权限
chmod 640 file.txt # 属主rw(6)、属组r(4)、其他无(0)
数字模式
通过八进制数字组合设置权限(r=4, w=2, x=1)
chmod u+w,g-x file.txt # 属主添加写权限,属组移除执行权限
chmod a=rwx dir/ # 所有用户赋予读写执行权限
字母模式(角色 + 操作符)
使用u(属主)、g(属组)、o(其他)、a(所有)指定目标角色,配合+(添加)、-(移除)、=(覆盖)操作
chmod -R 755 project/ # 目录属主rwx,属组和其他r-x
递归修改目录
使用-R选项递归处理子目录及文件
chown:修改属主和属组
chown user file.txt # 修改属主为user
chown user:group file.txt # 同时修改属主和属组
chown -R root:admin logs/ # 递归修改目录及其内容的属主和属组
chgrp:修改属组
chgrp developers code.py # 修改属组为developers
高级技巧与注意事项
特殊权限:
Setuid(s):可执行文件运行时临时拥有属主权限(如/usr/bin/passwd)。
Setgid(s):目录中创建的文件继承目录属组。
Sticky Bit(t):仅允许文件属主删除文件(如/tmp目录)。
默认权限掩码(umask):
用户创建文件时,实际权限为666 - umask,目录为777 - umask。
查看当前掩码:umask(默认值通常为0022)。
参考文件权限:
使用–reference选项复制其他文件的权限:
chmod --reference=template.txt target.txt
检测文件权限(access 函数)
access函数用于检查调用进程是否具有对文件的特定访问权限(如读、写或执行)。其函数原型为:
#include <unistd.h>
int access(const char *pathname, int mode);
pathname:文件路径。
mode:权限检查类型,可组合以下标志:
F_OK:检查文件是否存在。
R_OK:检查是否可读。
W_OK:检查是否可写。
X_OK:检查是否可执行。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
const char *file = "data.txt";
// 检查文件是否存在
if (access(file, F_OK) == -1) {
perror("文件不存在");
exit(EXIT_FAILURE);
}
// 检查是否有读权限
if (access(file, R_OK) == 0) {
printf("可读\n");
}
// 检查是否有写权限
if (access(file, W_OK) == 0) {
printf("可写\n");
}
return 0;
}
access返回 0 表示检查通过,返回 - 1 表示权限不足或文件不存在。
需结合errno判断具体错误原因(如ENOENT表示文件不存在)。
基于实际用户 ID:access的权限判断基于进程的实际用户 ID(Real UID),而非有效用户 ID(Effective UID)。这与open等函数的行为不同(后者基于有效用户 ID)。
chmod 函数
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
const char *filename = "test.txt";
mode_t new_mode = 0644; // 属主 rw-, 属组 r--, 其他 r--
// 修改文件权限
if (chmod(filename, new_mode) == -1) {
perror("chmod");
exit(EXIT_FAILURE);
}
printf("文件权限已修改为 %o\n", new_mode);
return 0;
}
头文件:
sys/stat.h:包含 chmod 和 mode_t 的定义。
stdio.h、stdlib.h、string.h:用于输入输出和错误处理。
函数参数:
filename:要修改权限的文件路径。
new_mode:新权限的八进制表示(如 0644 对应 rw-r–r–)。
错误处理:
chmod 返回 -1 表示失败,通过 perror 打印错误信息。
chmodat
#include <fcntl.h>
#include <unistd.h>
int dir_fd = open("/path/to/dir", O_RDONLY);
if (dir_fd == -1) { /* 错误处理 */ }
// 修改目录内文件的权限(相对于 dir_fd)
if (chmodat(dir_fd, "file.txt", 0644, 0) == -1) {
perror("chmodat");
}
close(dir_fd);
权限掩码(umask):
新权限会受当前进程 umask 的影响(实际权限 = new_mode & ~umask)。
例如:若 umask 为 0022,设置 0666 会实际生效为 0644。
特殊权限:
可通过 | 运算符设置特殊权限:
mode_t mode = 0755 | S_ISUID; // 添加 Setuid 权限(rwsr-xr-x)
符号链接处理:
chmod 默认修改符号链接指向的目标文件权限。
若需直接修改符号链接本身的权限,需结合 fchmodat 和 AT_SYMLINK_NOFOLLOW 标志。
fchmodat
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
const char *link_path = "symlink.txt";
const char *target_path = "target.txt";
// 创建符号链接
if (symlink(target_path, link_path) == -1) {
perror("symlink");
exit(EXIT_FAILURE);
}
// 修改符号链接本身的权限(不跟随目标)
if (fchmodat(AT_FDCWD, link_path, 0777, AT_SYMLINK_NOFOLLOW) == -1) {
perror("fchmodat");
exit(EXIT_FAILURE);
}
printf("符号链接权限已设置为 777\n");
return 0;
}
使用 chmod 函数可方便地修改文件权限,需注意 umask 和特殊权限的影响。
- 对于符号链接或复杂路径操作,优先使用 chmodat 或 fchmodat 以增强控制。
- 始终进行错误检查,确保权限修改成功。
umask 介绍
umask 是 Linux 系统中用于控制新创建文件和目录默认权限的掩码。它决定了在创建文件或目录时,从默认权限中去除哪些权限位。umask 的值是一个八进制数,与文件和目录的权限位相对应。
umask 的工作机制
当用户创建一个文件或目录时,系统会先根据默认的权限模式(文件通常是 666,目录通常是 777),然后与 umask 进行按位取反的 “与” 操作,得到最终的实际权限。例如,umask 的值为 022,对于文件来说:
默认权限 666(八进制)转换为二进制是 110 110 110。
umask 022 转换为二进制是 000 010 010,取反后为 111 101 101。
进行按位 “与” 操作:(110 110 110) & (111 101 101) = 110 100 100,转换回八进制就是 644,这就是新创建文件的实际权限。对于目录,默认权限 777(111 111 111)与 umask 取反按位 “与” 后得到 755(111 101 101)。
目录文件
opendir:打开目录并返回DIR指针。
#include <dirent.h>
DIR *opendir(const char *dirname);
成功返回DIR指针,失败返回NULL。
readdir:读取目录条目
struct dirent *readdir(DIR *dirp);
返回struct dirent结构体指针,包含文件名和文件类型等信息。
遍历结束或出错时返回NULL。
closedir:关闭目录流。
int closedir(DIR *dirp);
遍历目录内容
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
int main() {
DIR *dir;
struct dirent *entry;
const char *dirname = ".";
// 打开目录
dir = opendir(dirname);
if (dir == NULL) {
perror("opendir");
exit(EXIT_FAILURE);
}
// 遍历目录条目
while ((entry = readdir(dir)) != NULL) {
printf("文件名: %s\n", entry->d_name);
printf("文件类型: ");
switch (entry->d_type) {
case DT_REG: printf("普通文件\n"); break;
case DT_DIR: printf("目录\n"); break;
case DT_LNK: printf("符号链接\n"); break;
default: printf("未知类型\n");
}
}
// 关闭目录
closedir(dir);
return 0;
}
过滤隐藏文件
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') // 跳过隐藏文件
continue;
// 处理可见文件
}
获取详细文件信息
结合stat函数获取文件属性(如大小、修改时间):
struct stat file_stat;
char path[256];
while ((entry = readdir(dir)) != NULL) {
snprintf(path, sizeof(path), "%s/%s", dirname, entry->d_name);
if (stat(path, &file_stat) == -1) {
perror("stat");
continue;
}
printf("文件大小: %ld bytes\n", file_stat.st_size);
}
scandir:按名称排序并过滤条目。
#include <dirent.h>
int scandir(const char *dir, struct dirent ***namelist,
int (*filter)(const struct dirent *),
int (*compar)(const struct dirent **, const struct dirent **));
ftw:递归遍历目录树。
#include <ftw.h>
int ftw(const char *dir, int (*fn)(const char *, const struct stat *, int), int nopenfd);