Linux应用开发2 标准C库和文件操作(linux下一切皆文件)

本文详细介绍了Linux下标准IO库的文件操作,包括fopen()、fclose()、unlink()、remove()、rename()等函数,以及文件的读写、重命名、删除和属性获取。讲解了fread()、fwrite()用于文件读写,fseek()和ftell()管理文件位置,还涵盖了错误检查、文件大小计算及缓冲区管理。此外,讨论了文件权限、目录操作如mkdir()、rmdir(),并阐述了stat()、fstat()、lstat()用于获取文件属性,以及进程的当前工作目录管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        这一章是标准IO库的打开、关闭、读写函数,以及对文件的一系列操作函数
        耐心看完,配合例子快速入门
        之所以要学文件操作,因为linux下一切皆文件,比如你编写的驱动文件也就是dev目录下的各个文件而已,所以对文件的操作函数要学!
        标准 I/O 库则是标准 C 库中用于文件 I/O 操作(譬如读文件、写文件等)相关的一系列库函数的集合,头文件<stdio.h>
FILE 指针 ,应用于标准IO库中的文件描述符
打开文件 fopen()
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
path参数 path 指向文件路径,可以是绝对路径、也可以是相对路径。
mode参数 mode 指定了对该文件的读写权限,是一个字符串,稍后介绍。
返回值:调用成功返回一个指向 FILE 类型对象的指针(FILE *),该指针与打开或创建的文件相关联,
后续的标准 I/O 操作将围绕 FILE 指针进行。如果失败则返回 NULL,并设置 errno 以指示错误原因。

删除文件unlink()函数

前面给大家介绍 link 函数,用于创建一个硬链接文件,创建硬链接时,inode 节点上的链接数就会增加;unlink()的作用与 link()相反,unlink()系统调用用于移除/删除一个硬链接(从其父级目录下删除该目录条目)。

#include <unistd.h>
int unlink(const char *pathname);
pathname需要删除的文件路径,可使用相对路径、也可使用绝对路径,如果 pathname 参数指定的文件不存在,则调用 unlink()失败。
返回值:成功返回 0;失败将返回-1,并设置 errno

删除文件remove() 函数(用于移除一个文件或空目录

与 unlink()rmdir()一样,remove()不对软链接进行解引用操作,若 pathname 参数指定的是一个软链接文件,则 remove()会删除链接文件本身、而非所指向的文件。

pathname 参数指定的是一个非目录文件,那么 remove()去调用 unlink(),如果 pathname 参数指定的是一个目录,那么 remove()去调用 rmdir()。(这是个boss)

#include <stdio.h>
int remove(const char *pathname);

pathname需要删除的文件或目录路径,可以是相对路径、也可是决定路径。
返回值:成功返回 0;失败将返回-1,并设置 errno

文件重命名rename()函数

rename()既可以对文件进行重命名,又可以将文件移至同一文件系统中的另一个目录下
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);

oldpath原文件路径。
newpath新文件路径。
返回值:成功返回 0;失败将返回-1,并设置 errno
调用 rename()会将现有的一个路径名 oldpath 重命名newpath 参数所指定的路径名。rename()调用仅操作目录条目,而不移动文件数据(不改变文件 inode 编号、不移动文件数据块中存储的内容),重命名既不影响指向该文件的其它硬链接,也不影响已经打开该文件的进程(譬如,在重命名之前该文件已被其它进程打开了,而且还未被关闭)。
根据 oldpathnewpath 的不同,有以下不同的情况需要进行说明:
newpath 参数指定的文件或目录已经存在,则将其覆盖;
newpath oldpath 指向同一个文件,则不发生变化(且调用成功)。
rename()系统调用对其两个参数中的软链接均不进行解引用。如果 oldpath 是一个软链接,那么将重命名该软链接;如果 newpath 是一个软链接,则会将其移除、被覆盖。
如果 oldpath 指代文件,而非目录,那么就不能将 newpath 指定为一个目录的路径名。要想重命名一个文件到某一个目录下,newpath 必须包含新的文件名。
如果 oldpath 指代为一个目录,在这种情况下,newpath 要么不存在,要么必须指定为一个空目录。
oldpath newpath 所指代的文件必须位于同一文件系统。由前面的介绍,可以得出此结论!
不能对.(当前目录)和..(上一级目录)进行重命名。
//可以重命名,可以用来移动位置
ret = rename("./test_file", "./new_file");

新建文件的权限

虽然调用 fopen()函数新建文件时无法手动指定文件的权限,但却有一个默认值:

S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH (0666)

fclose()关闭文件

#include <stdio.h>

int fclose(FILE *stream);

读文件和写文件

fread()fwrite()库函数对文件进行读、写操作
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
ptrfread()将读取到的数据存放在参数 ptr 指向的缓冲区中;
sizefread()从文件读取 nmemb 个数据项,每一个数据项的大小为 size 个字节,所以总共读取的数据大小为 nmemb * size 个字节。
nmemb参数 nmemb 指定了读取数据项的个数。
streamFILE 指针。
返回值:调用成功时返回读取到的数据项的数目(数据项数目并不等于实际读取的字节数,除非参数 size 等于 1);如果发生错误或到达文件末尾,则 fread()返回的值将小于参数 nmemb,那么到底发生了错误还是到达了文件末尾,fread()不能区分文件结尾和错误,究竟是哪一种情况,此时可以使用 ferror()feof() 函数来判断,具体参考 4.7 小节内容的介绍。
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr将参数 ptr 指向的缓冲区中的数据写入到文件中。
size参数 size 指定了每个数据项的字节大小,与 fread()函数的 size 参数意义相同。
nmemb参数 nmemb 指定了写入的数据项个数,与 fread()函数的 nmemb 参数意义相同。
streamFILE 指针。
返回值:调用成功时返回写入的数据项的数目(数据项数目并不等于实际写入的字节数,除非参数 size等于 1);如果发生错误,则 fwrite()返回的值将小于参数 nmemb(或者等于0
 /* 打开文件 */
 if (NULL == (fp = fopen("./test_file", "w"))) {
 perror("fopen error");
 exit(-1);
 }
 /* 写入数据 */
 if (sizeof(buf) >
 fwrite(buf, 1, sizeof(buf), fp)) {
 printf("fwrite error\n");
 fclose(fp);
 exit(-1);
 }
fseek 定位(设置文件读写位置偏移量)
lseek() 用于文件 I/O,而库函数 fseek()则用于标准 I/O
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
streamFILE 指针。
offsetlseek()函数的 offset 参数意义相同。
whencelseek()函数的 whence 参数意义相同。
返回值:成功返回 0;发生错误将返回-1,并且会设置 errno 以指示错误原因;与 lseek()函数的返回值意义不同,
        这里要注意! 调用库函数 fread()、fwrite()读写文件时,文件的读写位置偏移量会自动递增,使用 fseek()可手动设置文件当前的读写位置偏移量。
ftell()函数(获取文件当前的读写位置偏移量)
#include <stdio.h>
long ftell(FILE *stream);
参数 stream 指向对应的文件,函数调用成功将返回当前读写位置偏移量;调用失败将返回-1,并会设置 errno 以指示错误原因。

通过 fseek()和 ftell()来计算出文件的大小

#include <stdio.h>
#include <stdlib.h>
int main(void) {
 FILE *fp = NULL;
 int ret;
 /* 打开文件 */
 if (NULL == (fp = fopen("./testApp.c", "r"))) {
 perror("fopen error");
 exit(-1);
 }
 printf("文件打开成功!\n");
 /* 将读写位置移动到文件末尾 */
 if (0 > fseek(fp, 0, SEEK_END)) {
 perror("fseek error");
 fclose(fp);
 exit(-1);
 }
 /* 获取当前位置偏移量 */
 if (0 > (ret = ftell(fp))) {
 perror("ftell error");
 fclose(fp);
 exit(-1);
 }
 printf("文件大小: %d 个字节\n", ret);
 /* 关闭文件 */
 fclose(fp);
 exit(0);
}

检查或复位状态

        调用 fread()读取数据时,如果返回值小于参数 nmemb 所指定的值,表示发生了错误或者已经到了文件末尾(文件结束 end-of-file),但 fread()无法具体确定是哪一种情况;在这种情况下,可以通过判断错误标志或 end-of-file 标志来确定具体的情况。

feof()函数
        库函数 feof()用于测试参数 stream 所指文件的 end-of-file 标志,如果 end-of-file 标志被设置了,则调用 feof()函数将返回一个非零值,如果 end-of-file 标志没有被设置,则返回 0。当文件的读写位置移动到了文件末尾时,end-of-file 标志将会被设置。
#include <stdio.h>
int feof(FILE *stream);
ferror()函数
        库函数 ferror()用于测试参数 stream 所指文件的错误标志,如果错误标志被设置了,则调用 ferror()函数将返回一个非零值,如果错误标志没有被设置,则返回 0。当对文件的 I/O 操作发生错误时,错误标志将会被设置。
#include <stdio.h>
int ferror(FILE *stream);
clearerr()函数
        库函数 clearerr()用于清除 end-of-file 标志和错误标志,当调用 feof()ferror()校验这些标志后,通常需要清除这些标志,避免下次校验时使用到的是上一次设置的值,此时可以手动调用 clearerr()函数清除标志。
        此函数没有返回值,调用将总是会成功!
        对于 end-of-file 标志,除了使用 clearerr()显式清除之外,当调用 fseek()成功时也会清除文件的 end-of-file 标志。
#include <stdio.h>
void clearerr(FILE *stream);

格式化 I/O:  就是Java的输入输出函数

输出 printf()

        C 库函数提供了 5 个格式化输出函数,包括:printf()、fprintf()、dprintf()、sprintf()、snprintf(),其函数定义如下所示:
1)printf()函数用于将格式化数据写入到标准输出;
2)dprintf()fprintf()函数用于将格式化数据写入到指定的文件中,两者不同之处在于,fprintf()使用 FILE 指针指定对应的文件、而 dprintf()则使用文件描述符 fd 指定对应的文件;
3)sprintf()snprintf()函数可将格式化的数据存储在用户指定的缓冲区 buf 中。
#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *buf, const char *format, ...);
int snprintf(char *buf, size_t size, const char *format, ...);
        有一个共同的参数 format,这是一个字符串,称为格式控制字符串,用于指定后续的参数如何进行格式转换,所以才把这些函数称为格式化输出

 

输入scanf()

        C 库函数提供了 3 个格式化输入函数,包括:scanf()、fscanf()、sscanf()
1)scanf()函数可将用户输入(标准输入)的数据进行格式化转换;
2)fscanf()函数从 FILE 指针指定文件中读取数据,并将数据进行格式化转换;
3)sscanf()函数从参数 str 所指向的字符串中读取数据,并将数据进行格式化转换。
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);

标准 I/O stdio 缓冲

setvbuf()函数
        调用 setvbuf()库函数可以对文件的 stdio 缓冲区进行设置,譬如缓冲区的缓冲模式、缓冲区的大小、起始地址等。
        
#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
streamFILE 指针,用于指定对应的文件,每一个文件都可以设置它对应的 stdio 缓冲区。
buf如果参数 buf 不为 NULL,那么 buf 指向 size 大小的内存区域将作为该文件的 stdio 缓冲区,因为stdio 库会使用 buf 指向的缓冲区,所以应该以动态(分配在堆内存,譬如 malloc,在 7.6 小节介绍)或静态的方式在堆中为该缓冲区分配一块空间,而不是分配在栈上的函数内的自动变量(局部变量)。如果 buf 等于 NULL,那么 stdio 库会自动分配一块空间作为该文件的 stdio 缓冲区(除非参数 mode 配置为非缓冲模式)。
mode参数 mode 用于指定缓冲区的缓冲类型,可取值如下:
_IONBF不对 I/O 进行缓冲(无缓冲)。意味着每个标准 I/O 函数将立即调用 write()或者 read(),并且忽略 buf size 参数,可以分别指定两个参数为 NULL 0。标准错误 stderr 默认属于这一种类型,从而保证错误信息能够立即输出。
_IOLBF采用行缓冲 I/O。在这种情况下,当在输入或输出中遇到换行符"\n"时,标准 I/O 才会执行文件 I/O 操作。对于输出流,在输出一个换行符前将数据缓存(除非缓冲区已经被填满),当输出换行符时,再将这一行数据通过文件 I/O write()函数刷入到内核缓冲区中;对于输入流,每次读取一行数据。对于终端设备默认采用的就是行缓冲模式,譬如标准输入和标准输出。
_IOFBF采用全缓冲 I/O。在这种情况下,在填满 stdio 缓冲区后才进行文件 I/O 操作(readwrite)。对于输出流,当 fwrite 写入文件的数据填满缓冲区时,才调用 write()stdio 缓冲区中的数据刷入内核缓冲区;对于输入流,每次读取 stdio 缓冲区大小个字节数据。默认普通磁盘上的常规文件默认常用这种缓冲模式。
size指定缓冲区的大小。
返回值:成功返回 0,失败将返回一个非 0 值,并且会设置 errno 来指示错误原因。需要注意的是,当 stdio 缓冲区中的数据被刷入到内核缓冲区或被读取之后,这些数据就不会存在于缓冲区中了,数据被刷入了内核缓冲区或被读走了。
setbuf()函数
setbuf()函数构建与 setvbuf()之上,执行类似的任务
#include <stdio.h>
void setbuf(FILE *stream, char *buf);
setbuffer()函数
setbuffer()函数类似于 setbuf(),但允许调用者指定 buf 缓冲区的大小
#include <stdio.h>
void setbuffer(FILE *stream, char *buf, size_t size);

文件属性与目录

Linux下文件类型
1 普通文件可以分为两大类:文本文件和二进制文件。
2 目录文件(文件夹)
3 字符设备文件和块设备文件
字符设备文件一般存放在 Linux 系统/dev/目录下,所以/dev 也称为虚拟文件系统 devfs
符号链接文件link :指向
管道文件pipe : 进程通讯
套接字文件socket :网络通讯

stat 函数(man 2 stat)信息存入struct stat结构体

stat 函数是 Linux 中的系统调用,用于获取文件相关的信息

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *buf);
pathname用于指定一个需要查看属性的文件路径。
bufstruct stat 类型指针,用于指向一个 struct stat 结构体变量。调用 stat 函数的时候需要传入一个 struct stat 变量的指针,获取到的文件属性信息就记录在 struct stat 结构体中,稍后给大家介绍 struct stat 结构体中
有记录了哪些信息。
返回值:成功返回 0;失败返回-1,并设置 error
struct stat
{
 dev_t st_dev; /* 文件所在设备的 ID */
 ino_t st_ino; /* 文件对应 inode 节点编号 */
 mode_t st_mode; /* 文件对应的模式 */
 nlink_t st_nlink; /* 文件的链接数 */
 uid_t st_uid; /* 文件所有者的用户 ID */
 gid_t st_gid; /* 文件所有者的组 ID */
 dev_t st_rdev; /* 设备号(指针对设备文件) */
 off_t st_size; /* 文件大小(以字节为单位) */
 blksize_t st_blksize; /* 文件内容存储的块大小 */
 blkcnt_t st_blocks; /* 文件内容所占块数 */
 struct timespec st_atim; /* 文件最后被访问的时间 */
 struct timespec st_mtim; /* 文件内容最后被修改的时间 */
 struct timespec st_ctim; /* 文件状态最后被改变的时间 */
};

判断文件类型

其中st_mode变量比较特殊,可以通过st_mode判断文件类型

S_IFSOCK  0140000 socket(套接字文件)
S_IFLNK 0120000 symbolic link(链接文件)
S_IFREG 0100000 regular file(普通文件)
S_IFBLK 0060000 block device(块设备文件)
S_IFDIR 0040000 directory(目录)
S_IFCHR 0020000 character device(字符设备文件)
S_IFIFO 0010000 FIFO(管道文件)
S_ISREG(m) #判断是不是普通文件,如果是返回 true,否则返回 false
S_ISDIR(m) #判断是不是目录,如果是返回 true,否则返回 false
S_ISCHR(m) #判断是不是字符设备文件,如果是返回 true,否则返回 false
S_ISBLK(m) #判断是不是块设备文件,如果是返回 true,否则返回 false
S_ISFIFO(m) #判断是不是管道文件,如果是返回 true,否则返回 false
S_ISLNK(m) #判断是不是链接文件,如果是返回 true,否则返回 false
S_ISSOCK(m) #判断是不是套接字文件,如果是返回 true,否则返回 false

eg 判断文件所有者对该文件是否具有可执行权限 

if (st.st_mode & S_IXUSR) {
//有权限
} else {
//无权限
}

eg 通过 st_mode 变量判断文件类型 or 使用 Linux 系统封装好的宏来进行判断

/* 判断是不是普通文件 */
if ((st.st_mode & S_IFMT) == S_IFREG) {
/* 是 */
}
/* 判断是不是链接文件 */
if ((st.st_mode & S_IFMT) == S_IFLNK) {
/* 是 */
}
/* 判断是不是普通文件 */
if (S_ISREG(st.st_mode)) {
/* 是 */
}
/* 判断是不是目录 */
if (S_ISDIR(st.st_mode)) {
/* 是 */
}

fstat lstat 函数

        前面给大家介绍了 stat 系统调用,起始除了 stat 函数之外,还可以使用 fstat lstat 两个系统调用来获取文件属性信息。fstatlstat stat 的作用一样,但是参数、细节方面有些许不同。
除此之外还有

文件属主(因为linux是一个多用户系统)

文件的访问权限(chmod 777)

文件的时间属性

符号链接(软连接)与硬连接

目录(文件夹)

        目录在Linux中算是一种特殊的文件形式,因此标准C库中会有打开、创建文件夹、删除文件夹、读取文件夹以及遍历文件夹中的文件等函数

mkdir 函数(创建目录)
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode)
pathname需要创建的目录路径。
mode新建目录的权限设置,设置方式与 open 函数的 mode 参数一样,最终权限为(mode & ~umask)。
返回值:成功返回 0;失败将返回-1,并会设置 errno。 pathname 参数指定的新建目录的路径,该路径名可以是相对路径,也可以是绝对路径,若指定的路径名已经存在,则调用 mkdir()将会失败
rmdir 函数(删除目录)
#include <unistd.h>
int rmdir(const char *pathname);

pathname需要删除的目录对应的路径名,并且该目录必须是一个空目录,也就是该目录下只有...这两个目录项;pathname 指定的路径名不能是软链接文件,即使该链接文件指向了一个空目录。
返回值:成功返回 0;失败将返回-1,并会设置 errno

opendir函数(打开目录)

opendir()函数用于打开一个目录,并返回指向该目录的句柄,供后续操作使用

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

name指定需要打开的目录路径名,可以是绝对路径,也可以是相对路径。
返回值:成功将返回指向该目录的句柄,一个 DIR 指针(其实质是一个结构体指针),其作用类似于open函数返回的文件描述符fd,后续对该目录的操作需要使用该DIR指针变量;若调用失败,则返回NULL

closedir 函数(关闭目录)

closedir()函数用于关闭处于打开状态的目录,同时释放它所使用的资源
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
dirp目录句柄。
返回值:成功返回 0;失败将返回-1,并设置 errno

readdir函数(读取目录)

readdir()用于读取目录,获取目录下所有文件的名称以及对应 inode 号。
#include <dirent.h>
struct dirent *readdir(DIR *dirp);

dirp目录句柄 DIR 指针。
返回值:返回一个指向 struct dirent 结构体的指针,该结构体表示 dirp 指向的目录流中的下一个目录条目。在到达目录流的末尾或发生错误时,它返回 NULL
rewinddir 函数(方便下次读取从头开始,重置目录流)
rewinddir()是 C 库函数,可将目录流重置为目录起点,以便对 readdir()的下一次调用将从目录列表中的第一个文件开始
#include <sys/types.h>
#include <dirent.h>
void rewinddir(DIR *dirp);
dirp目录句柄。
返回值:无返回值。

进程的当前工作目录

        Linux 下的每一个进程都有自己的当前工作目录(current working directory),当前工作目录是该进程解析、搜索相对路径名的起点(不是以" / "斜杆开头的绝对路径)。譬如,代码中调用 open 函数打开文件时,传入的文件路径使用相对路径方式进行表示,那么该进程解析这个相对路径名时、会以进程的当前工作目录作为参考目录。
getcwd 函数(获取进程当前的工作目录)
#include <unistd.h>
char *getcwd(char *buf, size_t size);
bufgetcwd()将内含当前工作目录绝对路径的字符串存放在 buf 缓冲区中。
size缓冲区的大小,分配的缓冲区大小必须要大于字符串长度,否则调用将会失败。
返回值:如果调用成功将返回指向 buf 的指针,失败将返回 NULL,并设置 errno
ptr = getcwd(buf, sizeof(buf));
printf("Current working directory: %s\n", buf);
chdir()和 fchdir()函数(改变当前工作目录)
        此两函数的区别在于,指定目录的方式不同,chdir()以路径的方式进行指定,而 fchdir()则是通过文件描述符,文件描述符可调用 open()打开相应的目录时获得。
#include <unistd.h>
int chdir(const char *path);
int fchdir(int fd);
path将进程的当前工作目录更改为 path 参数指定的目录,可以是绝对路径、也可以是相对路径,指定的目录必须要存在,否则会报错。
fd将进程的当前工作目录更改为 fd 文件描述符所指定的目录(譬如使用 open 函数打开一个目录)。
返回值:成功均返回 0;失败均返回-1,并设置 errno
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值