• 硬链接,软链接,link,rename,symlink,opendir和readdir


    什么是硬链接

    struct stat {
                   nlink_t   st_nlink;       /* Number of hard links
                };
    
    • 1
    • 2
    • 3

    stat结构体就有一个成员变量----硬链接数

    • 使用ln命令就可以创建硬链接

    • 创建硬链接,就是再为文件创建一个名字

    每创建一个硬链接,文件就多一个文件名,硬件链接数+1

    • 多个文件名指向了同一个文件,操作文件时,使用任何一个名字都可以

    创建硬链接后所得到的多个文件名,指向的同一个inode节点,只有inode节点代表了文件的真实存在,inode节点只有一个,因此多个文件名指向的是同一个文件,不管使用的是哪一个文件名,都能操作这个文件。

    硬链接数
    • 记录了有多少个文件名指向了inode节点,通过创建硬链接,每增加一个文件名,就多一个硬链接数
    • 同理,每删除一个硬链接,也就是删除一个文件名,就少一个硬链接数

    删除文件时,只是将文件的inode节点空间释放了,如果这个文件有数据的话,那么这个文件的数据仍然还在,在这种情况下,只要将文件的inode节点空间恢复,即可还原该文件。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7fe3Diap-1667544982020)(/home/guojiawei/.config/Typora/typora-user-images/image-20221104144320671.png)]

    如图,新创建的目录的硬链接数是2.

    因为新创建的目录,一开始就有两个名字指向了目录的inode节点,分别是目录的本名text和.

    • 在该目录下,每多创建一个目录,当前目录就会多一个硬链数

    Linux不允许用户自己给目录创建硬链接,只能由Linux系统自己给目录创建硬链接

    用户只能给目录以外的,其它类型的文件创建硬链接

    目录

    目录是特殊的文件,本质上是一个表格,用于存储文件名和inode节点空间

    可以在目录中创建多个链接指向文件,这些链接指向相同的inode节点空间

    link函数

    #include 
    //功能:为非目录文件建立一个新的硬连接(ln命令就是调用这个系统函数来实现的)
    //返回值:调用成功返回0,失败返回-1,errno被设置
    int link(const char *oldpath, const char *newpath);
    
    • 1
    • 2
    • 3
    • 4
     link("./mm.txt","./mm1.txt");
    
    • 1

    unlink函数

    #include 
    //功能:删除一个硬连接,其实就是删除一个名字。	
    //返回值:调用成功返回0,失败返回-1,errno被设置
    //参数:pathname:要删除路径名
    int unlink(const char *pathname);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    只能用于删除非目录文件的硬链接,不能删除目录的硬链接,Linux系统不允许用户修改目录的硬件链接

    使用unlink创建临时文件

    临时文件:

    • 只在程序运行过程中有效,程序运行结束后就自动删除

    • 使用unlink就可以实现一个临时文件

    open创建一个文件后(新文件的硬链接数都是1),然后立即调用unlink将文件硬链接数减为0,将其删除。

    虽然文件的硬链接数变成了0,但是在进程没有结束之前,这个文件仍然可以被使用,直到进程结束后,文件才被删除

    #include
    #include
    #include 
    #include 
    #include 
    #include 
    
    int main(){
        int fd=open("mm.txt",O_RDWR|O_CREAT,0664);
        if(fd==-1){
            perror("open fail!");
            exit(-1);
        }
        unlink("./mm.txt");
        write(fd,"hello,world!\n",12);
        lseek(fd,0,SEEK_SET);
        char buf[30]={0};
        read(fd,buf,sizeof(buf));
        printf("buf=%s\n",buf);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    remove函数

    #include 
    //功能:可以用于删除任何文件(既可以删除目录文件,也可以删除非目录文件)
    //删除非目录文件时,功能与unlink一样。
    //返回值:调用成功返回0,失败返回-1,errno被设置
    int remove(const char *pathname);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    int main(){
        remove("./aaa");//删除目录aaa
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4

    rename函数

    mv命令

    • 改名
    • 移动
    • 移动+改名

    mv ./mm.txt …/mm_aa.txt

    #include 
    //功能:修改文件路径名,将旧的路径名oldpath,改为新的路径名newpath
    //返回值:调用成功返回0,失败返回-1,errno被设置
    int rename(const char *oldpath, const char *newpath);
    
    • 1
    • 2
    • 3
    • 4
    rename("./mm_aa.txt","./aaa/mm.txt");//使用
    
    • 1

    如果文件移动起始位置和目标位置,在同一个分区里面的话

    移动文件时,不会移动文件的数据;

    只是把文件的基本信息(名字、inode编号),从这个目录记录到另一个目录下。

    什么是符号链接

    符号链接文件也被称为软链接文件

    使用ln -s就可以创建符号链接文件

    • 符号链接文件就是一个快捷图标,它指向了另一个文件

    和windows一样,原文件被删除了,快捷指向就没意义了


    软链接和硬链接的对比

    • 创建硬连接

      同一个文件有多个不同的名字,它们指向是同一个inode节点

    • 创建符号链接文件

      符号链接文件与它所指向的文件,是两个完全不同的独立的文件,拥有自己独立的inode节点。

      符号链接文件的数据就是指向文件的文件名,文件大小就是名字的字符个数

    不能给目录创建硬链接,但是可以给目录创建符号链接;只要你有需要,可以给任何文件创建符号链接文件。而且可以给符号链接文件,创建硬链接

    s_link是link目录的软链接,p_s_link是s_link的硬链接

    symlink

    #include 
    //功能:为oldpath,创建符号连接文件newpath
    //函数返回值:调用成功返回0,失败返回-1,errno被设置
    int symlink(const char *target, const char *linkpath);
    
    • 1
    • 2
    • 3
    • 4

    使用ln创建硬链接时,调用的是link函数; 使用ln -s创建符号链接时,调用的是symlink

    readlink

    #include 
    //功能:读符号链接文件的数据(指向文件的名字)
    /*
        1)const char *path:符号连接文件的路径名。
    	2)char *buf:存放名字缓存的地址。
    	3)size_t bufsiz:缓存的大小
    */
    //返回值:调用成功,返回读到的字节数,失败返回-1,errno被设置
    ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    int main(){
        symlink("./link.c","slink");
        char buf[30]={0};
        readlink("./slink",buf,sizeof(buf));
        printf("buf=%s\n",buf);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    stat半成品

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    void print_error(char *str){
    	perror(str);
    	exit(-1);
    }
    
    int main(int argc, char **argv){
    	int ret = 0;
    	int i = 0;
    	struct stat sta = {0};
    	if(argc != 2){
    		printf("./a.out fileName\n\n");
    		exit(-1);
    	}
    
    	/* 获取文件属性 */
    	ret = lstat(argv[1], &sta);
    	if(-1 == ret) print_error("stat fail");
    	
    	/* 打印文件类型 */
    	char file_type = '0';	
    	if(S_ISLNK(sta.st_mode)) file_type = 'l'; 
    	else if(S_ISREG(sta.st_mode)) file_type = '-';
    	else if(S_ISDIR(sta.st_mode)) file_type = 'd';
    	else if(S_ISCHR(sta.st_mode)) file_type = 'c';
    	else if(S_ISBLK(sta.st_mode)) file_type = 'b';
    	else if(S_ISFIFO(sta.st_mode)) file_type = 'p';
    	else if(S_ISSOCK(sta.st_mode)) file_type = 's';
    	printf("%c", file_type);
    
    	/* 打印文件权限 */	
    	char buf[10] = {0};
    	char tmp_buf[] = "rwxrwxrwx";
    	for(i=0; i<9; i++)
    	{
    		if(sta.st_mode & (1<<(8-i))) buf[i] = tmp_buf[i];
    		else buf[i] = '-';
    	}
    	printf("%s", buf);
    	
    	/* 打印文件属性 */
    	printf(" %lu %u %d %ld %ld %s", sta.st_nlink, \
    		sta.st_uid, sta.st_gid, sta.st_size, sta.st_atime, argv[1]);
        //打印出链接文件的指向
    	if(file_type=='l'){
    		char mm[30]={0};
    		readlink(argv[1],mm,sizeof(mm));
    		printf("->%s\n",mm);
    	}
    	else printf("\n");
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    符号跟随函数 与 符号不跟随函数

    (1) 符号跟随函数

    调用某个函数操作文件,当指定的路径名是符号链接文件时:

    如果函数最后操作的是符号链接文件所指向的文件,而不是“符号链接文件”本身,

    这个函数就是符号跟随函数,因为它跟到符号链接文件所指向的背后去了。

    • 比如stat、open就是符号跟随函数,因为这些操作的都是符号链接文件所指向的文件

    (2)符号不跟随函数

    当路径名是符号链接文件时,函数操作的就是符号链接文件本身,不会跟随

    比如lstat就是符号不跟随函数,因为获取文件属性时,如果操作的是符号链接文件的话,那么获取的是符号链接文本身的属性。

    • 凡是需要指定“文件路径名”函数,只要函数名字是l打头的,比如像lstat,基本都是符号不跟随函数。
    • lseek不需要指定路径名,而是通过文件描述符(fd)操作的,不存在符号跟随与不跟随的问题。

    getcwd

    #include 
    //这是一个库函数,执行pwd命令就是调用这个函数实现的
    //功能:获取进程的当前工作目录
    /*参数
    	1)buf:存放获取到的当前路径的缓存
    	2)size:缓存的大小
    */
    char *getcwd(char *buf, size_t size);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    int main(){
        char buf[30]={0};
        char *p=getcwd(buf,100);
        printf("pwd=%s\n",buf);
        printf("p=%p,buf=%p\n",p,buf);
        return 0;
    }
    /*执行结果:
    pwd=/home/guojiawei/Desktop/link
    p=0x7ffe8d093ac0,buf=0x7ffe8d093ac0
    getcwd的返回值是存放路径的地址
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    chdir

    change chdir

    #include 
    //功能:切换进程当前工作目录到path。
    int chdir(const char *path);
    
    • 1
    • 2
    • 3
    int main()
    {
        char buf[30] = {0};
        char *p = getcwd(buf, 100);
        printf("pwd=%s\n", buf);
        chdir("../");
        getcwd(buf, 100);
        printf("pwd_cur=%s\n", buf);
        return 0;
    }
    /*执行结果:
    pwd=/home/guojiawei/Desktop/link
    pwd_cur=/home/guojiawei/Desktop
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    cd命令就是调用chdir的命令

    mkdir

    #include 
    #include 
    //功能:创建新目录。
    /*参数:
      1)pathname:需创建目录的路径名
      2)mode:指定目录的原始权限,一般给0775
    */
    int mkdir(const char *pathname, mode_t mode);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    给目录指定原始权限时,一定要有x权限,否者无法进入这个目录

    rmdir

    rm命令删除目录时,调用的都是rmdir这个函数

    • rmdir命令:只能删除空目录,rmdir命令用的很少
    • rm:不管目录空不空,都能删除,rm用的最多
    	                 rm
    					 |
    				   remove
    				  |     |
    				  |     |
    				 /       \
    				/         \
    			unlink 	     rmdir
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    #include 
    //函数功能:删除路径名为pathname的这个目录
    int rmdir(const char *pathname);
    
    • 1
    • 2
    • 3

    删除时,Linux系统会调用相关函数,将目录硬链数全部减位0,然后目录就被删除了

    如果目录不为空,必须递归调用rmdir函数,实现递归删除

    opendir和readdir

    • opendir:打开目录,以便调用readdir读取目录项
    • readdir:读取目录里面的目录项

    目录项:
    目录里面的数据,其实就是一条一条的目录项,每个目录项就是一个文件的基本信息

    文件基本信息:文件名inode节点号

    不能使用open函数打开目录,只能使用opendir打开

    #include 
    #include 
    //功能:打开目录
    //参数:name---需打开目录的路径名
    /*返回值:
     调用成功:返回一个DIR *的指针,这个指针指向了被打开的目录,readdir通过这个指针就可以读取目录的目录项。
     调用失败:返回NULL,errno被设置。
    */
    DIR *opendir(const char *name);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    #include 
    //功能:读取目录里的目录项-----每调用一次,就读取出一条目录项。
    //参数dirp:opendir打开目录时,得到的指针。
    //返回值:调用成功,返回指针指向struct dirent结构体的指针
    /*
    返回NULL的话有如下两种情况:
    						1)读到目录的末尾时,返回NULL。
    						2)函数调用失败时,也返回NULL,不过errno被设置。	
    */
    struct dirent *readdir(DIR *dirp);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 结构体 struct dirent

      这个结构体就是用来存放一条目录项的:

      调用readdir读取到目录项后,会自动开辟一个struct dirent变量来存放目录项,然后将变量的指针返回;

      应用程序通过这个指针,就可以访问结构体中的目录项信息(文件基本信息)。

      struct dirent {
                     ino_t          d_ino;       /* Inode number */
                     off_t          d_off;       /* Not an offset; see below */
                     unsigned short d_reclen;    /* Length of this record */
                     unsigned char  d_type;      /* Type of file; not supported
                                                    by all filesystem types */
                     char           d_name[256]; /* Null-terminated filename */
                   };
      //最重要的就是:inode编号和文件名
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      怎么判断函数是否调用失败了呢?

      • 如果ernno==0,表示没有设置错误号,返回NULL是因为读到了文件的末尾。
      • 如果errno!=0,表示是因为函数调用出错而返回的NULL。
    #include 
    #include 
    //opendir头文件
    #include 
    #include 
    #include 
    
    int main(){
        DIR *pot=opendir(".");
        if(pot==NULL){
            perror("opendir fails!");
            exit(-1);
        }
        while(1){//循环读
        struct dirent *dir_p=readdir(pot);
        if(dir_p==NULL && errno!=0){
             perror("readdir fails\n");
        }
        if(dir_p==NULL && errno==0) break;//读到末尾
        printf("inode=%lu,fname=%s\n",dir_p->d_ino,dir_p->d_name);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    chmod 和 fchmod

    #include 
    //功能:修改文件权限
    //chmod:使用路径名操作
    //fchmod:使用文件描述符操作
    int chmod(const char *pathname, mode_t mode);
    int fchmod(int fd, mode_t mode);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    正确主动关闭websocket,异常关闭处理
    OK3568 UBUNTU 安装使用I2C-TOOLS
    图书管理系统
    ElasticSearch中关于Nasted嵌套查询的介绍:生动案例,通俗易懂,彻底吸收
    C++ 虚函数表原理和类的内存分布
    node.js --- MVC
    Clickhouse同步mysql(基于物化引擎)
    树莓派(六)树莓派交叉编译
    浅尝KBQA中使用语义角色标注进行约束挂载
    CentOS安装gitlab服务及创建git仓库实践笔记
  • 原文地址:https://blog.csdn.net/weixin_47173597/article/details/127689155