三、文件IO——系统调用(续)
3.2.4 read 函数--- 读文件
read(由已打开的文件读取数据)
1 #include<unistd.h> 2 ssize_t read(int fd, void * buf, size_t count);
- * 函数说明
- read() 会把参数 fd 所指的文件传送 count 个字节到 buf 指针所指的内存中。
- 若参数 count 为0,则 read() 不会有作用并返回0.
- 返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动
- * 参数
- fd 为先前由 open() 或 creat() 所返回的文件描述词。
- buf 存放读取数据的缓存
- count 要求读取一次数据的字节数
- * 附加说明
- 如果顺利 read() 会返回实际读到的字节数,最好能将返回值与参数 count 做比较,若返回的字节数要比要求读到的字节数少,则有可能读到了文件尾、从管道(pipe)或终端机读取,或者是 read() 被信号中断了读取动作。当有错误发生时,则返回 -1,错误代码存入 errno 中,而文件读写位置则无法预期,
- * 错误代码
- EINTR 此调用被信号所中断
- * EAGAIN 当使用不可阻断 I/O 时(O_NONBLOCK),若无数据可读取则返回此值
- * EBADF 参数 fd 非有效的文件描述词,或该文件已关闭
- 由多种情况可使实际督导的字节数少于要求读写字节数
- 读普通文件时,在读到要求字节数之前已到达了文件尾端
- 当从终端设备读时,通常一次最多读一行
- 当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数
- 某些面向记录的设备,例如磁带,一次最多返回一个记录
- 进程由于信号造成中断
- 读操作从文件的当前位移量处开始,在成功返回前,该位移量增加实际读得的字节数
3.2.5 write 函数--- 写文件
write(将数据写入已打开的文件内)
1 #include<unistd.h> 2 ssize_t read(int fd, void * buf, size_t count);
- * 相关函数 open,read,fcntl,close,lseek,sync,fsync,fwrite
- * 函数说明
- write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。当然,文件读写位置也会随之移动。
- write 出错的一个常见原因是:磁盘已写满,或者超过了对一个给定进程的文件长度限制
- 对于普通文件,写操作从文件的当前位移量处开始。如果在打开该文件时,指定了 O_APPEND 选择项,则在每次写操作之前,将文件位移量设置在文件的当前结尾处。在一次成功写之后,该文件位移量增加实际写的字节数。
- * 返回值
- 如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。
- 返回值通常与参数 count 的值不同,如果不一样则表示出错
- * 错误代码
- EINTR 此调用被信号所中断
- * EAGAIN 当使用不可阻断 I/O 时(O_NONBLOCK),若无数据可读取则返回此值
- * EBADF 参数 fd 非有效的文件描述词,或该文件已关闭
3.2.6 lseek 函数--- 文件定位
1 #include<sys/types.h> 2 #include<unistd.h> 3 off_t lseek(int fildes,off_t offset ,int whence);
- * 相关函数 dup,open,fseek
- 函数功能:
- 定位一个已打开的文件
- * 函数说明
- 每一个已打开的文件都有一个读写位置,当打开文件时通常其读写位置是指向文件开头,若是以附加的方式打开文件(如O_APPEND),则读写位置会指向文件尾。当read()或write()时,读写位置会随之增加,lseek()便是用来控制该文件的读写位置。
- * 参数:
- @fildes 已打开的文件描述符
- * @offset 位移量。根据参数whence来移动读写位置的位移数。
- @ whence 定位的位置,为下列其中一种:
- * SEEK_SET 将该文件的位移量设置为距离文件开始处 offset 个字节
- * SEEK_CUR 将该文件的位移量设置为其当前值处 加offset 个字节,offset 可为正或负
- * SEEK_END 将该文件的位移量设置为文件长度 加offset 个字节,offset 可为正或负
- * 当whence 值为SEEK_CUR 或SEEK_END时,参数offet允许负值的出现
- * 返回值
- 若成功则返回新的文件位移量(绝对偏移量),若出错返回-1.
- * 特别使用方式
- * 1) 欲将读写位置移到文件开头时:lseek(int fildes,0,SEEK_SET);
- * 2) 欲将读写位置移到文件尾时:lseek(int fildes,0,SEEK_END);
- * 3) 想要取得目前文件位置时:lseek(int fildes,0,SEEK_CUR);
- * 附件说明
- Linux系统不允许lseek()对tty装置作用,此项动作会令lseek()返回ESPIPE。
- 其他使用方式:
- lseek 也可用来确定所涉及的文件是否可以设置位移量。如果文件描述符引用的是一个管道或FIFO,则 lseek 返回 -1,并将 errno 设置为 EPIPE
- 每个打开文件都由一个与其相关联的“当前文件偏移量”。它是一个非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读或写的字节数。按系统默认,当打开一个文件时,除非指定 O_APPEND 选择项,否则该位移量被设置为0
3.3 例子
3.3.1 文件读写
io.h
1 #ifndef __IO_H__ 2 #define __IO_H__ 3 4 extern void copy(int fdin, int fdout); 5 6 #endif
io.c
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <unistd.h> 5 #include "io.h" 6 #include <string.h> 7 #include <errno.h> 8 #include <stdlib.h> 9 #include <stdio.h> 10 #include <fcntl.h> 11 12 13 #define BUFFER_LEN 1024 14 15 /* 文件的读写拷贝 */ 16 void copy(int fdin, int fdout) 17 { 18 char buff[BUFFER_LEN]; 19 ssize_t size; 20 21 while((size = read(fdin, buff, BUFFER_LEN)) > 0) { //从 fdin 中读取 BUFFER_LEN 个字节存放入 buff 中 22 if(write(fdout, buff, size) != size) { 23 fprintf(stderr, "write error: %s\n", strerror(errno)); 24 exit(1); 25 } 26 } 27 if(size < 0) { 28 fprintf(stderr, "read error:%s\n", strerror(errno)); 29 exit(1); // 相当于 return 1; 30 } 31 }
将 io.c 编译成 .o 文件,供其他模块进行调用
1 gcc -o obj/io.o -Iinclude -c src/io.c
- -o:指定输出的目录和文件格式
- -Iinclude:指定包含的头文件目录,-I使指定包含头文件,后面的inlcude 即是头文件所在的目录,也可以采用绝对路径
- -c:指定源文件
cp.c
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <unistd.h> 5 #include "io.h" 6 #include <string.h> 7 #include <errno.h> 8 #include <stdlib.h> 9 #include <stdio.h> 10 #include <fcntl.h> 11 12 int main(int argc, char *argv[]) 13 { 14 if(argc != 3) { 15 fprintf(stderr, "usage: %s srcfile destfile\n", argv[0]); 16 exit(1); 17 } 18 19 int fdin; 20 int fdout; 21 22 //打开一个待读取的文件 23 fdin = open(argv[1], O_RDONLY); 24 if(fdin < 0) { 25 fprintf(stderr, "open error: %s\n", strerror(errno)); 26 exit(1); 27 } else { 28 printf("open file: %d\n", fdin); 29 } 30 31 //打开一个待写入的文件 32 fdout = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0777); 33 if(fdout < 0) { 34 fprintf(stderr, "open error: %s\n", strerror(errno)); 35 exit(1); 36 } else { 37 printf("open file: %d\n", fdout); 38 } 39 40 //文件复制 41 copy(fdin, fdout); 42 43 close(fdin); 44 close(fdout); 45 46 return 0; 47 }
编译:
1 gcc -o bin/cp -Iinclude obj/io.o src/cp.c
运行:
3.3.2 lseek 文件定位
(1)例子1
打印要读取的文件的总的大小
每次写完后,文件定位的位置
io.c
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <unistd.h> 5 #include "io.h" 6 #include <string.h> 7 #include <errno.h> 8 #include <stdlib.h> 9 #include <stdio.h> 10 #include <fcntl.h> 11 12 13 #define BUFFER_LEN 1024 14 15 /* 文件的读写拷贝 */ 16 void copy(int fdin, int fdout) 17 { 18 char buff[BUFFER_LEN]; 19 ssize_t size; 20 21 // printf("file length: %ld\n", lseek(fdin, 0L, SEEK_END));//将文件定位到文件尾部,偏移量为0L 22 // lseek(fdin, 0L, SEEK_SET);// 定位到文件开头 23 24 while((size = read(fdin, buff, BUFFER_LEN)) > 0) { //从 fdin 中读取 BUFFER_LEN 个字节存放入 buff 中 25 printf("current: %ld\n", lseek(fdin, 0L, SEEK_CUR)); //打印当前位置 26 27 if(write(fdout, buff, size) != size) { 28 fprintf(stderr, "write error: %s\n", strerror(errno)); 29 exit(1); 30 } 31 } 32 if(size < 0) { 33 fprintf(stderr, "read error:%s\n", strerror(errno)); 34 exit(1); // 相当于 return 1; 35 } 36 }
cp.c
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <unistd.h> 5 #include "io.h" 6 #include <string.h> 7 #include <errno.h> 8 #include <stdlib.h> 9 #include <stdio.h> 10 #include <fcntl.h> 11 12 int main(int argc, char *argv[]) 13 { 14 if(argc != 3) { 15 fprintf(stderr, "usage: %s srcfile destfile\n", argv[0]); 16 exit(1); 17 } 18 19 int fdin; 20 int fdout; 21 off_t ret; 22 23 //打开一个待读取的文件 24 fdin = open(argv[1], O_RDONLY); 25 if(fdin < 0) { 26 fprintf(stderr, "open error: %s\n", strerror(errno)); 27 exit(1); 28 } else { 29 printf("open file: %d\n", fdin); 30 } 31 32 ret = lseek(fdin, 0L, SEEK_END); //将文件定位到文件末尾 33 if(ret == -1) { 34 fprintf(stderr, "lseek error: %s\n", strerror(errno)); 35 exit(1); 36 } 37 printf("file length: %ld\n", ret); 38 39 ret = lseek(fdin, 0L, SEEK_SET);// 定位到文件开头 40 if(ret == -1) { 41 fprintf(stderr, "lseek error: %s\n", strerror(errno)); 42 exit(1); 43 } 44 45 //打开一个待写入的文件 46 fdout = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0777); 47 if(fdout < 0) { 48 fprintf(stderr, "open error: %s\n", strerror(errno)); 49 exit(1); 50 } else { 51 printf("open file: %d\n", fdout); 52 } 53 54 //文件复制 55 copy(fdin, fdout); 56 57 close(fdin); 58 close(fdout); 59 60 return 0; 61 }
编译:
1 gcc -o bin/cp -Iinclude src/io.c src/cp.c
执行:
(2)空洞文件制作
空洞文件就是从文件尾部跳出若干个字节再写入信息,中间空出来的信息就是一个空洞
hole_file.c
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <unistd.h> 5 #include <string.h> 6 #include <errno.h> 7 #include <stdlib.h> 8 #include <stdio.h> 9 #include <fcntl.h> 10 11 //生成空洞文件 12 char *buff = "0123456789"; 13 14 15 int main(int argc, char *argv[]) 16 { 17 if(argc < 2) { 18 fprintf(stderr, "usage: %s [file]\n", argv[0]); 19 exit(1); 20 } 21 22 int fd; 23 size_t size; 24 25 fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0777); 26 if(fd < 0) { 27 perror("open error"); 28 exit(1); 29 } 30 31 // 写入多少个字节,一个字符串占多少个字节 32 size = strlen(buff) * sizeof(char); 33 34 // 将字符串写入到空洞文件中 35 if(write(fd, buff, size) != size) { 36 perror("write error"); 37 exit(1); 38 } 39 40 // 定位到文件尾部的 10 个字节处 41 if(lseek(fd, 10L, SEEK_END) == -1) { 42 perror("lseek error"); 43 exit(1); 44 } 45 46 // 从文件尾部的10个字节处再写入字符串 47 if(write(fd, buff, size) != size) { 48 perror("write error"); 49 exit(1); 50 } 51 52 close(fd); 53 return 0; 54 55 }
编译:
1 gcc -o bin/hole_file src/hole_file.c
执行: