1. 文件的打开和关闭
1.1 以只读的方式打开文件
t_file.h
#ifndef T_FILE_H
#define T_FILE_H
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#endif
t_stdio.h
#ifndef T_STDIO_H_
#define T_STDIO_H_
#include <stdio.h>
#define E_MSG(STRING, VAL) do{perror(STRING); return(VAL);}while(0)
#endif
sudo cp t_file.h t_stdio.h /usr/include/
r_file.c
// #include <sys/types.h>
// #include <sys/stat.h>
// #include <fcntl.h>
// #include <unistd.h>
#include <t_file.h>
#include <t_stdio.h>
int main(int argc, char* argv[]) {
// 以只读的方式打开文件
// 返回文件描述符 fd
int fd = open(argv[1], O_RDONLY);
if(fd == -1)E_MSG("open", -1);
//
printf("file open success... %s\n", argv[1]);
// 关闭文件描述符 fd
close(fd);
}
$ ls -l *
-rw-rw-r-- 1 moonx moonx 0 12月 10 20:01 tt
-rw-r--r-- 1 moonx moonx 0 12月 10 20:07 tx
--w--w--w- 1 moonx moonx 0 12月 11 16:02 ty
$ gcc r_file.c
$ a.out tt
file open success... tt
$ a.out tx
file open success... tx
$ a.out ty
open: Permission denied
$ a.out t1
open: No such file or directory
1.2 以写的方式打开文件
- 文件不存在, 创建文件,指定新文件的权限为0644,如果文件存在,将文件内容清空为0
wt_file.c
#include <t_file.h>
#include <t_stdio.h>
int main(int argc, char* argv[]) {
// 打开文件, 以写的方式打开文件, 文件不存在创建文件
// 并指定新文件的权限为0644,如果文件存在, 将文件清空
int flags = O_WRONLY| O_CREAT| O_TRUNC;
int mode = 0644;
int fd = open(argv[1],flags, mode);
if(fd == -1)E_MSG("open", -1);
//
printf("file open success... %s\n", argv[1]);
close(fd);
}
$ gcc wt_file.c
$ ./a.out hello
file open success... hello
$ ls hello -l
-rw-r--r-- 1 moonx moonx 0 12月 11 16:46 hello
$ echo 111 > hello
$ ./a.out hello
file open success... hello
$ cat hello
- 文件不存在,创建文件, 指定新文件的权限为0644,如果文件存在报错
we_file.c
#include <t_file.h>
#include <t_stdio.h>
int main(int argc, char* argv[]) {
// 打开文件, 以写的方式打开文件, 文件不存在创建文件
// 并指定新文件的权限为0644,如果文件存在, 报错
// 如果文件存在,O_CREAT 和 O_EXCL一起使用,调用open返回失败
int flags = O_WRONLY| O_CREAT| O_EXCL;
int mode = 0644;
int fd = open(argv[1],flags, mode);
if(fd == -1)E_MSG("open", -1);
//
printf("file open success... %s\n", argv[1]);
close(fd);
}
$ gcc we_file.c
$ a.out hello
open: File exists
$ rm hello
$ a.out hello
file open success... hello
$ ls hello
hello
$ ls -l hello
-rw-r--r-- 1 moonx moonx 0 12月 11 17:48 hello
2 文件的读写
2.1 read
- count 是请求系统去读取count个字节。比如定义的buf有128个字节,count也就同样给128,说明下buf的大小。如果读了130就超过buf的空间了,造成两个字节的浪费。 所以count一般和buf空间的大小一致。
- 返回值是实际读到的字节数,它可能比count小,这不是错误,仅仅说明已经接近文件末尾。( 比如文件一共有512个字节, 一次读100,连续读了5次,读到500个字节, 第六次读时, 只会读12个字节,返回值是12, 是比count少的,这不代表错误,仅仅代表接近了文件的末尾)
- read读取到的字节数不包含结束符,而是包含换行符
2.2 write
- 把buf里的数据写到跟文件描述符fd相关的那个文件里
- count也是向系统请求写入文件的的字节数 , buf 里可能有很多字节,count会指定只把buf的某些数据写到文件当中
- 如果返回值(实际写入的字节数)比count少,这也不代表错误,仅仅代表磁盘满了,再写不进去了
2.3 lseek
2.4 代码示例
- cp scr_file dst_file 讲scr_file 的内容复制到dst_file文件当中, 代码实现cp命令功能
t_cp.c
#include <t_stdio.h>
#include <t_file.h>
//将源文件中的内容复制到目标文件当中
//函数的返回值表示实际拷贝的字节数
int cp_file(int s_fd, int d_fd){
int total = 0; //记录世界拷贝的字节数
// r代表读取到的字节数,
// w代表实际写入到目标文件的字节数
int r,w;
char buf[128];
// 从源文件里读出来到buf里的数据,不能保证一次就写到目标文件当中的,
// 在写的过程当中有可能发生意外,比如被信号打断等
// 所以这里用了两个个循环来处理意外发生的情况
while ((r = read(s_fd, buf, 128)) > 0) { //条件为真表示实际读取到了数据
// 这个tmp的作用是,在第二个while循环中,第二次向目标文件中写入数据时不从buf的的一个字节开始写
char *tmp = buf;
while (r > 0) { //保证每次读取到的数据完全写入到目标文件当中
w = write(d_fd, tmp, r);
r = r -w; //r 写入之后代表剩下的, r>0 说明本次没有写完,需要继续回去写
total += w;
tmp += w;
// buf += w; //记住buf是数组的名字,常量,这种操作是错的
}
}
return total;
}
// argv[1]表示源文件的名字, argv[2]表示目标文件的名字
int main(int argc, char *argv[]) {
//框架
//1. 以只读的方式打开源文件
int src_fd = open(argv[1], O_RDONLY);
//2.以写的方式打开目标文件
//如果文件不存在,创建文件,制定权限为0664. 如果存在将文件内容清空
int flags = O_WRONLY | O_CREAT | O_TRUNC;
int dst_fd = open(argv[2], flags, 0644);
if(src_fd == -1| dst_fd == -1) E_MSG("open", -1);
//3.将源文件中的内容复制到目标文件当中
cp_file(src_fd, dst_fd);
//4.关闭文件描述符
close(src_fd);
close(dst_fd);
}
$ gcc t_cp.c
$ ./a.out t_cp.c tt
$ echo hello > tt
$ ./a.out tt tx
$ cat tx
hello
3 复制文件描述符
3.1 dup
3.2 dup2
3.3 案例分析
- 文件描述符表下标0, 1, 2 分别代表 标准输入, 标准输出, 标准错误输出
- /home/tarena/tt是新打开的文件(找文件描述符表指针数组中未被使用的最小的下标3, 把struct file类型的对象(files)的地址放进下标对应的存储区里),文件对应的文件描述符是3
- 如右图,把文件描述符表3里的内容复制到文件描述符表1中,标准输出就输出到 /home/tarena/tt 里(也就是输出重定向)
- 图中右边中可以看到,把文件描述符表1中的内容复制(dup)到表中下标最小且未被使用的文件描述符对应的位置, 这时文件描述符4和文件描述符1里的内容就指向了标准输出的sruct_file对象,现在往1和4里写都会写到屏幕上
- 图中左边中可以看到, 把文件描述符表项3中的内容复制(dup2,目标文件描述符里有内容)文件描述符表項1中。这时只有4指向了标准输出,1和3都指向打开的tt对应struct_file类型对象(这个对象里的引用计数就是2了)。这时往1和3里写都是一样的效果, 会写入tt。
- 图中右边可以看到,关闭文件描述符表3,然后就只有1里的地址指向tt对应的struc_file对象。到这里就完成文件描述符的复制,也就改变了进程的输出的方向
- 如图中左边所示,把文件描述符表项4的内容又拷贝(dup2)到1中,现在就没有文件描述符指向tt对应的struct_file。,然后再close(4)就到了图中右边。这样就又恢复了原来的标准输出,输出到显示器。
3.4 代码示例
#include <t_stdio.h>
#include <t_file.h>
#include <string.h>
// 重定向文件名通过argv[1]
int main(int argc, char *argv[]) {
char *msg = "this is a test...\n";
// 图一
int flags = O_WRONLY | O_CREAT | O_TRUNC;
int fd = open(argv[1], flags, 0644); //fd ==3
if(fd == -1) E_MSG("open", -1);
int s_fd = dup(1); //讲标准输出保存到sfd中,s_fd ==4
// 图二
//将打开的文件描述符,复制到标准输出
// 如果1是打开的,先将1关闭,再把fd考过去
dup2(fd, 1);
//关闭文件描述符
close(fd);
// 通过标准输出将信息输出到文件
write(1, msg, strlen(msg));
// 图三
//恢复标准输出,重新指向显示器
dup2(s_fd, 1);
close(4);
write(1, msg, strlen(msg));
}
moonx@moonx:~/Desktop/C/c++$ gcc direct.c
moonx@moonx:~/Desktop/C/c++$ ./a.out 11
this is a test...
moonx@moonx:~/Desktop/C/c++$ cat 11
this is a test...