DEV Community

Stukdee_Gorye
Stukdee_Gorye

Posted on

C语言文件读取问题即解决方法

C语言文件读取问题

在使用C语言读取相对路径下的文件时,可能会遇到“无法找到文件的问题”。

测试环境:

系统:

gcc:

Apple clang version 14.0.0 (clang-1400.0.29.202)
Target: x86_64-apple-darwin22.2.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
Enter fullscreen mode Exit fullscreen mode

目录结构:

.
|____.DS_Store
|____a.c
|____a.txt
Enter fullscreen mode Exit fullscreen mode

测试:

#include <stdio.h>
int main(){
    char text[20];
    if(freopen("./a.txt","r",stdin) == NULL){
        perror("无法打开文件");
    }
    else{
        if(fgets(text,sizeof(text),stdin) == NULL){
            printf("文件读取失败\n");
            return 1;
        }
        fclose(stdin);
        printf("%s\n",text);
    }
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

在两种不同的运行方式下,代码运行的结果不同:

在终端且在可执行文件的目录下编译并运行文件:


可以看到文件读取成功。

但如果直接双击或在主目录(又或是除可执行文件所在的其它目录)下运行已经编译的文件:

这是双击文件时终端所显示的


这是在主目录下运行终端所显示的


根据终端输出的内容,代码读取文本失败。

原因探究

我将通过一段代码,输出程序所操作的目录(或者有其它更容易理解的称呼)。

#include <stdio.h>
#include <unistd.h>
#include <limits.h>
int main(){
    char cwd[PATH_MAX];
    if (getcwd(cwd,sizeof(cwd)) != NULL) {
        printf("当前目录: %s\n", cwd);
    } else {
        perror("getcwd()失败");
    }
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

再次按照先前的方式运行文件:


在其它目录下运行:


由此可见,代码所操作的目录,是终端所在的目录(或称之为终端所操作的目录),代码所读取文件的情况会因此而改变。

解决方法

可以直接获取文件的绝对路径:

这是适用于macOS的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h> /*提供PATH_MAX。*/
#include <mach-o/dyld.h> /*提供_NSGetExecutablePath*/
#include <libgen.h> /*提供dirname (optional)*/
#include <unistd.h>
char *concatenate(const char *str1,const char *str2){ /*字符拼接*/
    size_t len1 = strlen(str1);
    size_t len2 = strlen(str2);
    char *result = (char*)malloc(len1 + len2 + 1);
    if(result == NULL){
        return NULL;
    }
    strcpy(result,str1);
    strcat(result,str2);
    return result;
}
char *the_path(){ /*macOS获取绝对路径的核心代码*/
    char path[PATH_MAX];
    uint32_t size = sizeof(path);
    char *real_path = NULL;
    char *dir_path = NULL;
    if(_NSGetExecutablePath(path,&size) == 0){ /*macOS特供函数*/
        printf("获取路径为:%s\n",path);
        real_path = realpath(path,NULL); /*规范路径格式*/
        if(real_path != NULL){
            printf("绝对路径为:%s\n",real_path);
            dir_path = strdup(real_path);
            if(dir_path != NULL){ /*只获取路径*/
                char *last_slash = strrchr(dir_path, '/');
                if(last_slash != NULL){
                    *last_slash = '\0';
                }
                printf("目录路径为:%s\n",dir_path);
            }
        }
        else{
            perror("realpath");
        }
    }
    else{
        fprintf(stderr,"缓冲太小,需要 %u bytes\n",size);
    }
    if(dir_path != NULL){
        free(real_path);
        return dir_path;
    }
    return NULL;
}
int main(){
    char cwd[PATH_MAX];
    if (getcwd(cwd,sizeof(cwd)) != NULL) {
        printf("当前目录: %s\n", cwd);
    } else {
        perror("getcwd()失败");
    }
    char text[20];
    if(freopen(concatenate(the_path(),"/a.txt"),"r",stdin) == NULL){
        perror("无法打开文件");
    }
    else{
        if(fgets(text,sizeof(text),stdin) == NULL){
            printf("文件读取失败\n");
            return 1;
        }
        fclose(stdin);
        printf("%s\n",text);
    }
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

再次通过不同方式运行:


文件读取成功,但是这只支持macOS,所以接下来我尝试在linux下实现。

Linux获取绝对路径

我将在Termius上远程控制我的树莓派,这是我的测试环境:


接下来我来创建一下与先前一致的测试目录:


a.c目前还没有被编写,现在我将再次测试linux是否支持直接使用相对路径:


由此可见,linux与先前的问题一样,我们仍然需要获取绝对路径。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> /*提供readlink*/
#include <limits.h> /*提供PATH_MAX*/
char *the_path(){
    char path[PATH_MAX];
    char *dir_path = NULL;
    ssize_t len = readlink("/proc/self/exe",path,sizeof(path) - 1);
    if(len != -1){
        path[len] = '\0';
        printf("绝对路径:%s\n",path);
        dir_path = strdup(path);
        char *last_slash = strrchr(dir_path,'/');
        if(last_slash != NULL){
            *last_slash = '\0';
        }
        printf("目录路径为:%s\n",dir_path);
        return dir_path;
    }
    else{
        perror("readlink");
    }
    return NULL;
}
char *concatenate(const char *str1,const char *str2){
    size_t len1 = strlen(str1);
    size_t len2 = strlen(str2);
    char *result = (char*)malloc(len1 + len2 + 1);
    if(result == NULL){
        return NULL;
    }
    strcpy(result,str1);
    strcat(result,str2);
    return result;
}
int main(){
    char text[20];
    if(freopen(concatenate(the_path(),"/a.txt"),"r",stdin) == NULL){
        perror("无法打开文件\n");
    }
    else{
        if(fgets(text,sizeof(text),stdin) == NULL){
            printf("文件读取失败\n");
            return 1;
        }
        fclose(stdin);
        printf("%s\n",text);
    }
    return 0;
}

Enter fullscreen mode Exit fullscreen mode

运行结果如下:


文件读取成功。

windows获取绝对路径

windows的问题与先前一致,测试就跳过了。

环境:


代码:

#include <stdio.h>
#include <windows.h>
#include <tchar.h> /*提供 _TCHAR 宏和 _tprintf 等函数*/
#include <limits.h>
#include <string.h>
char *the_path() {
    _TCHAR path[MAX_PATH];
    DWORD length = GetModuleFileName(NULL,path,MAX_PATH);
    char *dir_path = NULL;
    if (length > 0) {
        _tprintf(_T("绝对路径(Unicode): %s\n"),path);
        dir_path = strdup(path);
        if(dir_path != NULL){
            char *last_slash = strrchr(dir_path,'\\');
            if(last_slash != NULL){
                *last_slash = '\0';
            }
            printf("目录路径为:%s\n",dir_path);
            return dir_path;
        }
    }
    else {
        DWORD error = GetLastError();
        _ftprintf(stderr,_T("失败代码: %lu\n"),error);
    }
    return NULL;
}
char *concatenate(const char *str1,const char *str2){ /*字符拼接*/
    size_t len1 = strlen(str1);
    size_t len2 = strlen(str2);
    char *result = (char*)malloc(len1 + len2 + 1);
    if(result == NULL){
        return NULL;
    }
    strcpy(result,str1);
    strcat(result,str2);
    return result;
}
int main() {
    char text[20];
    if(freopen(concatenate(the_path(),"/a.txt"),"r",stdin) == NULL){
        perror("无法打开文件");
    }
    else{
        if(fgets(text,sizeof(text),stdin) == NULL){
            printf("文件读取失败\n");
            return 1;
        }
        fclose(stdin);
        printf("%s\n",text);
    }
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

为了使PowerShell正常显示中文,代码与测试文本都以GBD格式存储。

效果如下:


文件读取成功。

跨平台支持

为了让代码可以在不同系统上顺利运行,同时适配一些没有考虑的系统,我将代码中和到了一起,并添加了argv获取方式作为保底。

最后的代码是这样的:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#if defined(_WIN32) || defined(_WIN64)
    #include <windows.h>
    #include <tchar.h>
#elif defined(__linux__)
    #include <unistd.h>
    #include <limits.h>
#elif defined(__APPLE__)
    #include <mach-o/dyld.h>
    #include <libgen.h>
    #include <unistd.h>
#endif
char *concatenate(const char *str1,const char *str2) {
    size_t len1 = strlen(str1);
    size_t len2 = strlen(str2);
    char *result = (char*)malloc(len1 + len2 + 1);
    if(result == NULL){
        return NULL;
    }
    strcpy(result,str1);
    strcat(result,str2);
    return result;
}
/*核心代码开头*/
char *get_path_from_argv(const char *argv0){
    if(argv0 == NULL){
        return NULL;
    }
    char *real_path = realpath(argv0,NULL);
    if(real_path != NULL){
        printf("使用argv[0]获取绝对路径:%s\n",real_path);
        char *dir_path = strdup(real_path);
        if(dir_path != NULL){
            char *last_slash = strrchr(dir_path,'/');
            if(last_slash == NULL){
                last_slash = strrchr(dir_path,'\\');
            }
            if(last_slash != NULL){
                *last_slash = '\0';
            }
            printf("目录路径为:%s\n",dir_path);
        }
        free(real_path);
        return dir_path;
    }
    return NULL;
}
char *the_path(int argc,char *argv[]){
    #if defined(_WIN32) || defined(_WIN64)
        _TCHAR path[MAX_PATH];
        DWORD length = GetModuleFileName(NULL,path,MAX_PATH);
        char *dir_path = NULL;
        if(length > 0){
            _tprintf(_T("绝对路径(Unicode): %s\n"), path);
            dir_path = strdup(path);
            if(dir_path != NULL){
                char *last_slash = strrchr(dir_path,'\\');
                if(last_slash != NULL){
                    *last_slash = '\0';
                }
                printf("目录路径为:%s\n",dir_path);
                return dir_path;
            }
        }
        else{
            DWORD error = GetLastError();
            _ftprintf(stderr, _T("失败代码: %lu\n"),error);
        }
        char *argv_path = get_path_from_argv(argc > 0 ? argv[0] : NULL);
        if(argv_path != NULL){
            return argv_path;
        }
        return NULL;
    #elif defined(__linux__)
        char path[PATH_MAX];
        char *dir_path = NULL;
        ssize_t len = readlink("/proc/self/exe",path,sizeof(path) - 1);
        if(len != -1){
            path[len] = '\0';
            printf("绝对路径:%s\n",path);
            dir_path = strdup(path);
            char *last_slash = strrchr(dir_path,'/');
            if(last_slash != NULL){
                *last_slash = '\0';
            }
            printf("目录路径为:%s\n",dir_path);
            return dir_path;
        }
        else{
            perror("readlink");
        }
        char *argv_path = get_path_from_argv(argc > 0 ? argv[0] : NULL);
        if(argv_path != NULL){
            return argv_path;
        }
        return NULL;
    #elif defined(__APPLE__)
        char path[PATH_MAX];
        uint32_t size = sizeof(path);
        char *real_path = NULL;
        char *dir_path = NULL;
        if(_NSGetExecutablePath(path,&size) == 0){
            printf("获取路径为:%s\n",path);
            real_path = realpath(path,NULL);
            if(real_path != NULL){
                printf("绝对路径为:%s\n",real_path);
                dir_path = strdup(real_path);
                if(dir_path != NULL){
                    char *last_slash = strrchr(dir_path,'/');
                    if(last_slash != NULL){
                        *last_slash = '\0';
                    }
                    printf("目录路径为:%s\n",dir_path);
                }
            }
            else{
                perror("realpath");
            }
        }
        else{
            fprintf(stderr,"缓冲太小,需要 %u bytes\n",size);
        }
        if(dir_path != NULL){
            free(real_path);
            return dir_path;
        }
        char *argv_path = get_path_from_argv(argc > 0 ? argv[0] : NULL);
        if(argv_path != NULL){
            return argv_path;
        }
        return NULL;
    #else
        fprintf(stderr, "不支持的操作系统\n");
        char *argv_path = get_path_from_argv(argc > 0 ? argv[0] : NULL);
        if(argv_path != NULL){
            return argv_path;
        }
        return NULL;
    #endif
}
/*核心代码结尾*/
int main(int argc,char *argv[]){ /*如果使用argv,需要加上“int argc,char *argv[]”这一段*/
    #if defined(__APPLE__)
        char cwd[PATH_MAX];
        if(getcwd(cwd,sizeof(cwd)) != NULL){
            printf("当前目录: %s\n", cwd);
        } 
        else{
            perror("getcwd()失败");
        }
    #endif
    char text[20];
    char *full_path = concatenate(the_path(argc,argv),"/a.txt"); /*所拼接的路径不能为“./a.txt”,需要为“/a.txt”的格式*/
    if(freopen(full_path,"r",stdin) == NULL){
        perror("无法打开文件");
    }
    else{
        if(fgets(text,sizeof(text),stdin) == NULL){
            printf("文件读取失败\n");
            free(full_path);
            return 1;
        }
        fclose(stdin);
        printf("%s\n",text);
    }
    free(full_path);
    return 0;
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)