You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在macOS下用C语言判断文件夹是否为别名?

问题描述

我在macOS下编写了一个递归程序,用于在指定目录树中查找特定文件。当前遇到的问题是:当程序找到指向同一分支上层目录的文件夹别名时,会陷入无限递归,一段时间后因段错误崩溃(递归层数仅约252层,这个数值偏低)。我已尝试修改代码跳过符号链接,但仍会遍历这些别名。

目录结构示例

每行左侧为文件夹名称,右侧为其内容:

  • folder1: folder2
  • folder2: folder1(别名)

代码片段

(已补充完整,未包含内存释放和错误检查)

char *name = //target file name
int myfind(){
    DIR *curr_dir = opendir(".");
    struct dirent *next_entry;
    struct stat info;
    char *entry_name;
    char *path;
    while((next_entry = readdir(curr_dir)) != NULL){
        entry_name = next_entry->d_name;
        //ignore . and ..
        if(strcmp(entry_name,".") == 0 || strcmp(entry_name,"..") == 0)
            continue;
        stat(entry_name, &info);
        if(strncmp(name, entry_name, 255) == 0){
            path = realpath(entry_name, NULL);
            //print info about file found
        }
        if(S_ISDIR(info.st_mode) && !S_ISLNK(info.st_mode)){ 
            char *prev_path = getwd(NULL);
            chdir(entry_name);
            myfind();
            chdir(prev_path);
        }
    }
}

解决方案

问题根源

  1. stat()的局限性:你当前使用的stat()会自动解析符号链接(包括macOS的文件夹别名),返回的是目标目录的信息而非别名本身。这导致!S_ISLNK(info.st_mode)的判断失效,程序依旧会进入别名指向的目录,触发无限递归。
  2. 栈溢出:递归层数达到252时崩溃,是因为进程栈空间有限,每次递归都会在栈上分配局部变量,层数过多会导致栈溢出,触发段错误。

修复方案

1. 替换stat()为lstat()

lstat()不会解析符号链接,能获取文件(包括别名、软链接)本身的属性,这样就能正确判断是否为链接文件,避免进入别名指向的目录。

2. 追踪已访问目录的inode和设备号

即使过滤了链接,若遇到硬链接目录(macOS下虽受限但仍需考虑)或别名指向已访问过的目录,仍可能触发循环。因此需要记录每个目录的st_dev(设备号)和st_ino(inode号),确保同一目录只被访问一次。

修改后的完整代码

#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

char *name = NULL; // 目标文件名,需外部赋值

// 记录已访问目录的设备号与inode号
typedef struct VisitedDir {
    dev_t dev;
    ino_t ino;
    struct VisitedDir *next;
} VisitedDir;

// 检查目录是否已访问过
int is_dir_visited(VisitedDir *head, dev_t dev, ino_t ino) {
    VisitedDir *curr = head;
    while (curr != NULL) {
        if (curr->dev == dev && curr->ino == ino) {
            return 1;
        }
        curr = curr->next;
    }
    return 0;
}

// 将目录添加到已访问列表
void add_visited_dir(VisitedDir **head, dev_t dev, ino_t ino) {
    VisitedDir *new_node = malloc(sizeof(VisitedDir));
    if (!new_node) return;
    new_node->dev = dev;
    new_node->ino = ino;
    new_node->next = *head;
    *head = new_node;
}

// 释放已访问列表内存
void free_visited_dir(VisitedDir *head) {
    VisitedDir *curr = head;
    while (curr != NULL) {
        VisitedDir *next = curr->next;
        free(curr);
        curr = next;
    }
}

int myfind(VisitedDir **visited) {
    DIR *curr_dir = opendir(".");
    if (!curr_dir) return -1;

    struct dirent *next_entry;
    struct stat info;
    char *entry_name;
    char *path;

    // 记录当前目录到已访问列表
    if (fstat(dirfd(curr_dir), &info) == 0) {
        if (!is_dir_visited(*visited, info.st_dev, info.st_ino)) {
            add_visited_dir(visited, info.st_dev, info.st_ino);
        }
    }

    while((next_entry = readdir(curr_dir)) != NULL) {
        entry_name = next_entry->d_name;
        if(strcmp(entry_name,".") == 0 || strcmp(entry_name,"..") == 0)
            continue;

        // 用lstat获取文件本身属性,不解析链接
        if (lstat(entry_name, &info) == -1) continue;

        // 匹配目标文件
        if(strncmp(name, entry_name, 255) == 0) {
            path = realpath(entry_name, NULL);
            if (path) {
                printf("找到文件: %s\n", path);
                free(path);
            }
        }

        // 仅处理未访问过的非链接目录
        if(S_ISDIR(info.st_mode) && !S_ISLNK(info.st_mode)) {
            if (is_dir_visited(*visited, info.st_dev, info.st_ino)) {
                continue;
            }

            char *prev_path = getwd(NULL);
            if (!prev_path) continue;

            if (chdir(entry_name) == 0) {
                myfind(visited);
                chdir(prev_path);
            }
            free(prev_path);
        }
    }

    closedir(curr_dir);
    return 0;
}

// 程序入口
int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("使用方式: %s <目标文件名>\n", argv[0]);
        return 1;
    }
    name = argv[1];
    VisitedDir *visited = NULL;
    myfind(&visited);
    free_visited_dir(visited);
    return 0;
}

额外提示

  • macOS的文件夹别名是Finder层面的特殊文件,本质是带资源分支的文件,但lstat()仍能识别其为链接文件,因此过滤逻辑有效。
  • 若要支持更深层次的目录遍历,可将递归实现改为迭代(用栈或队列存储待访问目录),彻底避免栈溢出问题。

内容的提问来源于stack exchange,提问作者Ernaldo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 21:13:12