zoukankan      html  css  js  c++  java
  • Linux基础 文件和目录

    文件和目录

    前言

    本章讨论文件属性和文件系统内容。除了上一章讨论的普通文件,Linux的文件概念还包括:目录、设备等。在Linux系统中,文件的种类包括:普通文件、目录、符号链接、块设备、字符设备、管道、套接字。

    本章讨论的主要内容为普通文件、目录和符号链接。它们的公共特点是,真实的保存在了硬盘中,而其它类型的文件是内核产生的文件,在硬盘中并不存在。

    文件属性

    通过stat函数或者stat命令可以获得文件属性。
    Snip20161007_10

    文件属性解释
    dev_t st_dev 设备号
    ino_t st_ino inode编号
    mode_t st_mode 访问权限相关
    nlink_t st_nlink 硬链接数量
    uid_t st_uid 拥有该文件的用户
    gid_t st_gid 拥有该文件的组
    dev_t st_rdev 设备号
    off_t st_size 文件尺寸
    blksize_t st_blksize 文件系统的IO尺寸
    blkcnt_t st_blocks 占用的block数量,一个block为512字节
    time_t st_atime 最后访问时间
    time_t st_mtime 最后修改时间
    time_t st_ctime 最后文件状态修改时间
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <stdio.h>
     #include <sys/types.h>
           #include <sys/stat.h>
           #include <fcntl.h>
    
    
    int main()
    {
        struct stat buf;
        int ret = stat(".", &buf);
        if(ret < 0)
        {
            perror("stat");
            return 0;
        }
    
        if(S_ISREG(buf.st_mode))
        {
            printf("this is regular file
    ");
        }
        else if(S_ISDIR(buf.st_mode))
        {
            printf("this is dir
    ");
        }
    
        // 测试拥有该文件的账户是否有读权限
        if(S_IRUSR & buf.st_mode)
        {
            printf("user can read
    ");
        }
    
        open("a.txt", O_CREAT|O_RDWR, 0777);
    
     //   access("./a.out", R_OK|W_OK|X_OK);
    
        printf("file size is %d
    ", (int)buf.st_size);
        getchar();
        return 0;
    }

    3.3 文件类型

    在前言中,提到文件类型包括七种,在stat结构题中,保存了文件的文件类型属性,它的文件类型属性保存在st_mode中。但是七种类型,只需要3位即可,而st_mode是一个整数,因此它还保存其它内容,如果需要判断一个文件属于何种类型,需要一些宏的帮助。


    Snip20161007_11

    int main()
    {
      struct stat buf;
      stat("a.txt", &stat);
      if(S_ISREG(buf.st_mode))
      {
        printf("%s ", "这是普通文件");
      }
    }

    文件类型属性是只读的属性,无法修改。

    3.4 用户和组

    Linux是一个多用户操作系统,因此每个文件都有属性,记录着这个文件属于哪个用户/组。
    用户/组信息可以被修改,可以通过chown来修改文件所属的用户和组信息。
    修改文件所属用户和组,需要root权限。
    新文件所属用户和组,是创建该文件的进程所属用户和组。

    • 实际账户和有效账户

    账户解释
    实际账户 登陆系统时的账户
    有效账户 决定进程的访问资源的账户

    3.5 文件访问权限

    文件使用了9个位来表示访问权限,和文件类型一起,保存在st_mode中。此9位分成3组,每组3个位,分别表示读/写/执行权限,而三个组分别表示拥有该文件的账户,拥有该文件的组,其它用户组的权限。如果对应位是1,表示有权限,如果是0表示没有权限。

    111101101

    文件访问权限经常用8进制来表示,比如上表的权限位可以表示为0755,意思是拥有它的账户对这个文件有读/写/执行权限,而拥有它的组有读/执行权限,其它账户对它有读/执行权限。

           Linux提供一些宏,来测试文件的权限位

                

    • 可以通过access函数来测试程序是否有访问某文件的权限。

    • 创建文件时,可以指定文件的访问权限位,但是会收到umask位影响。

    • 可以通过chmod来修改文件的权限位

    3.6 其它权限位

    3.6.1 SUID

    只能对文件设置,如果文件设置了该位,那么该文件被运行时,对应的进程的权限,不是运行该程序账户的权限,而是拥有该用户的权限。

    在对文件未设置SUID的情况下:

    如果对文件设置了SUID,那么:

    可以通过chmod u+s或者chmod u-s来设置获取去除SUID。
    设置SUID可以让一个普通账户拥有它不该有的权限,容易产生安全漏洞。

    3.6.2 SGID

    可以对文件和目录设置,如果对文件设置,那么它的作用类似SUID,不过影响的是组。

    如果对目录设置,那么拷贝到该目录下的文件都会被置位,除非拷贝是带-p参数。 在Ubuntu下测试并不如此。
    在Ubuntu下设置了目录的SGID之后,在那个目录下创建的文件,拥有者是有效账户,而拥有组是该目录的拥有组。

    命令:
    chmod g+s
    chmod g-s

    3.6.3 StickyBit

    可以对文件或者目录设置,如果对文件设置,那么当这个文件运行并退出后,系统依旧保留该文件对应的映象,这样这个程序再次运行时,就不需要加载再加载了。这个属性的作用并不大,因为它占用了内存。

    如果对目录设置,那么表示在该目录下,账户只能删除和修改它自己创建的文件,对于其它账户创建的文件,它不能修改和删除。这个位作用比较大,在一些公共目录,往往有这个属性,比如/tmp

    命令:
    chmod o+t
    chmod o-t

    总结:

    设置对象设置方法查看效果
    SUID 文件 chmod u+s 如果用户执行权限位为s或者S,则表示SUID有设置 当该文件被执行时,进程所拥有的权限是拥有该文件的账户权限
    SUID 目录 不可设置    
    SGID 文件 chmod g+s 如果组执行权限位为s或者S,则表示GUID有设置 当执行该文件时,进程所属组是该拥有该文件的组
    SGID 目录 chmod g+s 同上 在该目录中创建文件时,该文件的所属组是目录的所属组
    StickyBit 文件 chmod o+t 如果其他执行权限位为t或者T,那么该文件有设置StickyBit 执行该文件并退出后,系统保留该文件占用的一些内存,以便加快下一次的加载运行
    StickyBit 目录 chmod o+t 同上 账户只能修改和删除该目录下属于该账户的文件,不能修改该目录下其他账户创建的文件

    3.7 文件长度

    st_size保存文件的长度,write函数会修改该属性,也可以通过truncate修改文件大小,truncate可以扩大文件或者缩小文件,缩小文件时,文件内容会被删减。

    文件大小可以通过lswc -cstat命令获取。
    也可以通过fseekftell函数配合获取,或者直接通过stat函数获取文件长度。

    3.8 文件系统

    3.8.1 文件管理

    文件系统描述文件在硬盘中的组织,保存在硬盘中的文件只有普通文件、目录、软链接。

    为了更加方便的管理持久性文件存储,操作系统一般对硬盘进行有规划的管理,规划包括:

    • 分区

    • 格式化

    文件系统指一个分区内,文件存储的组织方式。

    Snip20161007_13

    在Linux下,通过mount命令将分区挂载到虚拟文件系统。

    3.8.2 inode

    一个硬盘分区,被格式化之后,可以认为硬盘被划分成两部分:管理数据和数据。管理数据部分保存着这个分区的分区信息,以及inode表。

    inode保存文件的属性信息,stat命令能看到的信息,大部分都是保存在inode里的,一个inode占用128或者256字节,这个依赖具体的文件系统,每当在硬盘上创建一个文件/目录时,系统为这个文件/目录分配一个inode。值得注意的是,文件名,不存在inode中,而是存在文件所在目录的文件内容部分。

    3.8.3 数据块

    数据部分被简单的、按照等大尺寸的划分成n块,一般每块数据块的尺寸为1024-4096,由具体文件系统决定。

    3.8.4 文件

    当创建一个文件时,系统为该文件分配一个inode。如果往该文件写数据,那么系统为该文件分配数据块,inode会记录这个数据块位置,当一个数据块不够用时,系统会继续为它分配数据块。

    3.8.5 目录

    当创建一个目录时,系统为该目录分配一个inode,同时分配一个数据块,并且在该数据块中,记录文件...对应的inode。

    如果在该目录下创建文件newfile,那么参考上一节内容,会为该文件创建inode,最后将newfile文件名和它的inode,作为一条记录,保存在目录的数据块中。

     

    Snip20161007_14

    如果一个inode被别人引用,那么它的引用计数器会加1。

    3.8.6 路径和寻址

    Linux系统采用以/划分的路径字符串来寻址文件。

    比如命令mkdir testdir,寻址和操作过程如下图:


    Snip20161008_17

    思考:为什么mv命令很快,而cp命令很慢,rename如何实现的

    补充:分区

    查看磁盘信息

    sudo fdisk -l

    磁盘名字 sda sdb ..
    分区名字 sda1 sda2 ...
    分区

    sudo fdisk /dev/sdb

    n 创建新分区
    p 输出分区信息
    w 保存分区信息并退出

    分区和挂载

    sudo mkfs.ext4 /dev/sdb1
    sudo mount /dev/sdb1 xxyy

    挂载成功之后,对xxyy目录的读写,其实是在/dev/sdb1文件系统中。

    开机自动挂载
    通过mount挂载的目录是临时的。如果希望开酒就挂载,那么可以将挂载命令写入到/etc/profile。或者修改/etc/fstab文件,/etc/fstab描述了开机需要挂载的文件系统信息。

    去除挂载
    通过手动umount去除挂载。

    3.8.7 硬链接和软链接

    硬链接不占用inode,只占用目录项。
    软链接占用inode。

    创建链接命令ln,硬链接只将对应的inode在目录总增加一个名字,并且将inode的引用计数器+1。

    为了可以跨文件系统和对目录进行链接,创建了软链接这种方式。ln -s

    // file --> file2
    int main()
    {
        // get file2
        struct stat buf;
        stat("file", &buf);
     
        // get 链接文件file的信息
        struct stat buf2;
        lstat("file", &buf2);
     
        // 如果lstat的参数所指文件不是链接文件
        // 那么它的效果和stat一样
        struct stat buf3;
        lstat("file2", &buf3)
    }
    读取symlink内容使用readlink命令。
    删除软链接不会删除软链接指向的文件。

    思考:为什么硬链接不能跨文件系统,而且不能对目录进行硬链接

    3.8.8 虚拟文件系统VFS

    内存无法加载硬盘所有内容,因为一般内存比硬盘小,但是在Linux内核中,维护了一个虚拟文件系统,将硬盘的目录结构映射到内存中。这个映射一般只包含已经被打开的文件。


    Snip20161008_18

    Snip20161019_1

    3.9 文件删除

    使用unlink命令和函数可以删除一个文件。
    如果此时文件已经打开,那么该文件也可以被unlink,但是删除操作不会立即执行,而会被保留到文件关闭时执行。

    unlink 删除文件,如果是链接,就删除链接,如果不是链接就删除文件。
    rmdir 只能删除空目录
    rm 会判断参数类型,如果是文件那么调用unlink,如果是目录调用rmdir
    如果要删除非空目录,要使用rm -r,-r选项先删除目录中的文件,再调用rmdir。
    int rm(const char* path);
    {
        // 怎么遍历目录
    }

    3.10 文件时间

    对文件的访问,会导致文件时间发生变化。系统会自动记录用户对文件的操作的时间戳,以便将来可以查询文件修改时间。

    如果需要故意修改,那么可以通过utime函数,修改文件的访问时间和修改时间。

    touch命令也可以将文件的时间修改为当前时间。touch命令的副作用是,如果参数所指文件不存在,那么创建一个空文件。

    当用户进行大规模拷贝时,cp操作会修改文件的访问时间,如果想提高效率,可以使用-p选项,避免文件属性的修改,同时加快速度。

     
    #include <sys/types.h>
           #include <utime.h>
    
    int main()
    {
        struct utimbuf buf;
        buf.actime = 0;
        buf.modtime = 0;
        utime("a.out", &buf);
    }

    利用utime来修改文件的访问时间和修改时间

    #include "../h.h"
    int main()
    {
        struct utimbuf buf;
        buf.actime = 0;
        buf.modtime = 0;
        utime("testfile", &buf);
     
        struct timeval tv[2];
        tv[0].tv_sec = 100000;
        tv[0].tv_usec = 10000;
     
        tv[1].tv_sec = 100000;
        tv[1].tv_usec = 10000;
        utimes("testfile", tv);
    }

    3.11 目录操作

    3.11.1 创建和删除目录

    mkdirrmdir

    3.11.2 遍历目录

    opendirclosedirreaddirrewinddirtelldirseekdir

    遍历目录

    #include "../h.h"
     
    int rm(const char* path)
    {
        // 判断path是个文件还是目录
        // 如果是文件,直接unlink然后返回
        struct stat stat_buf;
        int ret = stat(path, &stat_buf);
        if(ret < 0)
        {
            perror("stat");
            return -1;
        }
        if(!S_ISDIR(stat_buf.st_mode))
        {
            unlink(path);
            return 0;
        }
     
        // 如果path是目录,遍历目录中的所有目录项
        char buf[1024];
        DIR* dir = opendir(path);
        if(dir == NULL)
            return -1;
     
        struct dirent* entry = readdir(dir);
        while(entry)
        {
            // 通过entry得到文件信息
            sprintf(buf, "%s/%s", path, entry->d_name);
            if(entry->d_type == DT_REG || entry->d_type == DT_LNK)
            {
                unlink(buf);
            }
            if(entry->d_type == DT_DIR)
            {
                // 忽略.和..目录
                if(strcmp(entry->d_name, ".") == 0
                        ||strcmp( entry->d_name, "..") == 0)
                {
                    entry = readdir(dir);
                    continue;
                }
                rm(buf);
            }
            entry = readdir(dir);
        }
     
        closedir(dir);
        rmdir(path);
        return 0;
    }
     
    int main(int argc, char* argv[])
    {
        if(argc == 1)
        {
            printf("usage: %s [pathname] ", argv[0]);
            return 0;
        }
        rm(argv[1]);
        return 0;   
    }

    seekdir和telldir

     
    #include "../h.h"
     
    int main()
    {
        long loc;
        DIR* dir = opendir("testdir");
     
        // no
        // seekdir(dir, 2);
        //
        struct dirent* entry;
        while(1)
        {
            loc = telldir(dir);
            entry = readdir(dir);
            if(entry == NULL) break;
     
            if(strcmp(entry->d_name, "a") == 0)
            {
                // 记录文件a的位置
                break;
            }
        }
     
        seekdir(dir, loc);
     
        while(1)
        {
            entry = readdir(dir);
            if(entry == NULL)
                break;
            printf("loc is %d, entry->d_name=%s ", (int)telldir(dir), entry->d_name);
        }
     
        // 将文件指针回到a的位置
    //    seekdir(dir, loc);
    }
    #include <dirent.h>
    #include <sys/types.h>
    #include <dirent.h>
    #include <stdio.h>
    
    int main(int argc, char* argv[])
    {
        DIR* dir = opendir(argv[1]);
        struct dirent* entry;
    
        while(1)
        {
            entry = readdir(dir);
            if(entry == NULL)
                break;
    
            // linux下,.开头是隐藏文件
            if(entry->d_name[0] == '.')
                continue;
    
            printf("%s
    ", entry->d_name);
        }
    
        closedir(dir);
    }

    3.12 练习

    3.12.1 实现文件拷贝,保留文件属性

    #include "../h.h"
     
    int main(int argc, char* argv[])
    {
        if(argc != 3)
        {
            printf("usage: %s [srcfile] [dstfile] ", argv[0]);
            return -1;
        }
     
        const char* filesrc = argv[1];
        const char* filedst = argv[2];
     
        FILE* fp_src = fopen(filesrc, "r");
        FILE* fp_dst = fopen(filedst, "w");
     
        char buf[4096];
        while(1)
        {
            int ret = fread(buf, 1, sizeof(buf), fp_src);
            if(ret <= 0)
                break;
            fwrite(buf, ret, 1, fp_dst);
        }
     
        fclose(fp_src);
        fclose(fp_dst);
     
     
        // 获取源文件属性
        struct stat src_stat;
        stat(filesrc, &src_stat);
     
        // 修改目标文件时间
        struct utimbuf timbuf;
        timbuf.actime = src_stat.st_atime;
        timbuf.modtime = src_stat.st_mtime;
        utime(filedst, &timbuf);
     
        // 修改权限
        chmod(filedst, src_stat.st_mode);
     
        // 修改所有者
        int ret = chown(filedst, src_stat.st_uid, src_stat.st_gid);
        if(ret < 0)
        {
            perror("chown");
        }
     
        return 0;
    }

    3.12.2 实现目录打包到文件,将文件解包成目录

     
    #include "../h.h"
     
    #include <map>
    #include <string>
    using namespace std;
     
    map<ino_t, std::string> savedfiles;
     
    void tarfile(const char* filename, FILE* fpOut)
    {
        struct stat stat_buf;
        stat(filename, &stat_buf);
     
        // 检查这个文件是否已经保存过,是否是其他文件的硬链接
        std::string filesaved = savedfiles[stat_buf.st_ino];
        if(filesaved.size() != 0)
        {
            // 此ino之前已经存过了
            fprintf(fpOut, "h %s %s ", filename, filesaved.c_str());
            return;
        }
     
        fprintf(fpOut, "f %s %lld ", filename, (long long int)stat_buf.st_size);
     
        FILE* fpIn = fopen(filename, "r");
        char buf[4096];
        while(1)
        {
            int ret = fread(buf, 1, sizeof(buf), fpIn);
            if(ret <= 0)
                break;
            fwrite(buf, ret, 1, fpOut);
        }
        fclose(fpIn);
     //   savedfiles.insert(std::pair<ino_t, std::string>(stat_buf.st_ino, std::string(filename)));
        // 如果该文件不是别人的硬链接,那么将文件拷贝之后,在全局map中记录ino
        savedfiles[stat_buf.st_ino] = std::string(filename);
    }
     
    int tardir(const char* dirname, FILE* fpOut)
    {
        char filepath[1024];
     
        // 写目录
        fprintf(fpOut, "d ");
        fprintf(fpOut, "%s ", dirname);
     
        DIR* dir = opendir(dirname);
        struct dirent* entry = readdir(dir);
        while(entry)
        {
            sprintf(filepath, "%s/%s", dirname, entry->d_name);
            // handle
            if(entry->d_type == DT_REG)
            {
                // write file
                tarfile(filepath, fpOut);
            }
            else if(entry->d_type == DT_DIR)
            {
                if(strcmp(entry->d_name, ".") == 0 ||
                        strcmp(entry->d_name, "..") == 0)
                {
                    entry = readdir(dir);
                    continue;
                }
     
                tardir(filepath, fpOut);
            }
            entry = readdir(dir);
        }
     
        closedir(dir);
    }
     
    int tar(const char* dirname, const char* outfile)
    {
        FILE* fpOut = fopen(outfile, "w");
     
        fprintf(fpOut, "xgltar ");
        fprintf(fpOut, "1.0 ");
     
        int ret = tardir(dirname, fpOut);
     
        fclose(fpOut);
        return ret;
    }
     
     
    #define line_buf_size 1024
    char line_buf[line_buf_size];
    #define get_line() fgets(buf, line_buf_size, fin)
     
    int untar1(FILE* fin)
    {
        char* buf = line_buf;
        if(get_line() == NULL)
            return -1;
     
        printf("now utar type=%s", buf);
     
        if(strcmp(buf, "d ") == 0)
        {
            get_line();
            buf[strlen(buf)-1] = 0;
            mkdir(buf, 0777);
            printf("mkdir %s ", buf);
        }
        else if(strcmp(buf, "f ") == 0)
        {
            get_line();
            buf[strlen(buf)-1] = 0; // filename
            FILE* out = fopen(buf, "w");
            printf("create file %s ", buf);
     
            get_line();
            long long int len = atoll(buf); // 1987
            printf("filelen %s ", buf);
     
            while(len > 0)
            {
                // 避免粘包问题
                int readlen = len < line_buf_size ? len : line_buf_size;
                int ret = fread(buf, 1, readlen, fin);
                fwrite(buf, 1, ret, out);
                len -= ret;
            }
     
            fclose(out);
        }
        else if(strcmp(buf, "h ") == 0)
        {
            get_line();
            buf[strlen(buf)-1] = 0; // filename new
            std::string new_path(buf);
     
            get_line();
            buf[strlen(buf)-1] = 0; // hardline filename old
     
            link(buf, new_path.c_str());
        }
     
        return 0;
    }
     
    int untar(const char* tarfile)
    {
        char* buf = line_buf;
     
        FILE* fin = fopen(tarfile, "r");
        //fgets(buf, line_buf_size, fin);
        get_line();
        if(strcmp(buf, "xgltar ") != 0)
        {
            printf("unknown file format ");
            return -1;
        }
     
        get_line();
        if(strcmp(buf, "1.0 ") == 0)
        {
            while(1)
            {
                int ret = untar1(fin);
                if(ret != 0)
                    break;
            }
     
        }
        else
        {
            printf("unknown version ");
            return -1;
        }
        return 0;
    }
     
     
    // ./mytar -c dir tarfile.xgl
    // ./mytar -u tarfile.xgl
    int main(int argc, char* argv[])
    {
        if(argc == 1)
        {
            printf("usage:  %s -c [dir] [tarfile] %s -u [tarfile] ", argv[0], argv[0]);
            return -1;
        }    
     
        const char* option = argv[1];
        if(strcmp(option, "-c") == 0)
        {
            const char* dirname = argv[2];
            const char* outfile = argv[3];
            return tar(dirname, outfile);
        }
        else if(strcmp(option, "-u") == 0)
        {
            const char* tarfile = argv[2];
            return untar(tarfile);
        }
     
        printf("option error ");
        return -1;
    }

    3.13 函数和命令

    3.13.1 函数

    stat/lstat:查看文件属性
    chmod:修改文件权限
    chown:修改文件的所有者
    utime:修改文件时间
    unlink:删除文件
    link:创建硬链接
    symlink:创建软链接
    rmdir:删除空目录
    mkdir:创建空目录
    opendir:打开目录
    closedir:关闭目录
    readdir:读取一个目录项,并且将目录项指针移到下一项
    seekdir:修改目录项指针
    rewainddir:重置目录项指针
    telldir:获得当前目录向指针
    判断权限位宏 S_IRUSR(stat.st_mode)
    判断文件类型宏S_ISDIR(stat.st_mode)

    3.13.2 命令

    stat:查看文件属性
    chmod:修改文件权限
    chown
    unlink:删除文件(不会跟随)
    ln:创建链接
    mkdir
    rmdir
    rm
    cp
    dd:拷贝数据(可以拷贝文件,也拷贝块设备)
    wc:计算文件内容的行数、单词数、字节数
    which:查找非内置的命令位置
    fdisk:查看磁盘信息、分区
    mkfs:在分区中创建文件系统(ext2,ext3,ext4, fat32, ntfs, xfs,nfs)
    mount:挂载
    umount:取消挂载

  • 相关阅读:

    二分查找法
    LeetCode-Two Sum III
    LeetCode-Add and Search Word
    LeetCode-Longest Substring with At Least K Repeating Characters
    LeetCode-Rearrange String k Distance Apart
    LeetCode-Game of Life
    LeetCode-Walls and Gates
    LeetCode-Water and Jug Problem
    LeetCode-Inorder Successor in BST
  • 原文地址:https://www.cnblogs.com/w-x-me/p/Linux.html
Copyright © 2011-2022 走看看