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:取消挂载

原文地址:https://www.cnblogs.com/w-x-me/p/Linux.html