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

        这一章是标准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);
ptr fread() 将读取到的数据存放在参数 ptr 指向的缓冲区中;
size fread() 从文件读取 nmemb 个数据项,每一个数据项的大小为 size 个字节,所以总共读取的数据大小为 nmemb * size 个字节。
nmemb 参数 nmemb 指定了读取数据项的个数。
stream FILE 指针。
返回值: 调用成功时返回读取到的数据项的数目(数据项数目并不等于实际读取的字节数,除非参数 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 参数意义相同。
stream FILE 指针。
返回值: 调用成功时返回写入的数据项的数目(数据项数目并不等于实际写入的字节数,除非参数 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);
stream FILE 指针。
offset lseek() 函数的 offset 参数意义相同。
whence lseek() 函数的 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);
stream FILE 指针,用于指定对应的文件,每一个文件都可以设置它对应的 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 操作( read write )。对于输出流,当 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 用于指定一个需要查看属性的文件路径。
buf struct 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 两个系统调用来获取文件属性信息。fstat lstat 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);
buf getcwd() 将内含当前工作目录绝对路径的字符串存放在 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、付费专栏及课程。

余额充值