
Linux系统中我们经常习惯于直接使用shell命令进行文件目录操作,但是对于我们的程序来说,有时候并不能像我们使用shell命令一样方便,这时候就需要使用Linux系统的系统调用和自带库函数对文件目录进行操作。
system() or 系统调用?
当然也有人可能会说Linux不是有system函数吗,可以直接使用system(cmd)函数传入shell命令呀,这样不是更方便吗
那么这里就先解释一下二者的区别在哪里
system()函数会创建一个shell子进程,然后再通过这个shell子进程解析执行命令,而系统调用或是库函数是直接对于内核或标准库进行调用,并没有进程创建产生的开销,性能更高,并且有着较高的移植性,在资源受限的嵌入式系统中,还是推荐使用系统调用或库函数操作
| 维度 | 系统调用/库函数(如 mkdir, open) | system("shell command") |
|---|---|---|
| 执行方式 | 直接调用内核或 C 标准库函数 | 启动一个子 shell(如 /bin/sh),再由 shell 解析并执行命令 |
| 性能 | 高(无进程创建开销) | 低(fork + exec + shell 解析) |
| 安全性 | 高(可控输入) | 低(易受命令注入攻击) |
| 可移植性 | POSIX 标准,跨 Unix/Linux 一致 | 依赖 shell 和命令是否存在(如 rm、mkdir -p) |
| 错误处理 | 精确(通过返回值/errno) | 模糊(只能获取 shell 退出码) |
| 功能范围 | 仅限系统 API 提供的功能 | 可执行任意 shell 命令(如管道、重定向) |
头文件引用
一般使用这系列系统调用和库函数都需要包含以下头文件
#include <sys/stat.h> // mkdir, stat, chmod, etc.
#include <unistd.h> // rmdir, chdir, unlink, etc.
#include <dirent.h> // opendir, readdir
#include <fcntl.h> // open flags
#include <ftw.h> // nftw (file tree walk)
文件目录读取
有时候我们会需要对一个文件目录进行解析,与直观的使用cd和ls不同,我们需要几个结构体以及函数对文件目录进行解析
stat
stat 是 Linux/Unix 系统中的一个系统调用(system call),用于获取文件或文件系统的信息(metadata),比如文件大小、权限、所有者、创建/修改时间等。
示例
#include <sys/stat.h>
#include <unistd.h>
struct stat st;
if (stat("filename.txt", &st) == 0) {
// 成功获取文件信息
printf("File size: %ld bytes\n", st.st_size);
printf("Permissions: %o\n", st.st_mode & 0777);
} else {
perror("stat failed");
}
stat()的第一个参数是文件路径(可以是相对或绝对路径)。- 第二个参数是一个
struct stat类型的指针,用于接收文件信息。 - 返回值:成功返回
0,失败返回-1。
struct stat 中常用字段
| 字段 | 含义 |
|---|---|
| st_size | 文件字节大小(非常重要!常用于读取整个文件) |
| st_mode | 文件类型和权限(如是否是目录、普通文件、权限位等) |
| st_mtime | 最后修改时间(可用于缓存判断) |
st_uid/st_gid | 所有者用户 ID / 组 ID |
ftat
fstat()(注意是 fstat),它是 stat() 的变体
stat(path, &buf)→ 通过文件路径获取信息。fstat(fd, &buf)→ 通过已打开的文件描述符(file descriptor) 获取信息。
示例
FILE *fp = fopen(argv[1],"r");
if(fstat(fileno(fp),&st) == 0)
{
printf("[fstat]get file info succ\r\n");
printf("file size:%d\r\n",st.st_size);
printf("permisson: %o\r\n",st.st_mode & 0777);
}
fileno() 可以拿到其底层的文件描述符(int fd)
fileno
fileno() 可以拿到其底层的文件描述符(int fd)
路径函数
DIR结构体
struct __dirstream
{
void *__fd;
char *__data;
int __entry_data;
char *__ptr;
int __entry_ptr;
size_t __allocation;
size_t __size;
__libc_lock_define (, __lock)
};
typedef struct __dirstream DIR;
DIR结构体类似于FILE,是一个内部数据结构,函数 DIR *opendir(const char *pathname),即打开文件目录,返回的就是指向DIR结构体的指针,而该指针由以下几个函数使用:
1 struct dirent *readdir(DIR *dp);
2 void rewinddir(DIR *dp);
3 int closedir(DIR *dp);
4 long telldir(DIR *dp);
5 void seekdir(DIR *dp,long loc);
dirent结构体
目录文件(directory file)的概念:这种文件包含了其他文件的名字以及指向与这些文件有关的信息的指针。从定义能够看出,dirent不仅仅指向目录,还指向目录中的具体文件,readdir函数同样也读取目录下的文件
struct dirent
{
long d_ino; /* inode number 索引节点号 */
off_t d_off; /* offset to this dirent 在目录文件中的偏移 */
unsigned short d_reclen; /* length of this d_name 文件名长 */
unsigned char d_type; /* the type of d_name 文件类型 */
char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */
}
从上述定义也能够看出来,dirent结构体存储的关于文件的信息很少,所以dirent同样也是起着一个索引的作用,如果想获得类似ls -l那种效果的文件信息,必须要靠stat函数了
示例
//显示指定路径下所有文件
int show_path(const char* path)
{
printf("entry the path:%s\r\n",path);
DIR *dir = opendir(path);
if(!dir)
return -1;
struct dirent *entry;
while(entry = readdir(dir))
{
if(strcmp(entry->d_name,".") == 0 || strcmp(entry->d_name,"..") == 0)
continue;
char full_path[1024];
snprintf(full_path,sizeof(full_path),"%s/%s",path,entry->d_name);
struct stat st;
if(stat(full_path,&st) == 0)
{
if(S_ISDIR(st.st_mode))
{
printf("[dir] %s %s\r\n",path,entry->d_name);
show_path(full_path);
}
else if(S_ISREG(st.st_mode))
{
printf("[file] %s/%s (%ld)bytes\r\n",path,entry->d_name,st.st_size);
}
}
}
closedir(dir);
return 0;
}
文件目录创建
创建文件目录也是一个常用操作,特别是我们需要建立日志系统时,通常使用mkdir创建函数
int mkdir(const char *pathname, mode_t mode);
- 第一个参数为文件路径名
- 第二个参数是可选的模式,可选如下:S_IRWXU00700权限,代表该文件所有者拥有读,写和执行操作的权限S_IRUSR(S_IREAD)00400权限,代表该文件所有者拥有可读的权限S_IWUSR(S_IWRITE)00200权限,代表该文件所有者拥有可写的权限S_IXUSR(S_IEXEC)00100权限,代表该文件所有者拥有执行的权限S_IRWXG00070权限,代表该文件用户组拥有读,写和执行操作的权限S_IRGRP00040权限,代表该文件用户组拥有可读的权限S_IWGRP00020权限,代表该文件用户组拥有可写的权限S_IXGRP00010权限,代表该文件用户组拥有执行的权限S_IRWXO00007权限,代表其他用户拥有读,写和执行操作的权限S_IROTH00004权限,代表其他用户拥有可读的权限S_IWOTH00002权限,代表其他用户拥有可写的权限S_IXOTH00001权限,代表其他用户拥有执行的权限
由于标准库没有mkdir -p 或 rm -rf,所以需要我们自己实现创建和删除多级目录
示例:递归创建和删除多级目录
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
int mkdir_p(const char *path, mode_t mode) {
char tmp[256];
strncpy(tmp, path, sizeof(tmp) - 1);
for (char *p = tmp + 1; *p; p++) {
if (*p == '/') {
*p = '\0';
if (mkdir(tmp, mode) != 0 && errno != EEXIST) return -1;
*p = '/';
}
}
return mkdir(tmp, mode) == 0 || errno == EEXIST ? 0 : -1;
}
#include <ftw.h>
// 使用 nftw() 可以方便地递归删除目录
int rmrf(const char *path) {
return nftw(path, [](const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
return remove(fpath);
}, 64, FTW_DEPTH | FTW_PHYS);
}
各种文件操作概览
1 . 基本文件操作
| 操作 | 函数 | 说明 |
|---|---|---|
| 创建/打开文件 | open(const char *pathname, int flags, mode_t mode) | 系统调用,返回文件描述符 |
| 关闭文件 | close(int fd) | 关闭文件描述符 |
| 读/写文件 | read(), write() | 基于文件描述符的读写 |
| 删除文件 | unlink(const char *pathname) | 删除文件(对硬链接计数减1) |
| 重命名/移动文件 | rename() | 同上,也适用于文件 |
| 截断文件 | truncate(const char *path, off_t length) 或 ftruncate(int fd, off_t length) | 改变文件大小 |
2. 文件属性操作
| 操作 | 函数 | 说明 |
|---|---|---|
| 获取文件状态 | stat(), lstat(), fstat() | 填充 struct stat |
| 修改文件权限 | chmod(const char *path, mode_t mode) 或 fchmod(int fd, mode_t mode) | |
| 修改文件所有者 | chown(const char *path, uid_t owner, gid_t group) | |
| 修改访问/修改时间 | utime(), utimes(), futimens()(POSIX) |
3. 高级操作
| 操作 | 函数 |
|---|---|
| 创建硬链接 | link(const char *oldpath, const char *newpath) |
| 创建符号链接 | symlink(const char *target, const char *linkpath) |
| 读取符号链接目标 | readlink(const char *path, char *buf, size_t bufsiz) |
| 检查文件是否存在 | 通常用 access() 或直接 stat()(更可靠) |
| 同步文件到磁盘 | fsync(int fd), fdatasync(int fd) |
迷你文件系统
下面是一个自创迷你文件系统示例:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <ftw.h>
#include <errno.h>
#include <libgen.h> // for dirname
#define SANDBOX_ROOT "./sandbox"
// ========================
// 工具函数
// ========================
char* path_join(const char *base, const char *rel) {
static char full[1024];
snprintf(full, sizeof(full), "%s/%s", base, rel);
return full;
}
// 安全拼接路径,并确保在 SANDBOX_ROOT 内(简单防护)
const char* safe_path(const char *user_path) {
if (user_path[0] == '/') {
fprintf(stderr, "错误:路径必须为相对路径(不能以 / 开头)\n");
return NULL;
}
if (strstr(user_path, "..")) {
fprintf(stderr, "错误:路径不能包含 '..'\n");
return NULL;
}
return path_join(SANDBOX_ROOT, user_path);
}
// ========================
// 1. mkdir -p
// ========================
int mkdir_p(const char *path, mode_t mode) {
if (!path || *path == '\0') return -1;
char *path_copy = strdup(path);
if (!path_copy) return -1;
char *p = path_copy;
if (*p == '/') p++;
while (*p) {
char *slash = strchr(p, '/');
if (slash) *slash = '\0';
if (mkdir(path_copy, mode) != 0 && errno != EEXIST) {
perror("mkdir");
free(path_copy);
return -1;
}
if (!slash) break;
*slash = '/';
p = slash + 1;
}
free(path_copy);
return 0;
}
// ========================
// 2. 写文件
// ========================
int write_file(const char *filepath, const char *content) {
// 确保父目录存在
char *dir_part = strdup(filepath);
char *dname = dirname(dir_part);
if (mkdir_p(dname, 0755) != 0) {
free(dir_part);
return -1;
}
free(dir_part);
int fd = open(filepath, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (fd == -1) {
perror("open");
return -1;
}
ssize_t len = strlen(content);
if (write(fd, content, len) != len) {
perror("write");
close(fd);
return -1;
}
close(fd);
return 0;
}
// ========================
// 3. 打印目录树
// ========================
void print_tree(const char *path, int depth) {
DIR *dir = opendir(path);
if (!dir) {
perror("opendir");
return;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
for (int i = 0; i < depth; i++) printf("│ ");
printf("├── %s\n", entry->d_name);
char fullpath[1024];
snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entry->d_name);
struct stat st;
if (stat(fullpath, &st) == 0 && S_ISDIR(st.st_mode)) {
print_tree(fullpath, depth + 1);
}
}
closedir(dir);
}
// ========================
// 4. 递归删除
// ========================
int remove_file(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
return remove(fpath);
}
int rm_rf(const char *path) {
if (nftw(path, remove_file, 64, FTW_DEPTH | FTW_PHYS) == 0) {
return 0;
} else {
perror("rm_rf");
return -1;
}
}
// ========================
// 主交互循环
// ========================
int main() {
// 初始化沙箱目录
if (mkdir_p(SANDBOX_ROOT, 0755) != 0) {
fprintf(stderr, "无法创建目录 %s\n", SANDBOX_ROOT);
return 1;
}
printf("迷你文件系统 (目录: %s)\n", SANDBOX_ROOT);
printf("支持命令:\n");
printf(" mkdir <目录路径> # 创建目录(可多级)\n");
printf(" write <文件路径> <内容> # 创建并写入文件(内容支持空格)\n");
printf(" tree [路径] # 查看目录结构(默认为沙箱根)\n");
printf(" rmdir <路径> # 递归删除文件或目录\n");
printf(" quit # 退出\n");
printf("----------------------------------------\n");
char line[1024];
while (1) {
printf("\nfs> ");
if (!fgets(line, sizeof(line), stdin)) break;
line[strcspn(line, "\n")] = 0; // 去掉换行
if (strlen(line) == 0) continue;
char *cmd = strtok(line, " ");
if (!cmd) continue;
if (strcmp(cmd, "quit") == 0) {
break;
}
else if (strcmp(cmd, "mkdir") == 0) {
char *arg = strtok(NULL, "");
if (!arg) {
printf("用法: mkdir <路径>\n");
continue;
}
const char *full = safe_path(arg);
if (!full) continue;
if (mkdir_p(full, 0755) == 0) {
printf("目录已创建: %s\n", full);
}
}
else if (strcmp(cmd, "write") == 0) {
char *file_arg = strtok(NULL, " ");
char *content = strtok(NULL, "");
if (!file_arg || !content) {
printf("用法: write <文件路径> <内容>\n");
continue;
}
const char *full = safe_path(file_arg);
if (!full) continue;
if (write_file(full, content) == 0) {
printf("文件已写入: %s\n", full);
}
}
else if (strcmp(cmd, "tree") == 0) {
char *arg = strtok(NULL, "");
const char *target;
if (arg) {
target = safe_path(arg);
if (!target) continue;
} else {
target = SANDBOX_ROOT;
}
printf("%s\n", target);
print_tree(target, 0);
}
else if (strcmp(cmd, "rmdir") == 0) {
char *arg = strtok(NULL, "");
if (!arg) {
printf("用法: rmdir <路径>\n");
continue;
}
const char *full = safe_path(arg);
if (!full) continue;
if (rm_rf(full) == 0) {
printf("已删除: %s\n", full);
}
}
else {
printf("未知命令: %s\n", cmd);
}
}
printf("exit!\n");
return 0;
}
运行示例:
迷你文件系统 (目录: ./sandbox)
支持命令:
mkdir <目录路径> # 创建目录(可多级)
write <文件路径> <内容> # 创建并写入文件(内容支持空格)
tree [路径] # 查看目录结构(默认为沙箱根)
rmdir <路径> # 递归删除文件或目录
quit # 退出
----------------------------------------
fs> mkdir dir1
目录已创建: ./sandbox/dir1
fs> mkdir dir2/dir3/dir4
目录已创建: ./sandbox/dir2/dir3/dir4
fs> write dir2/dir3/hello.txt hello world!
文件已写入: ./sandbox/dir2/dir3/hello.txt
fs> tree
./sandbox
├── dir1
│ ├── hello.txt
├── dir2
│ ├── dir3
│ │ ├── dir4
│ │ ├── hello.txt
fs> rmdir dir2/dir3/dir4
已删除: ./sandbox/dir2/dir3/dir4
fs> tree
./sandbox
├── dir1
│ ├── hello.txt
├── dir2
│ ├── dir3
│ │ ├── hello.txt
fs> quit
exit!