文件目录操作

文件属性操作函数

在不同编程语言中,处理文件属性的函数和方法各有不同。这里我将分别介绍 C 和 Python 中的一些常用文件属性操作函数。

C 语言

在 C 语言中,标准库提供了几种函数来获取和修改文件的属性。这些函数主要通过操作系统的系统调用来实现,常用于POSIX系统(如Unix、Linux)。

  1. 获取文件属性

    • statfstat
      • 这些函数用于获取文件的状态信息,如大小、创建时间、修改时间、访问权限等。
      • stat 函数通过文件名获取信息,而 fstat 则通过文件描述符获取。

    示例代码:

    #include <sys/stat.h>
    #include <stdio.h>
    
    int main() {
        struct stat fileStat;
        if (stat("example.txt", &fileStat) < 0) {
            return 1;
        }
        printf("File Size: %ld bytes\n", fileStat.st_size);
        printf("Number of Links: %ld\n", fileStat.st_nlink);
        printf("File inode: %ld\n", fileStat.st_ino);
    
        return 0;
    }
    
  2. 修改文件属性

    • chmodfchmod
      • 这些函数用于改变文件的权限。
      • chmod 通过文件名修改权限,fchmod 通过文件描述符。

    示例代码:

    #include <sys/stat.h>
    
    int main() {
        chmod("example.txt", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
        return 0;
    }
    

Python 语言

在 Python 中,可以使用标准库中的 os 模块和 pathlib 模块来操作文件属性。

  1. 获取文件属性 (os 模块)

    • os.stat():获取文件的状态信息。

    示例代码:

    import os
    
    stat_info = os.stat('example.txt')
    print('File size:', stat_info.st_size)
    print('Last modified:', stat_info.st_mtime)
    print('Permissions:', oct(stat_info.st_mode))
    
  2. 修改文件属性

    • os.chmod():修改文件权限。
    • os.utime():修改文件的访问和修改时间。

    示例代码:

    import os
    
    # 修改文件权限
    os.chmod('example.txt', 0o644)
    
    # 修改文件的访问和修改时间
    import time
    os.utime('example.txt', (time.time(), time.time()))
    

这些函数和方法为在不同的编程环境中处理文件属性提供了基本的工具。对于更复杂的文件属性操作(如扩展属性或特定文件系统的特性),可能需要使用特定操作系统的API或第三方库。

access函数

在C和类Unix系统中,access() 函数是用于检查当前用户对给定文件的访问权限的标准库函数。这个函数定义在 <unistd.h> 头文件中。它主要用于检查调用进程是否可以对指定的文件执行读、写或执行操作。

函数原型

#include <unistd.h>
int access(const char *pathname, int mode);

参数:

  • pathname: 指向包含文件名的路径的字符串。
  • mode: 指定检查类型的参数。这个参数可以是以下常量的组合:
    • R_OK: 检查读权限。
    • W_OK: 检查写权限。
    • X_OK: 检查执行权限。
    • F_OK: 检查文件是否存在。

返回值:

  • 成功时返回0:表示指定的权限或存在性检查通过。
  • 失败时返回-1:表示某种权限不足或文件不存在。失败的具体原因可以通过 errno 来检查。

示例代码

下面的C代码示例展示了如何使用 access() 函数来检查一个文件的不同访问权限:

#include <unistd.h>
#include <stdio.h>

int main() {
    const char *filepath = "example.txt";

    // 检查文件是否存在
    if (access(filepath, F_OK) == 0) {
        printf("The file exists.\n");
    } else {
        perror("Error checking for file existence");
    }

    // 检查读权限
    if (access(filepath, R_OK) == 0) {
        printf("Read permission is granted.\n");
    } else {
        perror("Error checking read permission");
    }

    // 检查写权限
    if (access(filepath, W_OK) == 0) {
        printf("Write permission is granted.\n");
    } else {
        perror("Error checking write permission");
    }

    // 检查执行权限
    if (access(filepath, X_OK) == 0) {
        printf("Execute permission is granted.\n");
    } else {
        perror("Error checking execute permission");
    }

    return 0;
}

注意事项

使用 access() 函数时要注意,它根据调用进程的实际用户ID和实际组ID来检查权限,而不是有效用户ID或组ID。这意味着如果程序以setuid或setgid模式运行,access() 仍然会按照原始用户的权限进行检查。

此外,使用 access() 函数有时可能导致安全问题,如时间竞争条件(TOCTOU, Time Of Check to Time Of Use)。当在检查和使用文件之间的时间差中文件的状态发生变化时,可能会导致安全漏洞。因此,在安全敏感的应用中要谨慎使用。

chmod函数

在Unix和类Unix系统中,chmod 函数用于更改文件或目录的权限。这个函数是通过操作系统提供的系统调用实现的,并定义在 C 语言的标准库中,通常包含在 <sys/stat.h> 头文件中。

函数原型

chmod 函数的原型如下:

#include <sys/stat.h>
int chmod(const char *path, mode_t mode);

参数:

  • path: 指向包含文件名或目录名路径的字符串。
  • mode: 指定新的权限设置。这是一个位掩码,可以使用多个位掩码常量组合来设置权限。

权限位掩码

权限位掩码用于指定文件的权限,通常包括读(r)、写(w)和执行(x)权限,分别对应用户(owner)、组(group)和其他(others):

  • S_IRUSR, S_IWUSR, S_IXUSR: 分别代表文件所有者的读、写和执行权限。
  • S_IRGRP, S_IWGRP, S_IXGRP: 分别代表同组用户的读、写和执行权限。
  • S_IROTH, S_IWOTH, S_IXOTH: 分别代表其他用户的读、写和执行权限。

权限位也可以组合使用,例如,设置所有者具有读写权限,组用户和其他用户具有读权限,可以如下设置:

mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;

示例代码

下面的C代码示例演示了如何使用 chmod 函数来修改文件权限:

/*
    #include <sys/stat.h>
    int chmod(const char *pathname, mode_t mode);
        修改文件的权限
        参数:
            - pathname: 需要修改的文件的路径
            - mode:需要修改的权限值,八进制的数
        返回值:成功返回0,失败返回-1

*/
#include <sys/stat.h>
#include <stdio.h>
int main() {

    int ret = chmod("a.txt", 0777);

    if(ret == -1) {
        perror("chmod");
        return -1;
    }

    return 0;
}

注意事项

  1. 权限: 只有文件的所有者或超级用户(root)可以修改文件的权限。
  2. 安全性: 更改文件权限可能会引发安全问题,特别是在多用户环境中。正确的权限设置对于保护敏感数据至关重要。
  3. 错误处理: 如果 chmod 函数失败,它将返回 -1 并设置 errno 以指示错误的原因。使用 perror()strerror(errno) 可以获取错误信息。

使用 chmod 函数时,应确保理解不同权限设置的含义,并仔细检查权限设置以避免不小心给予过多的权限,特别是在涉及可执行文件和敏感数据的情况下。

truncate函数

在Unix和类Unix系统中,truncate 函数用于更改一个文件的大小。如果文件原来的大小超过了指定的大小,额外的数据会被丢弃。如果文件原来的大小小于指定的大小,文件将会被扩展,新添加的部分会用零填充。这个函数定义在 <unistd.h> 头文件中。

函数原型

truncate 函数的原型如下:

#include <unistd.h>
int truncate(const char *path, off_t length);

参数:

  • path: 指向要被更改大小的文件名的字符串。
  • length: 新的文件大小。

返回值:

  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno 来表示错误原因。

权限和错误处理

使用 truncate 函数时需要有适当的文件写权限。如果尝试缩减一个文件,但没有写权限,调用将失败。常见的错误代码包括:

  • EACCES: 访问被拒绝,文件的权限不允许写操作。
  • EINVAL: 指定的长度无效。
  • EISDIR: 指定的文件是一个目录。
  • ENOENT: 指定的文件不存在。

示例代码

下面的C代码示例展示了如何使用 truncate 函数来改变文件的大小:

#include <unistd.h>
#include <stdio.h>

int main() {
    const char *filepath = "example.txt";

    // 设置文件大小为 100 字节
    if (truncate(filepath, 100) == 0) {
        printf("File size changed to 100 bytes successfully.\n");
    } else {
        perror("Error changing file size");
    }

    return 0;
}

ftruncate 函数

除了 truncate,还有一个相关的函数 ftruncate,它通过文件描述符而不是文件名来指定文件:

#include <unistd.h>
int ftruncate(int fd, off_t length);

这个函数通常用在已经通过 open 等函数获得文件描述符的情境中,允许你更改打开文件的大小。ftruncate 的用法与 truncate 类似,但它适用于那些已经通过文件描述符操作的场景。

使用这些函数时,应确保正确处理可能的错误,并且只在确定要改变文件大小的情况下使用它们,因为数据的丢失是不可逆的。

目录操作函数

在编程中,处理目录涉及创建、删除、遍历和修改目录属性等多种操作。不同编程语言提供了各自的库来简化这些操作。下面我将介绍C语言和Python语言中的一些常用目录操作函数。

C语言中的目录操作

在C语言中,你通常会使用POSIX标准定义的函数来进行目录操作。这些函数定义在 <dirent.h><sys/types.h><sys/stat.h><unistd.h> 等头文件中。

  1. 创建目录

    • mkdir(const char *pathname, mode_t mode):创建一个新目录。
  2. 删除目录

    • rmdir(const char *pathname):删除一个空目录。
  3. 读取目录内容

    • opendir(const char *name):打开一个目录流。
    • readdir(DIR *dirp):读取目录流中的下一个目录项。
    • closedir(DIR *dirp):关闭目录流。
  4. 改变当前工作目录

    • chdir(const char *path):改变当前工作目录。

示例:C语言遍历目录

以下是一个使用C语言中的目录操作函数来遍历目录内容的示例:

#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    DIR *dir;
    struct dirent *entry;

    dir = opendir(".");
    if (dir == NULL) {
        perror("Failed to open directory");
        return EXIT_FAILURE;
    }

    while ((entry = readdir(dir)) != NULL) {
        printf("%s\n", entry->d_name);
    }

    closedir(dir);
    return EXIT_SUCCESS;
}

Python中的目录操作

Python通过标准库中的 osos.path 模块提供目录操作功能,更为简洁和方便。

  1. 创建和删除目录

    • os.mkdir(path):创建一个目录。
    • os.makedirs(path, exist_ok=True):创建多层目录,如果 exist_okTrue,则如果目录已存在不会引发异常。
    • os.rmdir(path):删除一个空目录。
    • os.removedirs(path):递归删除目录。
  2. 遍历目录

    • os.listdir(path):列出目录中的文件和子目录。
    • os.walk(path):生成目录树中的文件名,用于遍历目录及其所有子目录的文件。
  3. 修改和获取目录属性

    • os.chdir(path):改变当前工作目录。
    • os.stat(path):获取目录的状态信息。

示例:Python遍历目录

以下是一个使用Python中的 os.walk 来遍历目录及其所有子目录的文件的示例:

import os

for root, dirs, files in os.walk('.'):
    for name in files:
        print(os.path.join(root, name))

总结

无论是在C语言还是Python中,目录操作都是文件系统管理的基本部分。每种语言都提供了一套工具函数来简化这些操作,使开发者能够轻松地编写代码来管理和维护文件系统。在实际应用中,根据需要选择合适的函数和方法来处理文件和目录是非常重要的。

mkdir函数

/*
    #include <sys/stat.h>
    #include <sys/types.h>
    int mkdir(const char *pathname, mode_t mode);
        作用:创建一个目录
        参数:
            pathname: 创建的目录的路径
            mode: 权限,八进制的数
        返回值:
            成功返回0, 失败返回-1
*/

#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>

int main() {

    int ret = mkdir("aaa", 0777);

    if(ret == -1) {
        perror("mkdir");
        return -1;
    }

    return 0;
}

chdir函数/getcwd函数

/*

    #include <unistd.h>
    int chdir(const char *path);
        作用:修改进程的工作目录
            比如在/home/nowcoder 启动了一个可执行程序a.out, 进程的工作目录 /home/nowcoder
        参数:
            path : 需要修改的工作目录

    #include <unistd.h>
    char *getcwd(char *buf, size_t size);
        作用:获取当前工作目录
        参数:
            - buf : 存储的路径,指向的是一个数组(传出参数)
            - size: 数组的大小
        返回值:
            返回的指向的一块内存,这个数据就是第一个参数

*/
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main() {

    // 获取当前的工作目录
    char buf[128];
    getcwd(buf, sizeof(buf));
    printf("当前的工作目录是:%s\n", buf);

    // 修改工作目录
    int ret = chdir("/home/nowcoder/Linux/lesson13");
    if(ret == -1) {
        perror("chdir");
        return -1;
    } 

    // 创建一个新的文件
    int fd = open("chdir.txt", O_CREAT | O_RDWR, 0664);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    close(fd);

    // 获取当前的工作目录
    char buf1[128];
    getcwd(buf1, sizeof(buf1));
    printf("当前的工作目录是:%s\n", buf1);
    
    return 0;
}

目录遍历函数

目录遍历是一种常用的文件系统操作,用于访问和操作目录树中的所有文件和子目录。不同的编程语言提供了各自的工具和函数来实现目录遍历。下面,我将详细介绍C语言和Python中的目录遍历方法。

C语言中的目录遍历

在C语言中,使用POSIX标准的<dirent.h>库可以遍历目录。该库提供了一组函数,允许你打开目录、读取目录内容和关闭目录。

主要函数

  • DIR *opendir(const char *name): 打开一个目录流。
  • struct dirent *readdir(DIR *dirp): 读取目录流中的下一个目录项。
  • int closedir(DIR *dirp): 关闭目录流。

示例代码

#include <stdio.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>

void list_directory(const char *path) {
    DIR *dir;
    struct dirent *entry;

    if ((dir = opendir(path)) == NULL) {
        perror("opendir() failed");
        return;
    }

    while ((entry = readdir(dir)) != NULL) {
        printf("%s\n", entry->d_name);
    }

    if (closedir(dir) == -1) {
        perror("closedir() failed");
    }
}

int main() {
    const char *path = ".";
    list_directory(path);
    return 0;
}

这段代码打开当前目录(.),遍历并打印每个文件和子目录的名称,然后关闭目录。

Python中的目录遍历

在Python中,可以使用os模块或pathlib模块来遍历目录。os模块提供了一个os.walk()函数,它为目录及其所有子目录下的每一个文件生成目录树中的文件名。pathlib模块提供了一个更现代的接口。

os模块使用os.walk()

import os

def list_directory(path):
    for root, dirs, files in os.walk(path):
        for name in files:
            print(os.path.join(root, name))
        for name in dirs:
            print(os.path.join(root, name))

list_directory('.')

pathlib模块使用Path.rglob()

from pathlib import Path

def list_directory(path):
    base_path = Path(path)
    for entry in base_path.rglob('*'):  # rglob is recursive glob
        print(entry)

list_directory('.')

这些示例展示了如何在C和Python中遍历目录。os.walk()Path.rglob()均递归地处理所有子目录,非常适合处理复杂的文件结构。Python的pathlib模块提供的面向对象的接口使得文件系统的交互更加直观和现代化。在实际应用中,根据需要选择合适的工具和方法来处理文件和目录至关重要。

opendir函数

在C语言中,opendir 函数是用于打开一个目录以便读取其中的条目。这个函数是 UNIX 和类 UNIX 系统中处理目录的标准方法之一,定义在 <dirent.h> 头文件中。通过使用 opendir 与其他函数如 readdirclosedir 结合,可以实现目录内容的遍历和操作。

函数原型

#include <dirent.h>
DIR *opendir(const char *name);

参数:

  • name: 要打开的目录的路径。

返回值:

  • 成功时返回一个指向 DIR 类型的指针,该指针代表了目录流。
  • 失败时返回 NULL,此时可以通过检查 errno 来获取错误详情。

错误处理

opendir 可能会因为多种原因失败,例如:

  • EACCES: 权限不足,无法读取目录。
  • EMFILE: 进程已打开的文件描述符数量达到限制。
  • ENFILE: 系统范围内打开的文件描述符数量达到限制。
  • ENOMEM: 内存不足,无法处理新的目录流。
  • ENOTDIR: 名称不是一个目录。
  • ENOENT: 目录不存在,或者路径中的一个目录组件不存在。

示例代码

以下是一个简单的使用 opendir 和其他相关函数来遍历目录内容的示例:

#include <stdio.h>
#include <dirent.h>

int main() {
    const char *dirPath = "/path/to/directory";
    DIR *dir = opendir(dirPath);
    struct dirent *entry;

    if (dir == NULL) {
        perror("Failed to open directory");
        return 1;
    }

    while ((entry = readdir(dir)) != NULL) {
        printf("%s\n", entry->d_name);
    }

    closedir(dir);
    return 0;
}

这段代码尝试打开一个目录,并使用 readdir 读取其中的每个条目,然后打印每个条目的名称。最后,使用 closedir 关闭目录流。

使用场景

opendir 函数通常用在需要列出目录内容的场景中,比如文件浏览器、文件同步工具或搜索工具中。通过组合使用 opendir, readdir, 和 closedir,你可以有效地遍历文件系统的目录结构。

这个函数提供了基本的目录打开功能,而扩展的功能(如过滤、排序目录内容等)则需要额外的代码来实现。在设计使用这些函数的程序时,确保考虑到异常处理和错误检查,以保证程序的健壮性和用户的良好体验。

readdir函数

在C语言中,readdir 函数是用于读取目录流(DIR 类型,由 opendir 函数返回)中的条目。每次调用 readdir 都会返回目录中的下一个条目,直到所有条目都被读取完毕。此函数在处理文件系统目录时非常有用,常见于文件管理器和文件搜索工具中。

函数原型

#include <dirent.h>
struct dirent *readdir(DIR *dirp);

参数:

  • dirp: 指向由 opendir 打开的目录流的指针。

返回值:

  • 成功时返回一个指向 struct dirent 结构的指针,该结构包含了目录条目的详细信息。
  • 当目录中没有更多条目或发生错误时,返回 NULL

结构体 dirent

struct dirent 包含了目录中文件的信息。其定义如下:

struct dirent
{
// 此目录进入点的inode
ino_t d_ino; 
// 目录文件开头至此目录进入点的位移
off_t d_off; 
// d_name 的长度, 不包含NULL字符
unsigned short int d_reclen; 
// d_name 所指的文件类型
unsigned char d_type; 
// 文件名
char d_name[256];
};
  • d_ino: 文件的inode编号。
  • d_off: 目录文件中的偏移量(通常不直接使用)。
  • d_reclen: 目录条目的长度。
  • d_type: 文件类型(如普通文件、目录等)。
  • d_name: 文件名。

d_type

DT_BLK - 块设备

DT_CHR - 字符设备

DT_DIR - 目录

DT_LNK - 软连接

DT_FIFO - 管道

DT_REG - 普通文件

DT_SOCK - 套接字

DT_UNKNOWN - 未知

示例代码

下面的代码示例演示了如何使用 opendirreaddir 来遍历一个目录的内容:

#include <stdio.h>
#include <dirent.h>

int main() {
    DIR *dir;
    struct dirent *entry;

    dir = opendir(".");
    if (dir == NULL) {
        perror("opendir() failed");
        return 1;
    }

    while ((entry = readdir(dir)) != NULL) {
        printf("%s\n", entry->d_name);
    }

    closedir(dir);
    return 0;
}

这段代码打开当前目录(.),遍历并打印每个文件和子目录的名称,然后关闭目录流。

注意事项

  • readdir 是非线程安全的,但有一个线程安全版本 readdir_r,尽管它已经被标记为过时(在 POSIX.1-2008 中弃用)。
  • 多次连续调用 readdir 可以遍历整个目录的内容。一旦所有条目都被读取,函数将返回 NULL
  • 如果需要对目录内容进行排序或过滤,你需要自己实现逻辑来处理 readdir 返回的条目。

使用 readdir 函数提供了一种有效的方法来访问和枚举目录内容,是许多基于文件系统操作的应用程序的基础。

closedir函数

在C语言中,closedir 函数用于关闭由 opendir 打开的目录流。这个函数是 POSIX 系统编程接口的一部分,定义在 <dirent.h> 头文件中。使用 closedir 是释放由 opendir 分配的资源的标准方法,这是确保系统资源得到妥善管理的重要步骤。

函数原型

#include <dirent.h>
int closedir(DIR *dirp);

参数:

  • dirp: 指向由 opendir 打开的目录流的指针。

返回值:

  • 成功时返回 0。
  • 失败时返回 -1,并且 errno 会被设置以指示错误原因。

示例代码

下面的示例代码展示了如何使用 opendir, readdir, 和 closedir 函数来遍历目录内容并最终关闭目录流:

#include <stdio.h>
#include <dirent.h>

int main() {
    DIR *dir;
    struct dirent *entry;

    dir = opendir(".");
    if (dir == NULL) {
        perror("Failed to open directory");
        return 1;
    }

    while ((entry = readdir(dir)) != NULL) {
        printf("%s\n", entry->d_name);
    }

    if (closedir(dir) < 0) {
        perror("Failed to close directory");
        return 1;
    }

    return 0;
}

这段代码首先尝试打开当前目录(.)。如果成功,它将遍历目录中的每个文件,打印每个文件的名称。遍历完成后,使用 closedir 关闭目录流,并检查是否成功关闭。

错误处理和注意事项

  • 如果 closedir 失败,错误信息可以通过 errno 获得。可能的错误包括:

    • EBADF: 参数 dirp 不是有效的目录流指针。
  • 即使 closedir 失败,程序也应该尽量继续执行释放其他资源的操作,因为留下未关闭的目录流可能导致资源泄漏。

  • 在多线程环境中,确保目录流在关闭后不被再次使用是非常重要的,因为使用已关闭的目录流可能导致未定义行为。

使用 closedir 来关闭目录流是一个良好的编程习惯,可以帮助避免资源泄露,保持程序的健壮性和稳定性。

/*
    // 打开一个目录
    #include <sys/types.h>
    #include <dirent.h>
    DIR *opendir(const char *name);
        参数:
            - name: 需要打开的目录的名称
        返回值:
            DIR * 类型,理解为目录流
            错误返回NULL


    // 读取目录中的数据
    #include <dirent.h>
    struct dirent *readdir(DIR *dirp);
        - 参数:dirp是opendir返回的结果
        - 返回值:
            struct dirent,代表读取到的文件的信息
            读取到了末尾或者失败了,返回NULL

    // 关闭目录
    #include <sys/types.h>
    #include <dirent.h>
    int closedir(DIR *dirp);

*/
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int getFileNum(const char * path);

// 读取某个目录下所有的普通文件的个数
int main(int argc, char * argv[]) {

    if(argc < 2) {
        printf("%s path\n", argv[0]);
        return -1;
    }

    int num = getFileNum(argv[1]);

    printf("普通文件的个数为:%d\n", num);

    return 0;
}

// 用于获取目录下所有普通文件的个数
int getFileNum(const char * path) {

    // 1.打开目录
    DIR * dir = opendir(path);

    if(dir == NULL) {
        perror("opendir");
        exit(0);
    }

    struct dirent *ptr;

    // 记录普通文件的个数
    int total = 0;

    while((ptr = readdir(dir)) != NULL) {

        // 获取名称
        char * dname = ptr->d_name;

        // 忽略掉. 和..
        if(strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0) {
            continue;
        }

        // 判断是否是普通文件还是目录
        if(ptr->d_type == DT_DIR) {
            // 目录,需要继续读取这个目录
            char newpath[256];
            sprintf(newpath, "%s/%s", path, dname);
            total += getFileNum(newpath);
        }

        if(ptr->d_type == DT_REG) {
            // 普通文件
            total++;
        }


    }

    // 关闭目录
    closedir(dir);

    return total;
}

在C语言中,dupdup2 函数用于复制文件描述符。这些函数是 UNIX 和类 UNIX 系统中的标准系统调用,定义在 <unistd.h> 头文件中。通过这些函数,你可以创建现有文件描述符的副本,这在重定向文件描述符、如标准输入、输出和错误流时特别有用。

dup 函数

函数原型

#include <unistd.h>
int dup(int oldfd);

参数:

  • oldfd: 要被复制的文件描述符。

返回值:

  • 成功时返回新的文件描述符,这个文件描述符是当前可用文件描述符中的最小数值。
  • 失败时返回 -1,并设置 errno 来指示错误原因。
/*
    #include <unistd.h>
    int dup(int oldfd);
        作用:复制一个新的文件描述符
        fd=3, int fd1 = dup(fd),
        fd指向的是a.txt, fd1也是指向a.txt
        从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符


*/

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>

int main() {

    int fd = open("a.txt", O_RDWR | O_CREAT, 0664);

    int fd1 = dup(fd);

    if(fd1 == -1) {
        perror("dup");
        return -1;
    }

    printf("fd : %d , fd1 : %d\n", fd, fd1);

    close(fd);

    char * str = "hello,world";
    int ret = write(fd1, str, strlen(str));
    if(ret == -1) {
        perror("write");
        return -1;
    }

    close(fd1);

    return 0;
}

dup2 函数

函数原型

#include <unistd.h>
int dup2(int oldfd, int newfd);

参数:

  • oldfd: 要被复制的文件描述符。
  • newfd: 指定的新文件描述符的数值。如果 newfd 已经打开,dup2 会先将其关闭,然后复制 oldfd

返回值:

  • 成功时返回 newfd
  • 失败时返回 -1,并设置 errno 来指示错误原因。

示例代码

这里有一个使用 dupdup2 的示例,该示例展示了如何重定向标准输出到一个文件。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
    int file = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (file < 0) {
        perror("Failed to open file");
        return 1;
    }

    // 使用 dup2 将标准输出重定向到文件
    if (dup2(file, STDOUT_FILENO) < 0) {
        perror("Failed to redirect standard output");
        return 1;
    }

    // 现在,所有的标准输出将写入文件
    printf("This will be written to the file.\n");

    // 关闭文件描述符
    close(file);

    return 0;
}

错误处理

常见的 errno 设置包括:

  • EBADF: oldfd 不是有效的文件描述符。
  • EMFILE: 达到进程允许打开的文件描述符的最大数量。
  • EINTR: 操作被信号中断。

注意事项

  • 使用 dupdup2 时,新的文件描述符将继承 oldfd 的文件偏移量和文件状态标志,但不继承文件锁。
  • 在使用 dup2 时,如果 newfd 已经打开,它将被 dup2 自动关闭,不需要手动关闭。这一点在进行重定向操作时尤其有用。
  • 当不再需要这些文件描述符时,应该关闭它们,以避免资源泄露。

这些函数提供了灵活的文件描述符管理功能,特别适合在需要复制、管理或重定向文件描述符的高级文件操作和进程间通信中使用。

fcntl函数

在C语言中,fcntl 函数是一个非常强大的文件控制接口,用于操作文件描述符的各种属性。它提供了对文件描述符的控制能力,比如设置文件描述符的状态标志、改变已打开文件的属性等。fcntl 函数定义在 <fcntl.h> 头文件中,是 UNIX 和类 UNIX 系统中的标准系统调用。

函数原型

#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

参数:

  • fd: 要操作的文件描述符。
  • cmd: 指定要执行的操作类型。
  • ...: 额外参数,其类型和数量依赖于 cmd 的值。

返回值:

  • 成功时返回值取决于操作类型。
  • 失败时返回 -1,并设置 errno 来指示错误原因。

常用的命令(cmd

  • F_DUPFDF_DUPFD_CLOEXEC: 复制文件描述符,并可设置复制的描述符在执行 exec 时自动关闭。
  • F_GETFD / F_SETFD: 获取或设置文件描述符的标志,例如 FD_CLOEXEC(在 exec 调用时关闭文件描述符)。
  • F_GETFL / F_SETFL: 获取或设置文件的状态标志,如 O_NONBLOCK(非阻塞模式)、O_ASYNC(异步I/O)等。
  • F_GETLK, F_SETLK, F_SETLKW: 文件锁定操作,用于实现记录锁定。

示例代码

下面是一个使用 fcntl 函数设置文件描述符为非阻塞模式的示例:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd, flags;

    // 打开文件
    fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("Open failed");
        return 1;
    }

    // 获取当前的文件状态标志
    flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) {
        perror("Fcntl get failed");
        return 1;
    }

    // 设置非阻塞标志
    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) == -1) {
        perror("Fcntl set failed");
        return 1;
    }

    // 此后的读操作将是非阻塞的
    // ...

    close(fd);
    return 0;
}

错误处理

常见的 errno 设置包括:

  • EBADF: fd 不是一个有效的文件描述符。
  • EACCES, EAGAIN: 在尝试设置锁定时可能会出现,取决于文件的访问模式和锁的类型。
  • EINVAL: cmd 命令无效,或者是在给定的情况下不适用。
  • EFAULT: 指向锁的指针超出了进程的地址空间。

注意事项

  • 使用 fcntl 时,应特别注意其影响可能会跨越进程的范围,例如文件锁和文件描述符标志在 fork 后可能会被子进程继承。
  • 更改文件描述符的状态可能会影响到所有共享该描述符的进程。

fcntl 是一个非常灵活和强大的工具,可以用来对文件描述符进行细粒度的控制,支持复杂的I/O操作和进程间通信场景。在设计使用 fcntl 的程序时,应仔细考虑其对程序行为的影响。

/*

    #include <unistd.h>
    #include <fcntl.h>

    int fcntl(int fd, int cmd, ...);
    参数:
        fd : 表示需要操作的文件描述符
        cmd: 表示对文件描述符进行如何操作
            - F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
                int ret = fcntl(fd, F_DUPFD);

            - F_GETFL : 获取指定的文件描述符文件状态flag
              获取的flag和我们通过open函数传递的flag是一个东西。

            - F_SETFL : 设置文件描述符文件状态flag
              必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改
              可选性:O_APPEND, O)NONBLOCK
                O_APPEND 表示追加数据
                NONBLOK 设置成非阻塞
        
        阻塞和非阻塞:描述的是函数调用的行为。
*/

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int main() {

    // 1.复制文件描述符
    // int fd = open("1.txt", O_RDONLY);
    // int ret = fcntl(fd, F_DUPFD);

    // 2.修改或者获取文件状态flag
    int fd = open("1.txt", O_RDWR);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    // 获取文件描述符状态flag
    int flag = fcntl(fd, F_GETFL);
    if(flag == -1) {
        perror("fcntl");
        return -1;
    }
    flag |= O_APPEND;   // flag = flag | O_APPEND

    // 修改文件描述符状态的flag,给flag加入O_APPEND这个标记
    int ret = fcntl(fd, F_SETFL, flag);
    if(ret == -1) {
        perror("fcntl");
        return -1;
    }

    char * str = "nihao";
    write(fd, str, strlen(str));

    close(fd);

    return 0;
}
  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值