目录、文件的相关操作
47P-系统编程阶段说在前面的话
系统调用 内核提供的函数
库调用 程序库中的函数
48P-open函数
manpage 第二卷,open函数如下,有两个版本的
返回一个文件描述符,理解为整数,出错返回-1
pathname 文件路径
flags 权限控制,只读,只写,读写。 O_RDONLY, O_WRONLY, O_RDWR
第二个open
多了一个mode参数,用来指定文件的权限,数字设定法
文件权限 = mode & ~umask
open常见错误:
- 打开文件不存在
- 以写方式打开只读文件(权限问题)
- 以只写方式打开目录
当open出错时,程序会自动设置errno,可以通过strerror(errno)来查看报错数字的含义
以打开不存在文件为例:
执行该代码,结果如下:
open函数:
int open(char *pathname, int flags) #include <unistd.h>
参数:
pathname: 欲打开的文件路径名
flags:文件打开方式: #include <fcntl.h>
O_RDONLY|O_WRONLY|O_RDWR O_CREAT|O_APPEND|O_TRUNC|O_EXCL|O_NONBLOCK ....
返回值:
成功: 打开文件所得到对应的 文件描述符(整数)
失败: -1, 设置errno
int open(char *pathname, int flags, mode_t mode) 123 775
参数:
pathname: 欲打开的文件路径名
flags:文件打开方式: O_RDONLY|O_WRONLY|O_RDWR O_CREAT|O_APPEND|O_TRUNC|O_EXCL|O_NONBLOCK ....
mode: 参数3使用的前提, 参2指定了 O_CREAT。 取值8进制数,用来描述文件的 访问权限。 rwx 0664
创建文件最终权限 = mode & ~umask
返回值:
成功: 打开文件所得到对应的 文件描述符(整数)
失败: -1, 设置errno
close函数:
int close(int fd);
49P-总结
gdb调试工具: 大前提:程序是你自己写的。 ---逻辑错误
基础指令:
-g:使用该参数编译可以执行文件,得到调试表。
gdb ./a.out
list: list 1 列出源码。根据源码指定 行号设置断点。
b: b 20 在20行位置设置断点。
run/r: 运行程序
n/next: 下一条指令(会越过函数)
s/step: 下一条指令(会进入函数)
p/print:p i 查看变量的值。
continue:继续执行断点后续指令。
finish:结束当前函数调用。
quit:退出gdb当前调试。
其他指令:
run:使用run查找段错误出现位置。
set args: 设置main函数命令行参数 (在 start、run 之前)
run 字串1 字串2 ...: 设置main函数命令行参数
info b: 查看断点信息表
b 20 if i = 5: 设置条件断点。
ptype:查看变量类型。
bt:列出当前程序正存活着的栈帧。
frame: 根据栈帧编号,切换栈帧。
display:设置跟踪变量
undisplay:取消设置跟踪变量。 使用跟踪变量的编号。
makefile: 管理项目。
命名:makefile Makefile --- make 命令
1 个规则:
目标:依赖条件
(一个tab缩进)命令
1. 目标的时间必须晚于依赖条件的时间,否则,更新目标
2. 依赖条件如果不存在,找寻新的规则去产生依赖条件。
ALL:指定 makefile 的终极目标。
2 个函数:
src = $(wildcard ./*.c): 匹配当前工作目录下的所有.c 文件。将文件名组成列表,赋值给变量 src。 src = add.c sub.c div1.c
obj = $(patsubst %.c, %.o, $(src)): 将参数3中,包含参数1的部分,替换为参数2。 obj = add.o sub.o div1.o
clean: (没有依赖)
-rm -rf $(obj) a.out “-”:作用是,删除不存在文件时,不报错。顺序执行结束。
3 个自动变量:
$@: 在规则的命令中,表示规则中的目标。
$^: 在规则的命令中,表示所有依赖条件。
$<: 在规则的命令中,表示第一个依赖条件。如果将该变量应用在模式规则中,它可将依赖条件列表中的依赖依次取出,套用模式规则。
模式规则:
%.o:%.c
gcc -c $< -o %@
静态模式规则:
$(obj):%.o:%.c
gcc -c $< -o %@
伪目标:
.PHONY: clean ALL
参数:
-n:模拟执行make、make clean 命令。
-f:指定文件执行 make 命令。 xxxx.mk
作业:编写一个 makefile 可以将其所在目录下的所有独立 .c 文件编译生成同名可执行文件。
open函数:
50P-复习
51P-makefile作业
将当前目录下所有.c文件编译为可执行程序
测试程序:
测试:
52P-read和write实现cp
read函数:
ssize_t read(int fd, void *buf, size_t count);
参数:
fd:文件描述符
buf:存数据的缓冲区
count:缓冲区大小
返回值:
0:读到文件末尾。
成功; > 0 读到的字节数。
失败: -1, 设置 errno
-1: 并且 errno = EAGIN 或 EWOULDBLOCK, 说明不是read失败,而是read在以非阻塞方式读一个设备文件(网络文件),并且文件无数据。
write函数:
ssize_t write(int fd, const void *buf, size_t count);
参数:
fd:文件描述符
buf:待写出数据的缓冲区
count:数据大小
返回值:
成功; 写入的字节数。
失败: -1, 设置 errno
用read和write实现一个copy函数:
编译一波
复制文件一波:
可以在复制函数里加入错误检测:
if(fd1 == -1){
perror(“open argv[1] error”);
exit(1);
}
错误处理函数: 与 errno 相关。
printf("xxx error: %d\n", errno);
char *strerror(int errnum);
printf("xxx error: %s\n", strerror(errno));
void perror(const char *s);
perror("open error");
用read/write实现的copy和fgetc/fputc实现的copy对比:
53P-系统调用和库函数比较—预读入缓输出
下面写两个文件拷贝函数,一个用read/write实现,一个用fputc/fgetc实现,比较速度
首先是fputc/fgetc
编译一波
运行一波
很快就拷贝完成了。
下面修改read那边的缓冲区,一次拷贝一个字符。
运行,如图:
速度缓慢。
原因分析:
read/write这块,每次写一个字节,会疯狂进行内核态和用户态的切换,所以非常耗时。
fgetc/fputc,有个缓冲区,4096,所以它并不是一个字节一个字节地写,内核和用户切换就比较少
预读入,缓输出机制。
所以系统函数并不是一定比库函数牛逼,能使用库函数的地方就使用库函数。
标准IO函数自带用户缓冲区,系统调用无用户级缓冲。系统缓冲区是都有的。
54P-文件描述符
文件描述符是指向一个文件结构体的指针
PCB进程控制块:本质 结构体。
成员:文件描述符表。
文件描述符:0/1/2/3/4。。。。/1023 表中可用的最小的。
0 - STDIN_FILENO
1 - STDOUT_FILENO
2 - STDERR_FILENO
55P-阻塞和非阻塞
阻塞、非阻塞: 是设备文件、网络文件的属性。
产生阻塞的场景。 读设备文件。读网络文件。(读常规文件无阻塞概念。)
/dev/tty -- 终端文件。
open("/dev/tty", O_RDWR|O_NONBLOCK) --- 设置 /dev/tty 非阻塞状态。(默认为阻塞状态)
一个例子,从标准输入读,写到标准输出:
执行程序,就会发现程序在阻塞等待输入
下面是一段更改非阻塞读取终端的代码:
- #include <unistd.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <errno.h>
- #include <string.h>
- #define MSG_TRY "try again\n"
- #define MSG_TIMEOUT "time out\n"
- int main(void)
- {
- char buf[10];
- int fd, n, i;
- fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
- if(fd < 0){
- perror("open /dev/tty");
- exit(1);
- }
- printf("open /dev/tty ok... %d\n", fd);
- for (i = 0; i < 5; i++){
- n = read(fd, buf, 10);
- if (n > 0) { //说明读到了东西
- break;
- }
- if (errno != EAGAIN) { //EWOULDBLOCK
- perror("read /dev/tty");
- exit(1);
- } else {
- write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
- sleep(2);
- }
- }
- if (i == 5) {
- write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));
- } else {
- write(STDOUT_FILENO, buf, n);
- }
- close(fd);
- return 0;
- }
执行,如图所示:
56-fcntl改文件属性
fcntl用来改变一个【已经打开】的文件的 访问控制属性
重点掌握两个参数的使用, F_GETFL,F_SETFL
fcntl:
int (int fd, int cmd, ...)
fd 文件描述符
cmd 命令,决定了后续参数个数
int flgs = fcntl(fd, F_GETFL);
flgs |= O_NONBLOCK
fcntl(fd, F_SETFL, flgs);
获取文件状态: F_GETFL
设置文件状态: F_SETFL
终端文件默认是阻塞读的,这里用fcntl将其更改为非阻塞读
- #include <unistd.h>
- #include <fcntl.h>
- #include <errno.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #define MSG_TRY "try again\n"
- int main(void)
- {
- char buf[10];
- int flags, n;
- flags = fcntl(STDIN_FILENO, F_GETFL); //获取stdin属性信息
- if(flags == -1){
- perror("fcntl error");
- exit(1);
- }
- flags |= O_NONBLOCK;
- int ret = fcntl(STDIN_FILENO, F_SETFL, flags);
- if(ret == -1){
- perror("fcntl error");
- exit(1);
- }
- tryagain:
- n = read(STDIN_FILENO, buf, 10);
- if(n < 0){
- if(errno != EAGAIN){
- perror("read /dev/tty");
- exit(1);
- }
- sleep(3);
- write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
- goto tryagain;
- }
- write(STDOUT_FILENO, buf, n);
- return 0;
- }
编译运行,结果如下:
可以看到,是非阻塞读取。
57P-午后回顾
58P-lseek函数
lseek函数:
off_t lseek(int fd, off_t offset, int whence);
参数:
fd:文件描述符
offset: 偏移量,就是将读写指针从whence指定位置向后偏移offset个单位
whence:起始偏移位置: SEEK_SET/SEEK_CUR/SEEK_END
返回值:
成功:较起始位置偏移量
失败:-1 errno
应用场景:
1. 文件的“读”、“写”使用同一偏移位置。
2. 使用lseek获取文件大小
3. 使用lseek拓展文件大小:要想使文件大小真正拓展,必须引起IO操作。
使用 truncate 函数,直接拓展文件。 int ret = truncate("dict.cp", 250);
lseek示例,写一个句子到空白文件,完事调整光标位置,读取刚才写那个文件。
这个示例中,如果不调整光标位置,是读取不到内容的,因为读写指针在内容的末尾
代码如下:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <string.h>
- #include <fcntl.h>
- int main(void)
- {
- int fd, n;
- char msg[] = "It's a test for lseek\n";
- char ch;
- fd = open("lseek.txt", O_RDWR|O_CREAT, 0644);
- if(fd < 0){
- perror("open lseek.txt error");
- exit(1);
- }
- write(fd, msg, strlen(msg)); //使用fd对打开的文件进行写操作,问价读写位置位于文件结尾处。
- lseek(fd, 0, SEEK_SET); //修改文件读写指针位置,位于文件开头。 注释该行会怎样呢?
- while((n = read(fd, &ch, 1))){
- if(n < 0){
- perror("read error");
- exit(1);
- }
- write(STDOUT_FILENO, &ch, n); //将文件内容按字节读出,写出到屏幕
- }
- close(fd);
- return 0;
- }
编译运行,结果如图:
下面这个代码用lseek的偏移来读取文件大小:
编译执行,结果如下:
是没有问题的。
下面使用lseek将fcntl.c原本的698字节填充为800字节,差值102字节:
比起读取大小的代码,就改了个偏移量
编译执行,如下:
下面再用ls命令查看fcntl.c,问题出来了:
它的大小不是800.
原因是,要使文件大小真正拓展,必须引起IO操作。
修改后的扩展文件代码如下:
编译运行,如下:
打开fcntl.c查看,如下:
简单小结一下:
对于写文件再读取那个例子,由于文件写完之后未关闭,读写指针在文件末尾,所以不调节指针,直接读取不到内容。
lseek读取的文件大小总是相对文件头部而言。
用lseek读取文件大小实际用的是读写指针初末位置的偏移差,一个新开文件,读写指针初位置都在文件开头。如果用这个来扩展文件大小,必须引起IO才行,于是就至少要写入一个字符。上面代码出现lseek返回799,ls查看为800的原因是,lseek读取到偏移差的时候,还没有写入最后的‘$’符号.
末尾那一大堆^@,是文件空洞,如果自己写进去的也想保持队形,就写入“\0”。
拓展文件直接使用truncate,简单粗暴:
使用 truncate 函数,直接拓展文件。 int ret = truncate("dict.cp", 250);
59P-传入传出参数
传入参数:
1. 指针作为函数参数。
2. 同常有const关键字修饰。
3. 指针指向有效区域, 在函数内部做读操作。
传出参数:
1. 指针作为函数参数。
2. 在函数调用之前,指针指向的空间可以无意义,但必须有效。
3. 在函数内部,做写操作。
4。函数调用结束后,充当函数返回值。
传入传出参数:
1. 指针作为函数参数。
2. 在函数调用之前,指针指向的空间有实际意义。
3. 在函数内部,先做读操作,后做写操作。
4. 函数调用结束后,充当函数返回值。
60P-目录项和inode
一个文件主要由两部分组成,dentry(目录项)和inode
inode本质是结构体,存储文件的属性信息,如:权限、类型、大小、时间、用户、盘快位置…
也叫做文件属性管理结构,大多数的inode都存储在磁盘上。
少量常用、近期使用的inode会被缓存到内存中。
所谓的删除文件,就是删除inode,但是数据其实还是在硬盘上,以后会覆盖掉。
61P-stat函数
获取文件属性,(从inode结构体中获取)
stat/lstat 函数:
int stat(const char *path, struct stat *buf);
参数:
path: 文件路径
buf:(传出参数) 存放文件属性,inode结构体指针。
返回值:
成功: 0
失败: -1 errno
获取文件大小: buf.st_size
获取文件类型: buf.st_mode
获取文件权限: buf.st_mode
符号穿透:stat会。lstat不会。
下面这个例子是获取文件大小的正规军解法,用stat:
编译运行,并查看mystat.c文件的大小,如下:
62P-lstat和stat
用stat查看文件类型,代码如下:
编译运行,查看一波mystat.c文件:
stat会拿到符号链接指向那个文件或目录的属性。
不想穿透符号就用lstat
63P-传出参数当返回值
其实就是传出参数在函数中被改变,可以用来传出。而且传出参数数量不限,比单纯的返回值更灵活。
64P-link和Unlink隐式回收
硬链接数就是dentry数目
link就是用来创建硬链接的
link可以用来实现mv命令
函数原型:
int link(const char *oldpath, const char *newpath)
用这个来实现mv,用oldpath来创建newpath,完事儿删除oldpath就行。
删除一个链接 int unlink(const char *pathname)
unlink是删除一个文件的目录项dentry,使硬链接数-1
unlink函数的特征:清除文件时,如果文件的硬链接数到0了,没有dentry对应,但该文件仍不会马上被释放,要等到所有打开文件的进程关闭该文件,系统才会挑时间将该文件释放掉。
下面用一段代码来验证unlink是删除dentry:
- /*
- *unlink函数是删除一个dentry
- */
- #include <unistd.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <string.h>
- #include <stdio.h>
- int main(void)
- {
- int fd, ret;
- char *p = "test of unlink\n";
- char *p2 = "after write something.\n";
- fd = open("temp.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);
- if(fd < 0){
- perror("open temp error");
- exit(1);
- }
- ret = write(fd, p, strlen(p));
- if (ret == -1) {
- perror("-----write error");
- }
- printf("hi! I'm printf\n");
- ret = write(fd, p2, strlen(p2));
- if (ret == -1) {
- perror("-----write error");
- }
- printf("Enter anykey continue\n");
- getchar();
- ret = unlink("temp.txt"); //具备了被释放的条件
- if(ret < 0){
- perror("unlink error");
- exit(1);
- }
- close(fd);
- return 0;
- }
编译程序并运行,程序阻塞,此时打开新终端查看临时文件temp.c如下:
可以看到,临时文件没有被删除,这是因为当前进程没结束。
输入字符使当前进程结束后,temp.txt就不见了
下面开始搞事,在程序中加入段错误成分,段错误在unlink之前,由于发生段错误,程序后续删除temp.txt的dentry部分就不会再执行,temp.txt就保留了下来,这是不科学的。
解决办法是检测fd有效性后,立即释放temp.txt,由于进程未结束,虽然temp.txt的硬链接数已经为0,但还不会立即释放,仍然存在,要等到程序执行完才会释放。这样就能避免程序出错导致临时文件保留下来。
因为文件创建后,硬链接数立马减为0,即使程序异常退出,这个文件也会被清理掉。这时候的内容是写在内核空间的缓冲区。
修改后代码如下:
- /*
- *unlink函数是删除一个dentry
- */
- #include <unistd.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <string.h>
- #include <stdio.h>
- int main(void)
- {
- int fd, ret;
- char *p = "test of unlink\n";
- char *p2 = "after write something.\n";
- fd = open("temp.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);
- if(fd < 0){
- perror("open temp error");
- exit(1);
- }
- ret = unlink("temp.txt"); //具备了被释放的条件
- if(ret < 0){
- perror("unlink error");
- exit(1);
- }
- ret = write(fd, p, strlen(p));
- if (ret == -1) {
- perror("-----write error");
- }
- printf("hi! I'm printf\n");
- ret = write(fd, p2, strlen(p2));
- if (ret == -1) {
- perror("-----write error");
- }
- printf("Enter anykey continue\n");
- getchar();
- close(fd);
- return 0;
- }
隐式回收:
当进程结束运行时,所有进程打开的文件会被关闭,申请的内存空间会被释放。系统的这一特性称之为隐式回收系统资源。
比如上面那个程序,要是没有在程序中关闭文件描述符,没有隐式回收的话,这个文件描述符会保留,多次出现这种情况会导致系统文件描述符耗尽。所以隐式回收会在程序结束时收回它打开的文件使用的文件描述符。
65P-文件目录rwx权限差异
vi 目录
会得到目录项的列表
66P-目录操作函数
目录操作函数:
DIR * opendir(char *name);
int closedir(DIR *dp);
struct dirent *readdir(DIR * dp);
struct dirent {
inode
char dname[256];
}
没有写目录操作,因为目录写操作就是创建文件。可以用touch
下面用目录操作函数实现一个ls操作:
编译执行,如下:
要隐藏这个.和..的话,在输出文件名的时候判定一下,只输出不是.和..的就行了
67P-总结
open函数:
int open(char *pathname, int flags) #include <unistd.h>
参数:
pathname: 欲打开的文件路径名
flags:文件打开方式: #include <fcntl.h>
O_RDONLY|O_WRONLY|O_RDWR O_CREAT|O_APPEND|O_TRUNC|O_EXCL|O_NONBLOCK ....
返回值:
成功: 打开文件所得到对应的 文件描述符(整数)
失败: -1, 设置errno
int open(char *pathname, int flags, mode_t mode) 123 775
参数:
pathname: 欲打开的文件路径名
flags:文件打开方式: O_RDONLY|O_WRONLY|O_RDWR O_CREAT|O_APPEND|O_TRUNC|O_EXCL|O_NONBLOCK ....
mode: 参数3使用的前提, 参2指定了 O_CREAT。 取值8进制数,用来描述文件的 访问权限。 rwx 0664
创建文件最终权限 = mode & ~umask
返回值:
成功: 打开文件所得到对应的 文件描述符(整数)
失败: -1, 设置errno
close函数:
int close(int fd);
错误处理函数: 与 errno 相关。
printf("xxx error: %d\n", errno);
char *strerror(int errnum);
printf("xxx error: %s\n", strerror(errno));
void perror(const char *s);
perror("open error");
read函数:
ssize_t read(int fd, void *buf, size_t count);
参数:
fd:文件描述符
buf:存数据的缓冲区
count:缓冲区大小
返回值:
0:读到文件末尾。
成功; > 0 读到的字节数。
失败: -1, 设置 errno
-1: 并且 errno = EAGIN 或 EWOULDBLOCK, 说明不是read失败,而是read在以非阻塞方式读一个设备文件(网络文件),并且文件无数据。
write函数:
ssize_t write(int fd, const void *buf, size_t count);
参数:
fd:文件描述符
buf:待写出数据的缓冲区
count:数据大小
返回值:
成功; 写入的字节数。
失败: -1, 设置 errno
文件描述符:
PCB进程控制块:本质 结构体。
成员:文件描述符表。
文件描述符:0/1/2/3/4。。。。/1023 表中可用的最小的。
0 - STDIN_FILENO
1 - STDOUT_FILENO
2 - STDERR_FILENO
阻塞、非阻塞: 是设备文件、网络文件的属性。
产生阻塞的场景。 读设备文件。读网络文件。(读常规文件无阻塞概念。)
/dev/tty -- 终端文件。
open("/dev/tty", O_RDWR|O_NONBLOCK) --- 设置 /dev/tty 非阻塞状态。(默认为阻塞状态)
fcntl:
int (int fd, int cmd, ...)
int flgs = fcntl(fd, F_GETFL);
flgs |= O_NONBLOCK
fcntl(fd, F_SETFL, flgs);
获取文件状态: F_GETFL
设置文件状态: F_SETFL
lseek函数:
off_t lseek(int fd, off_t offset, int whence);
参数:
fd:文件描述符
offset: 偏移量
whence:起始偏移位置: SEEK_SET/SEEK_CUR/SEEK_END
返回值:
成功:较起始位置偏移量
失败:-1 errno
应用场景:
1. 文件的“读”、“写”使用同一偏移位置。
2. 使用lseek获取文件大小
3. 使用lseek拓展文件大小:要想使文件大小真正拓展,必须引起IO操作。
使用 truncate 函数,直接拓展文件。 int ret = truncate("dict.cp", 250);
传入参数:
1. 指针作为函数参数。
2. 同常有const关键字修饰。
3. 指针指向有效区域, 在函数内部做读操作。
传出参数:
1. 指针作为函数参数。
2. 在函数调用之前,指针指向的空间可以无意义,但必须有效。
3. 在函数内部,做写操作。
4。函数调用结束后,充当函数返回值。
传入传出参数:
1. 指针作为函数参数。
2. 在函数调用之前,指针指向的空间有实际意义。
3. 在函数内部,先做读操作,后做写操作。
4. 函数调用结束后,充当函数返回值。
void aaa();
int aaa(int *p, struct stat *p2, strcut student *p3);
bbb()
{
aaa();
}
stat/lstat 函数:
int stat(const char *path, struct stat *buf);
参数:
path: 文件路径
buf:(传出参数) 存放文件属性。
返回值:
成功: 0
失败: -1 errno
获取文件大小: buf.st_size
获取文件类型: buf.st_mode
获取文件权限: buf.st_mode
符号穿透:stat会。lstat不会。
link/unlink:
隐式回收。
目录操作函数:
DIR * opendir(char *name);
int closedir(DIR *dp);
struct dirent *readdir(DIR * dp);
struct dirent {
inode
char dname[256];
}
68P-复习
应用程序的系统调用过程
应用程序->标库函数->系统调用->驱动->硬件
69P-递归遍历目录思路分析
任务需求:使用opendir closedir readdir stat实现一个递归遍历目录的程序
输入一个指定目录,默认为当前目录。递归列出目录中的文件,同时显示文件大小。
思路分析
递归遍历目录:ls-R.c
1. 判断命令行参数,获取用户要查询的目录名。 int argc, char *argv[1]
argc == 1 --> ./
2. 判断用户指定的是否是目录。 stat S_ISDIR(); --> 封装函数 isFile() { }
3. 读目录: read_dir() {
opendir(dir)
while (readdir()){
普通文件,直接打印
目录:
拼接目录访问绝对路径。sprintf(path, "%s/%s", dir, d_name)
递归调用自己。--》 opendir(path) readdir closedir
}
closedir()
}
read_dir() --> isFile() ---> read_dir()
70P-递归遍历目录代码预览
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/stat.h>
- #include <dirent.h>
- #include <pthread.h>
- void isFile(char *name);
- // 打开目录读取,处理目录
- void read_dir(char *dir, void (*func)(char *))
- {
- char path[256];
- DIR *dp;
- struct dirent *sdp;
- dp = opendir(dir);
- if (dp == NULL) {
- perror("opendir error");
- return;
- }
- // 读取目录项
- while((sdp = readdir(dp)) != NULL) {
- if (strcmp(sdp->d_name, ".") == 0 || strcmp(sdp->d_name, "..") == 0) {
- continue;
- }
- //fprintf();
- // 目录项本身不可访问, 拼接. 目录/目录项
- sprintf(path, "%s/%s", dir, sdp->d_name);
- // 判断文件类型,目录递归进入,文件显示名字/大小
- //isFile(path);
- (*func)(path);
- }
- closedir(dp);
- return ;
- }
- void isFile(char *name)
- {
- int ret = 0;
- struct stat sb;
- // 获取文件属性, 判断文件类型
- ret = stat(name, &sb);
- if (ret == -1) {
- perror("stat error");
- return ;
- }
- // 是目录文件
- if (S_ISDIR(sb.st_mode)) {
- read_dir(name, isFile);
- }
- // 是普通文件, 显示名字/大小
- printf("%10s\t\t%ld\n", name, sb.st_size);
- return;
- }
- int main(int argc, char *argv[])
- {
- // 判断命令行参数
- if (argc == 1) {
- isFile(".");
- } else {
- isFile(argv[1]);
- }
- return 0;
- }
71P-递归遍历目录实现
先写个简易版的,可以判定文件,读取文件大小:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <pthread.h>
- #include <sys/stat.h>
- void isFile(char *name){
- int ret = 0;
- struct stat sb;
- ret = stat(name, &sb);
- if(ret == -1){
- perror("stat error");
- return;
- }
- if(S_ISDIR(sb.st_mode)){
- }
- printf("%s\t%ld\n", name, sb.st_size);
- return;
- }
- int main(int argc, char *argv[]){
- if(argc == 1) {
- isFile(".");
- }
- else {
- isFile(argv[1]);
- }
- return 0;
- }
编译运行,并查看一个文件,如下:
下面完善功能,把对目录的递归处理补全,如下:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/stat.h>
- #include <dirent.h>
- #include <pthread.h>
- void isFile(char *name);
- // 打开目录读取,处理目录
- void read_dir(char *dir, void (*func)(char *))
- {
- char path[256];
- DIR *dp;
- struct dirent *sdp;
- dp = opendir(dir);
- if (dp == NULL) {
- perror("opendir error");
- return;
- }
- // 读取目录项
- while((sdp = readdir(dp)) != NULL) {
- if (strcmp(sdp->d_name, ".") == 0 || strcmp(sdp->d_name, "..") == 0) {
- continue;
- }
- //fprintf();
- // 目录项本身不可访问, 拼接. 目录/目录项
- sprintf(path, "%s/%s", dir, sdp->d_name);
- // 判断文件类型,目录递归进入,文件显示名字/大小
- //isFile(path);
- (*func)(path);
- }
- closedir(dp);
- return ;
- }
- void isFile(char *name)
- {
- int ret = 0;
- struct stat sb;
- // 获取文件属性, 判断文件类型
- ret = stat(name, &sb);
- if (ret == -1) {
- perror("stat error");
- return ;
- }
- // 是目录文件
- if (S_ISDIR(sb.st_mode)) {
- read_dir(name, isFile);
- }
- // 是普通文件, 显示名字/大小
- printf("%10s\t\t%ld\n", name, sb.st_size);
- return;
- }
- int main(int argc, char *argv[])
- {
- // 判断命令行参数
- if (argc == 1) {
- isFile(".");
- } else {
- isFile(argv[1]);
- }
- return 0;
- }
这里和视频里有一点差异就是,这里用的回调函数来实现对目录中目录项的处理,视频里直接调用的isFile,差别不大。
编译运行,如下:
如图,基本达到了ls-r的功能。
72P-递归遍历目录总结
递归改回调,问题不大
73P-dup和dup2
用来做重定向,本质就是复制文件描述符:
dup 和 dup2:
int dup(int oldfd); 文件描述符复制。
oldfd: 已有文件描述符
返回:新文件描述符,这个描述符和oldfd指向相同内容。
int dup2(int oldfd, int newfd); 文件描述符复制,oldfd拷贝给newfd。返回newfd
一个小例子,给一个旧的文件描述符,返回一个新文件描述符:
编译运行,如下:
dup基本就这样了,后续使用也就起个保存作用。
下面讲dup2(dupto):
下面这个例子,将一个已有文件描述符fd1复制给另一个文件描述符fd2,然后用fd2修改fd1指向的文件:
编译运行,如下图:
下面查看hello.c
没错,惠惠是我老婆。
上面那个例子,fd1是打开hello.c的文件描述符,fd2是打开hello2.c的文件描述符
用dup2将fd1复制给了fd2,于是在对fd2指向的文件进行写操作时,实际上就是对fd1指向的hello.c进行写操作。
这里需要注意一个问题,由于hello.c和hello2.c都是空文件,所以直接写进去没关系。但如果hello.c是非空的,写进去的内容默认从文件头部开始写,会覆盖原有内容。
dup2也可以用于标准输入输出的重定向。
下面这个例子,将输出到STDOUT的内容重定向到文件里:
编译执行,如下:
这个程序,将fd1的内容复制给了fd2,使得原来指向hello2.c的fd2也指向了hello.c
并通过fd2向hello.c里写入了惠惠是我老婆。完事儿将标准输出重定向至fd1,就是将要显示在标准输出的内容,写入了fd1指向的文件,就是hello.c中
这里有一点和上面程序不同,就是hello.c是处于打开状态的,连续写入两段话,写入小忍是我老婆的时候,读写指针在这句话末尾,就不会覆盖惠惠是我老婆这句话,所以,都是我老婆,没有问题的。
这里再强调一下,打开一个文件,读写指针默认在文件头,如果文件本身有内容,直接写入会覆盖原有内容。
74P-fcntl实现dup描述符
fcntl 函数实现 dup:
int fcntl(int fd, int cmd, ....)
cmd: F_DUPFD
参3: 被占用的,返回最小可用的。
未被占用的, 返回=该值的文件描述符。
下面这个代码用fcntl来实现描述符的复制:
对于fcntl中的参数0,这个表示0被占用,fcntl使用文件描述符表中的最小文件描述符返回
假设传入0,传一个7,且7未被占用,则会返回7
所以这个参数可以这样理解,你传入一个文件描述符k,如果k没被占用,则直接用k复制fd1的内容。如果k被占用,则返回描述符表中最小可用描述符,也就是自己指定一个一志愿,如果行,就返回这个。如果不行,国家给你分配一个最小的。
编译执行,如下:
如图可知,原来指向hello.c的文件描述符是3,复制了一个,新的文件描述符4也指向hello.c
下面这个例子,用fcntl复制2次文件描述符,第一次使用默认分配,就是传0,第二次使用自己选定文件描述符复制,完事儿向文件里写入内容
编译执行,结果如下:
可见上述说明都是没有问题的。