这里写目录标题
目录操作
getcwd、chdir函数
作用
分别是获取当前工作目录、以及改变当前进程的工作目录
原型
目录与文件的关系
简介
首先明确:目录也是一个文件
其文件内容就是所有子文件的目录项(dentry),使用vim打开一个目录,也是显示其子文件的目录项
而其三个权限与文件的区别见上图
代码
测试:没有x权限就无法cd,首先使用ls -ld 目录名(若要查看一个文件的详细信息,是使用ls -l ,即没有d)
之后使用chmod 改变文件权限,a-x,是三组都减去x权限
最终,该目录没有x权限后,就无法cd了
opendir
man 3 卷(说明其不是一个系统调用,而是一个库函数(标准库))
而且返回值是DIR*,与文件的FILE*类似,且二者都无法查看其实现,所以说对于目录的这些操作与对文件的类似的操作是同级别的
函数原型
传入目录的路径(精确到目录本身)
返回值如果是NULL,那么就调用失败,设置errno
closedir
man 3 卷
函数原型
readdir
函数原型
该函数会返回一个dirent结构体,该结构体就是目录项,可以看到目录项中不仅有文件名、inode数(此处的inode我们一般也不用,而是使用“对文件其他操作”一栏中的stat函数传出的stat结构体),还有其他信息(只不过很少用的到)
返回值:如果到了文件尾,那么返回NULL,此时没出错,errno不会被设置
如果出错了,那么还是会返回NULL,但是此时errno会被设置
代码(实现ls功能)
使用opendir、closedir、readdir实现ls功能
需要注意的是,最终的结果会显示指定的目录的子文件之外,还会显示“.”、“…” 这两个被隐藏的文件,我们可以在代码中加以判断进行continue
总结
综合应用
目标
实现一个ls -R,也就是显示当前子文件的同时,如果子文件是一个目录,那么将其目录内的子文件也显示出来,依次类推,有目录就向内显示
思路
对于1,当argc是1时,说明没有传参,那么默认是传入 ./
2,特殊情况的处理:之后判断传入的第一次参数是文件还是目录,文件的话直接输出即可
3,若是目录
则打开目录,循环读取,读取时判读是否是文件,是文件,则打印
不是文件,则说明要进行递归读取,那么就调用步骤3即可,这里注意要拼接一下相对路径,因为已经来到了第二层,而该进程不会来到第二层,还是以argv[1]的视角来调用函数,所以,第二层调用opendir时,要先拼接上第一层的路径(即打开argv[1]中的哪个目录)
4,
而每个看起来空的目录,其内部都会有两个目录:“.”、“…”
所以,要加以判断防止出现无限递归
代码
头文件:
main:(步骤1)
如果agrc是1,说明没有输入参数,那么默认为“.”
否则,我们认为其输入了一个参数,传入isFile函数
isFile:(步骤2,对特殊情况进行处理 + 后续步骤3中每个循环步可以直接使用该函数:是文件则输出,是目录则递归)
传入一个name之后,拿到该“文件”(此处的文件是广义的,因为目录也是文件)的stat
之后判断他是否是一个目录,如果是,等待进行下一步操作
如果不是,那么输出其文件名和文件大小,而目录执行完if之后,也会执行到该输出文件,所以也会输出目录的大小(因为stat表示的“文件”,是广义的“文件”,其属性可以表示“狭义文件”,以及“目录”的属性)
read_dir:(步骤3)
注意no.9:对函数进行声明
no.24~no.26:对".“、”…"目录进行特殊处理,让其跳过
回调代码
将递归改为回调函数:
1、对read_dir,添加一个参数,这是一个函数指针,参数是char *(返回值和参数可能取决于所回调的函数,如下图第2步,isFile函数返回值是void 参数是char 星)
2、调用read_dir时,第二个参数要传入回调的函数名
3、在read_dir中,递归语句,改为回调函数:
func是一个函数指针,我们先解引用:*func,之后加上括号以防歧义,后面跟上括号进行参数的传入
回调函数的作用是:可以运行后,通过传参,传入不同的递归函数
重定向
命令行的重定向
重定向,就是将“要输出到屏幕”的内容,写到一个文件内
如何在程序中用代码实现呢:
其实写到屏幕。就是写到“标准输出”这个文件描述符里,所以,我们只要重点操作文件描述符即可实现重定向的功能
dup
函数原型
参数一:旧的已有的文件描述符
返回值:成功返回新的文件描述符,失败返回-1,设置errno
代码
使用dup,我们首先要有一个旧的文件描述符,想要有一个旧的文件描述符,我们就要打开一个文件,这样该文件才会有文件描述符,
旧的文件描述符是3,dup会返回一个新的文件描述符,是拷贝旧的文件描述符得到的,所以,newfd是4,是拷贝3得来的
可能会用于后续,我们使用dup2后,一个文件的文件描述符会丢失,我们可以使用dup先预存一个
dup2
函数原型
两个参数,参数一:旧的文件描述符 参数二:新的文件描述符
(但是此新非彼新,这个虽然称为新的文件描述符,实际上,这也是一个已有的,一个打开已经存在的文件所获得的已有的文件描述符)
作用:将旧的文件描述符赋值给新的文件描述符,并且返回新的文件描述符,原理图:
可以看到,拷贝文件描述符,只是拷贝文件描述符这个指针,所以拷贝完之后,相当于两个指针指向一个文件了
他和dup的区别:
dup是生成一个新的,目前不存在的文件描述符
dup2:是已有两个文件描述符,我们可以指定参数二的描述符拷贝参数一的描述符
dup无法指定新的描述符,而是按照文件描述符表的顺序进行生成
dup2是指定一个已有的文件描述符
代码dup2
打开两个文件,就可以得到两个文件描述符(注意,要以可读写的方式打开,不然文件描述符没有写的权限,是无法进行内容写入的,无法进行下面的测试)(追加写入的设置,也是在“打开文件时所传入的打开方式”来设置)
之后,将fd2拷贝fd1,也就是fd2现在指向跟fd1一样的文件
所以,我们下面向fd2写入内容,会写入到fd1文件内,效果如下:
out文件的内容:
因为这里的打开方式是普通的可写,而没有使用“追加”,所以,会从最开始进行覆盖写入。
输出重定向
上图的20行和22行,测试了输出重定向
我们想要实现输出重定向,就要把目标文件的文件描述符,拷贝给STDOUT_FILENO这个文件描述符,这样,一切向“屏幕输出”的内容,都会输出到目标文件内
使用fcntl函数来实现dup
代码
我们的fcntl,本来是用于修改文件的“阻塞非阻塞”属性,这里可以通过传参,来实现dup,当我们第二个参数传入F_DUPFD,第三个参数传入0,那么由于0被占用了,所以,就会拷贝第一个参数:旧的文件描述符,到最新的可用的文件描述符,返回新的fd,也就实现了dup的功能
使用fcntl函数来实现dup
代码
当我们第三个参数传入一个未被占用的,指定的文件描述符的时候,就会实现dup2的功能,将fd1拷贝给7
同时我们对7进行写入,会写到fd1中
总结
实际上,不管第三个参数传入0还是7,都是使用>=参数3的最小的可用的文件描述符,只不过0是百分百被占用的,而7是我们事先知道没被占用的,所以,只是根据不同的特性进行了模拟