一、文件存储
一个文件主要由两部分组成,dentry(目录项)和inode
inode本质是结构体,存储文件的属性信息,如:权限、类型、大小、时间、用户、盘块位置…
也叫做文件属性管理结构,大多数的inode都存储在磁盘上。
少量常用、近期使用的inode会被缓存到内存中。
所谓的删除文件,就是删除inode,但是数据其实还是在硬盘上,以后会覆盖掉。
二、文件操作
1. stat函数:获取文件属性,(从inode结构体中获取)
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不会。
#include <stdio. h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread .h>
#include <sys/stat.h>
int main(int argc,char *argv[])
{
struct stat sbuf;
int ret = stat(argv[1], &sbuf);
if(ret == -1)
{
perror( "stat error" );
exit(1);
}
printf( "file size: %ld\n" , sbuf.st_size);
return 0;
}
查看f.c文件大小执行:
./mystat f.c
2. lstat:查看文件类型,用法同stat
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<sys/stat.h>
int main(int argc,char *argv[])
{
struct stat sbuf;
int ret = lstat(argv[1],&sbuf);
if (ret == -1)
{
perror("stat error");
exit(1);
}
if (S_ISREG(sbuf.st_mode)){
printf("It's a regular file\n");
}else if(S_ISDIR(sbuf.st_mode)){
printf("It's a dir\n");
}else if (S_ISFIFO(sbuf.st_mode)){
printf("It's a pipe\n");
}else if(S_ISLNK(sbuf.st_mode)){
printf("It's a sym link\n");
}
return 0;
}
stat会拿到符号链接指向那个文件或目录的属性---符号穿透
查看符号链接文件时不想让其穿透则使用lstat函数
首先对test.c创建一个软连接
ln -s test.c test.s/test.soft
查看改文件类型,执行以下代码:
./mystat test.s/test.soft
out:
It's a sym link
如果使用stat函数,则以上输出为:
It's a dir/It's a regular file
3.link和unlink隐式回收
int link(const char *oldpath, const char *newpath);
link函数,可以为已经存在的文件创建目录项(硬链接)
int unlink(const char *pathname);
- unlink是删除一个文件的目录项dentry,使【硬链接数-1】
- unlink函数的特征:清除文件时,如果文件的硬链接数到0了,没有dentry对应,但该文件仍不会马上被释放,要等到所有打开文件的进程关闭该文件,系统才会挑时间将该文件释放掉。
编程实现mv命令的改名操作:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int main(int argc,char *argv[])
{
link(argv[1],argv[2]);
unlink(argv[1]);
return 0;
}
对tets.c改为tets1.c执行以下命令:
./mymv test.c test1.c
三、目录操作
1. getcwd函数
char *getcwd(char *buf, size_t size);
功能:获取当前进程的工作目录
参数:
buf :缓冲区,存储当前的工作目录size :缓冲区大小
返回值:
成功:buf中保存当前进程工作目录位置失败:NULL
2. chdir函数
int chdir(cohst char *path);
功能:修改当前进程(应用程序)的路径
参数:
path:切换的路径返回值:
成功:0失败: -1
示例:
int main (void)
{
int ret = -l;
char buf[SIZE] ;
//1、获取当前进程的工作目录memset(buf, 0, SIZE);
if (NULL =getcwd(buf, SIZE))
{
perror("getcwd error");
return 1;
}
printf( " buf: %s \n", buf);
//2.改变当前进程的工作目录
ret = chdir( "/home/deng");
if (-1 ==ret)
{
perror ("chdir error");
return 1;
}
//3.获取当前进程的工作目录memset(buf,0,SIZE);
if (NULL == getcwd(buf,SIZE))
{
perror ("getcwd error");
return 1;
}
printf("buf: %s\n" , buf);
}
3. opendir函数
DIR *opendir(const char *name) ;
功能:打开一个目录
参数:
name:目录名返回值:
成功:返回指向该目录结构体指针失败:NULL
4. closedir函数
int closedir(DIR *dirp);
功能:关闭目录
参数:
dirp: opendir返回的指针返回值:
成功:0失败: -1
示例:
//目录打开和关闭
int main(void)
{
DIR *dir = NULL;
//1.打开日录
dir = opendir("test");
if (NULL == dir)
{
perror ("opendir error");
return l;
}
closedir(dir);
return 0;
}
5. readdir函数
struct dirent *readdir(DIR *dirp);
功能:读取目录
参数:
dirp: opendir的返回值返回值:
成功:目录结构体指针失败:NULL
相关结构体说明:
struct dirent{
ino_t d_ino; //此目录进入点的inode
off_t d_off; //目录文件开头至此目录进入点的位移
signed short int d_reclen; // d_name 的长度,不包含NULL字符
unsigned char d_type; // d_type所指的文件类型
char d_name [256]; //文件名};
实现 ls -a 代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<dirent.h>
int main(int argc,char *argv[])
{
DIR * dp;
dp = opendir(argv[1]);
if (dp == NULL){
perror("opendir error");
exit(1);
}
struct dirent *sdp;
while((sdp = readdir(dp)) != NULL){
printf("%s\t",sdp->d_name);
}
printf("\n");
closedir(dp);
return 0;
}
在当前目录下使用命令,执行:./myls ./ 即展示当前目录下的文件及大小
四、递归遍历目录
任务需求:使用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()
代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<dirent.h>
#include<stdio.h>
#include<sys/stat.h>
#define PATH_LEN 256
//dir=/homeitgan/linux fcn=isfile
void fetchdir(const char *dir,void (*fcn)(char *))
{
char name[PATH_LEN];
struct dirent *sdp;
DIR *dp;
if ((dp = opendir(dir)) == NULL){ //打开目录失败
//perror("fetchdir can't open");
fprintf(stderr,"fetchdir:can't open %s\n",dir);
return;
}
while ((sdp = readdir(dp)) != NULL){
if (strcmp(sdp->d_name,".") == 0 || strcmp(sdp->d_name,"..") == 0){ //防止出现无限递归
continue;
}
if (strlen(dir)+strlen(sdp->d_name)+2>sizeof(name)){
fprintf(stderr,"fetchdir:name %s %s too long\n",dir,sdp->d_name);
}else{
sprintf(name,"%s/%s",dir,sdp->d_name);
(*fcn)(name);
}
}
closedir(dp);
}
void isfile(char *name)
{
struct stat sbuf;
if(stat(name,&sbuf) == -1){
fprintf(stderr,"isfile:can't access %s\n",name);
exit(1);
}
if ((sbuf.st_mode & S_IFMT) == S_IFDIR){
fetchdir(name,isfile);
}
printf("%8ld %s\n",sbuf.st_size,name);
}
int main(int argc,char *argv[])
{
if(argc == 1)
isfile(".");
else
while (--argc > 0) //可一次查询多个目录
isfile(*++argv);//循环调用该函数处理各个命令行传入的目录
return 0;
}
执行 ./ls_R test.c 可查看test.c的文件大小
执行 ./ls_R 可查看当前目录下所有文件的大小
五、文件描述符复制
1. dup和dup2
用来做重定向,本质就是复制文件描述符
重定向:
cat myls.c > out 将myls.c的东西读出再写到out文件中
cat myls.c >> out 将myls.c的东西读出再追加到out文件中
dup()和dup2()用来做重定向,本质就是复制文件描述符,使新的文件描述符也标识旧的文件描述符所标识的文件;
这个过程类似于现实生活中的配钥匙,一把钥匙对应一把锁,然后我们又去配了一把新钥匙,此时两把钥匙都可以打开锁,而dup()和dup2()也一样,原来的文件描述符和新复制出来的文件描述符都指向同一个文件,我们操作这两个文件描述符的任何一个 都能操作它所对应的文件
dup函数
int dup(int oldfd);
功能:
通过oldfd复制出一个新的文件描述符,新的文件描述符是调用进程文件描述符表中最小可用的文件描述符,最终oldfd和新的文件描述符都指向同一个文件。
参数:
oldfd :需要复制的文件描述符oldfd返回值:
成功:新文件描述符失败:-1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
int main(int argc,char *argv[])
{
int fd = open("./out",O_RDONLY);//012 ---3
int newfd = dup(fd);//4
printf("newfd = %d\n",newfd);
return 0;
}
make之后执行输出为:newfd = 4
dup2函数:
int dup2(int oldfd,int newfd);
功能:
通过oldfd 复制出一个新的文件描述符newfd,如果成功,newfd和函数返回值是同一个返回值,最终oldfd和新的文件描述符newfd都指向同一个文件。
参数:
oldfd :需要复制的文件描述符
newfd :新的文件描述符,这个描述符可以人为指定一个合法数字(0 - 1023),如果指定的数字已经被占用(和某个文件有关联),此函数会自动关闭close()断开这个数字和某个文件的关联,再来使用这个合法数字。
返回值:
成功:返回newfd失败:返回-1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
int main(int argc,char *argv[])
{
int fd1 = open(argv[1],O_RDWR);//0123---3
int fd2 = open(argv[2],O_RDWR);//0123---4
int fdret = dup2(fd1,fd2);//返回新文件描述符fd2
printf("fdret = %d\n",fdret);//写入fd1指向的文件
int ret = write(fd2,"1234567",7);//将屏幕输入重定向给fd1所指向的文件
printf("ret = %d\n",ret);
dup2(fd1,STDOUT_FILENO);
printf("-------------------886\n");
return 0;
}
首先新建2个空白文件out和out1
make之后执行以下命令:
./dup2 out out1
首先1234567会写入out文件中,其次再将-------------------886写入out文件
int fdret = dup2(fd1,fd2); 其中fd1 = 3,fd2 = 4
把3拷贝给4,即把4指向3(out文件)
dup2(fd1,STDOUT_FILENO); STDOUT_FILENO = 1
把3拷贝给1,即把1指向3(out文件)
此时指向out的文件描述符有三个
2. fcntl函数
- fcntl用来改变一个【已经打开】的文件的 访问控制属性
- 重点掌握两个参数的使用, F_GETFL,F_SETFL
int (int fd, int cmd, ...)
参数:
fd 文件描述符
cmd 命令,决定了后续参数个数
获取文件状态: F_GETFL
设置文件状态: F_SETFL
返回值:
int flgs = fcntl(fd, F_GETFL);
flgs |= O_NONBLOCK
fcntl(fd, F_SETFL, flgs);
终端文件默认是阻塞读的,这里用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;
}
fcntl实现dup描述符:
int fcntl(int fd, int cmd, ....);
cmd: F_DUPFD
参3:
被占用的,返回最小可用的。
未被占用的, 返回=该值的文件描述符。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<pthread.h>
int main(int argc, char *argv[])
{
int fd1 = open(argv[1],O_RDWR);
printf("fd1 = %d\n",fd1);
int newfd = fcntl(fd1,F_DUPFD,0);//0被占用,fcntl使用文件描述符表中可用的最小文件描述符返回
printf("newfd = %d\n",newfd);
int newfd2 = fcntl(fd1,F_DUPFD,7);//7未被占用,返回>=7的文件描述符
printf("newfd2 = %d\n",newfd2);
int ret = write(newfd2,"YYYYYYYYYYYYY",7);//只能写入7个
printf("ret = %d\n",ret);
return 0;
}
make之后执行:
./fcntl_dup out
out:
fd1 = 3
newfd = 4
newfd2 = 7
ret = 7