Linux下文件/目录操作的相关函数

1.文件系统

把一个磁盘分成一个或多个分区。每个分区可用包含一个文件系统

文件系统是,一组规则,规定对文件的存储及读取的一般方法。文件系统在磁盘格式化过程中指定。

常见的文件系统有:fat32 ntfs exfat ext2 、ext3 、ext4

inode是固定长度的记录项,包含有关文件的大部分信息

inode

其本质为结构体,存储文件的属性信息。如:权限、类型、大小、时间、用户、盘块位置……也叫作文件属性管理结构,大多数的inode都存储在磁盘上。

少量常用、近期使用的inode会被缓存到内存中。

  • 每个inode中有一个链接数,其值为指向该inode的目录项数(上图中有两个目录项指向同一个inode)。只有当链接数减为0时,才删除该文件。链接数包含在stat结构的st_nlink成员中(POSIX.1常量LINK_MAX指定了一个文件链接数的最大值)。这种链接类型称为硬链接
  • 另外一种链接类型称为符号链接。符号链接文件的实际内容(在数据块中)包含了该符号链接所指向的文件的名字
  • inode包含了文件有关的所有信息:文件类型、文件访问权限位、文件长度和指向文件数据块的指针等。stat结构中的大多数信息都取自inode。只有2项重要数据存放在目录项中:文件名和inode号
  • 因为目录项中的inode编号指向同一文件系统中的相应inode,一个目录项不能指向另一个文件系统的inode
  • 在不更换文件系统的情况下为一个文件重命名时,该文件的实际内容并未移动,只需构造一个指向现有inode的新目录项,并删除老的目录项。链接数不会改变

下图为在一个目录下创建一个目录testdir,注意testdir所在目录,以及新建目录testdir的inode链接数:

dentry

目录项,其本质依然是结构体,重要成员变量有两个 {文件名,inode,...},而文件内容(data)保存在磁盘盘块中。

也就是上一点inode图中包含inode结点编号和文件名的那一块

不同的文件有相同的inode号实现存储在同一块分区内,也就是硬链接

硬链接其中就要求到要处在同一块分区内,并且针对的是文件

2.文件操作

stat函数

使用stat函数最多的地方可能就是ls -l命令获得有关一个文件的所有信息

获取文件属性,存在buf中(从inode结构体中获取)
int stat(const char *path, struct stat *buf); 
        成返回0;失败返回-1 设置errno为恰当值。
        参数1:文件路径
        参数2:inode结构体指针,存放文件属性 (传出参数)

文件属性将通过传出参数返回给调用者。
练习:使用stat函数查看文件属性
【stat.c】
#include<syss/types.h>
#include<sys/stat.h>
#include<unistd.h>
文件信息结构用一个结构体stat表示,实际定义可能随具体实现有所不同,但基本形式是:
struct stat{
    mode_t              st_mode;    /*文件模式字,包含有文件类型、ID和读写权限位信息*/
    ino_t               st_ino;     /* inode号 */
    dev_t               st_dev;     /* 设备号(文件系统) */
    dev_t               st_rdev;    /* 特殊文件的设备号 */
    nlink_t             st_nlink;   /* 链接数 */
    uid_t               st_uid;     /* 所有者的用户ID */
    gid_t               st_gid;     /* 组所有者的ID */
    off_t               st_size;    /* 字节大小,用于一般文件 */
    struct timespec     st_atime;   /* 最后一次访问时间 */
    struct timespec     st_mtime;   /* 最后一次修改时间 */
    struct timespec     st_ctime;   /* 最后一个文件状态改变的时间 */
    blksize_t           st_blksize; /* 磁盘块(block)大小 */
    blkcnt_t            st_blocks;  /* 分配的磁盘块(block)数量 */
};

stat结构体中的st_size属性:

stat结构体中的st_mode属性:

st_mode属性:

  • 普通(一般)文件
  • 目录文件
  • 字符特殊文件:这种类型的文件提供对设备不带缓冲的访问,每次访问长度可变(系统中的所有设备要么是字符特殊文件,要么是块特殊文件)
  • 块特殊文件:这种类型的文件提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行
  • FIFO:这种类型的文件用于进程间通信,有时也称为命名管道
  • 符号链接:这种类型的文件指向另一个文件
  • 套接字:这种类型的文件用于进程间的网络通信

提供了一些宏定义供于查看文件是否为目录等

stat函数在查看链接的时候会穿透,比如给一个文件起软链接后,去查看这个软链接对应的链接名,会把它当作一个文件,而不是显示它是一个链接

lstat函数

类似于stat。但是当pathname为一个符号链接时,返回符号链接(而不是由该符号链接引用的文件)的有关信息,存在buf中

【get_file_type.c】
int lstat(const char *path, struct stat *buf); 
成返回0;失败返回-1 设置errno为恰当值。

文件类型判断方法:st_mode 取高4位。 但应使用宏函数:           
       S_ISREG(m)  is it a regular file?
       S_ISDIR(m)  directory?
       S_ISCHR(m)  character device?
       S_ISBLK(m)  block device?
       S_ISFIFO(m) FIFO (named pipe)?
       S_ISLNK(m)  symbolic link?  (Not in POSIX.1-1996.)
       S_ISSOCK(m) socket?  (Not in POSIX.1-1996.)
穿透符号链接:stat:会;lstat:不会

不穿透符号链接其实在其他方面也见到过:

给一个文件 f.c 起符号链接 f.link 后,用cat f.link 或 vim f.link 都能对 f.c 进行操作(穿透符号链接),但是如果用 ls -l f.link 会发现不穿透符号链接,会把 f.c 的文件信息优先显示出来而不是显示 f.link 文件的信息

系统和命令其实就是实现与被实现的关系

另外一种方式去获取文件的类型:(了解)

用按位与的方式,而不是直接采用宏定义去判断

IFMT实际上就是16位的位图掩码,来表示文件的权限类型

特殊权限位置

黏住位

即S_ISVTX

  • 在UNIX尚未使用请求分页式技术的早期版本中,S_ISVTX位被称为粘着位
  • 后来的UNIX版本称它为保存正文位

用途

  • 以前,如果一个可执行文件设置了该位,当程序第一次被执行,在其终止时,程序正文部分的一个副本仍被保存在交换区。这使得下次执行该程序时能较快地将其装载入内存(原因是:通常的UNIX文件系统中,文件的各数据块很可能是随机存放的,相比较而言,交换区是被作为一个连续文件来处理的)
  • 现在,系统扩展了粘着位的使用范围,Single UNIX Specification允许针对目录设置粘着位。如果对一个目录设置了该位,只有满足下列2个情况,才能删除或重命名该目录下的文件:

对该目录具有写权限

  1. 满足下列条件之一:
  2. 拥有此文件
  • 拥有此目录
  • 是超级用户

目录/tmp和/var/tmp就是设置粘着位的典型候选者

setUID位

进程有两个ID:

  • EID(有效用户ID),表示进程履行哪个用户的权限。
  • UID(实际用户ID),表示进程实际属于哪个用户。

多数情况下,EID和UID相同。但是,当文件的setID被设置后两个ID则有可能不一样。

例如:当进程执行一个root用户的文件,若该文件的setID位被设置为1, 那么执行该文件时,进程的UID不变。EID变为root,表示进程开始履行root用户权限。

access函数

测试指定文件是否存在/拥有某种权限。
int access(const char *pathname,  int mode); 
    返回值:成功/具备该权限:0;失败/不具备 -1 设置errno为相应值。
    参数2:R_OK、W_OK、X_OK
通常使用access函数来测试某个文件是否存在。F_OK

chmod函数

 修改文件的访问权限
 int chmod(const char *path, mode_t mode);
     成功:0;失败:-1设置errno为相应值
 int fchmod(int fd, mode_t mode);

truncate函数

截断文件长度成指定长度。常用来拓展文件大小,代替lseek。
       int truncate(const char *path, off_t length);
           成功:0;失败:-1设置errno为相应值
       int ftruncate(int fd, off_t length);

link函数

思考,为什么目录项要游离于inode之外,画蛇添足般的将文件名单独存储呢??这样的存储方式有什么样的好处呢?

其目的是为了实现文件共享。Linux允许多个目录项共享一个inode,即共享盘块(data)。不同文件名,在人类眼中将它理解成两个文件,但是在内核眼里是同一个文件。

link函数,可以为已经存在的文件创建目录项dentry(硬链接)。
int link(const char *oldpath,  const char *newpath);
    成功:0;失败:-1设置errno为相应值
    注意:由于两个参数可以使用“相对/绝对路径+文件名”的方式来指定,所以易出错。
如:link("../abc/a.c", "../ioc/b.c")
    若a.c,b.c都对, 但abc,ioc目录不存在也会失败。

mv命令既是修改了目录项,而并不修改文件本身,可以通过link和unlink来实现mv

硬链接实际上就是创建了一个目录项,与原文件具有相同的inode节点号,指向了同一盘块位置

unlink函数

 删除一个文件的目录项;
int unlink(const char *pathname);
     成功:0;失败:-1设置errno为相应值
练习:编程实现mv命令的改名操作
【imp_mv.c】
注意Linux下删除文件的机制:不断将st_nlink -1,直至减到0为止。无目录项对应的文件,将会被操作系统择机释放。(具体时间由系统内部调度算法决定)

因此,我们删除文件,从某种意义上说,只是让文件具备了被释放的条件。
unlink函数的特征:清除文件时,如果文件的硬链接数到0了,没有dentry对应,但该文件仍不会马上被释放。要等到所有打开该文件的进程关闭该文件,系统才会挑时间将该文件释放掉。
【unlink_exe.c】

实现mv:

比如想实现像mv将a.c改名为b.c:mv ./a.c ./b.c

link("./a.c", "./b.c");

unlink("./a.c");

unlink后目录项被释放掉,此时打开另一个终端去cat查看下该文件是查找不到的,但是unlink后的write是仍可以写的,只不过写入到stat相关的缓冲区当中,因此不会将if语句中的内容打印出来,此时进程还在继续当中,temp.txt文件还没被释放掉

隐形回收

当进程结束运行时,所有该进程打开的文件会被关闭,申请的内存空间会被释放。系统的这一特性称之为隐式回收系统资源。

symlink函数

创建一个符号链接
int symlink(const char *oldpath, const char *newpath);
成功:0;失败:-1设置errno为相应值

readlink函数

读取符号链接文件本身内容,得到链接所指向的文件名。
ssize_t readlink(const char *path, char *buf, size_t bufsiz);
成功:返回实际读到的字节数;失败:-1设置errno为相应值。
在终端窗口中也适用:
ln -s test.c test.soft
cat test.soft
>>>这是一个目录
readlink test.soft
>>>(显示软链接所指向的目录位置)

rename函数

重命名一个文件。
 int rename(const char *oldpath, const char *newpath); 
 成功:0;失败:-1设置errno为相应值

3.目录操作

工作目录:“./”代表当前目录,指的是进程当前的工作目录,默认是进程所执行的程序所在的目录位置。

目录操作的函数是在man的第三卷,是库函数

getcwd函数

获取进程当前工作目录(卷3,标库函数)
char *getcwd(char *buf, size_t size);
    成功:buf中保存当前进程工作目录位置。失败返回NULL。

chdir函数

每个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点(不以斜线开始的路径名为相对路径名)

用户登录到UNIX系统时,其当前工作目录通常是口令文件(/etc/passwd)中该用户登录项的第6个字段——用户的起始目录

  • 当前工作目录是进程的一个属性(所以如果调用chdir修改当前目录,只影响调用函数的进程本身)
  • 起始目录则是登录名的一个属性
改变当前进程的工作目录
int chdir(const char *path); 
    成功:0;失败:-1设置errno为相应值

文件、目录权限

目录文件也是“文件”,其文件内容是该目录下所有子文件的目录项dentry,可以尝试vim打开一个目录。

R

W

X

文件

文件的内容可以被查看

cat、more、less...

内容可以被修改

vi、>...

可以运行产生一个进程

./文件名

目录

目录可以被浏览

ls、tree...

创建、删除、修改文件

mv、touch、mkdir...

可以被打开、进入

cd

目录设置黏住位:若有w权限,创建不变,删除、修改只能由root、目录所有者、文件所有者操作。

查看目录的详情:

ls -ld test.dir/

opendir函数

根据传入的目录名打开一个目录 (库函数)
DIR * 类似于 FILE *
DIR *opendir(const char *name);  
# 成功返回指向该目录结构体指针,失败返回NULL
# 参数支持相对路径、绝对路径两种方式:例如:打开当前目录:① getcwd() , opendir() ② opendir(".");

closedir函数

关闭打开的目录
int closedir(DIR *dirp);
# 成功:0;失败:-1设置errno为相应值

readdir函数

读取目录(库函数)
struct dirent *readdir(DIR *dirp);  
# 成功返回目录项结构体指针;失败返回NULL设置errno为相应值
# 需注意返回值,读取数据结束时也返回NULL值,所以应借助errno进一步加以区分。
struct 结构体:
           struct dirent {
               ino_t          d_ino;      inode编号
               off_t          d_off;       
               unsigned short  d_reclen;    文件名有效长度
               unsigned char   d_type;     类型(vim打开看到的类似@*/等)
               char          d_name[256];文件名
           };

实现ls不打印隐藏文件。每5个文件换一个行显示。【imp_ls2.c】

. 和 .. 是隐藏文件

rewinddir函数

回卷目录读写位置至起始。
void rewinddir(DIR *dirp);
返回值:无

telldir/seekdir函数

获取目录读写位置
long telldir(DIR *dirp); 
成功:与dirp相关的目录当前读写位置。失败-1,设置errno

修改目录读写位置
void seekdir(DIR *dirp, long loc); 
返回值:无
参数loc一般由telldir函数的返回值来决定。

4.递归遍历目录

查询指定目录,递归列出目录中文件,同时显示文件大小

ls -R
ls-R.c  --> 通过命令行在运行的时候直接传入要递归的目录名
1.判断命令行参数,获取用户要查询的目录。
    如果用户没有输入目录名(ls -R ==> ls -R .),进行转化, argc == 1 --> ./
2.判断用户指定的是否是目录。stat S_ISDIR()   --> 封装函数 isFile
3.读目录
    opendir(dir)
    while(reddir(dir)){
        普通文件:直接打印
        目录:
            拼接目录访问绝对路径。sprintf(path,"%s/%s",dir,d_name)
                # 递归调用自己传入的路径应当是绝对路径比较稳妥
            递归调用自己。 -->  opendir(path)
         readdir closedir                                     
    }
#include<unistd.h>
#include<sys/stat.h>
#include<dirent.h>
#include<stdio.h>
#include<stdblib.h>
#include<string.h>
#define PATH_LEN 256

void fetchdir(const char *dir, void (*fcn)(char *)) //该函数被调用则表示输入的是目录
{
    char name[PATH_LEN];
    struct dirent *sdp;
    DIR *dp;
    if((dp = opendir(dir)) == NULL){
        fprintf(stderr, "fetchdir:can't open %s\n",dir);
        return;    
    }
    while((sdp = readdir(dp)) != NULL){
        if(strcmp(sdp->name,".") == 0 || strcmp(sdp->name,"..") == 0){  // 防止出现死循环
            continue;
        }
        if(strlen(dir) + strlen(sdp->d_name) + 2 > sizeof(name)){
            //要求文件名+路径长度不大于256 
            fprintf(stderr, "fetchdir: name %s %s too long\n",dir, sdp_d_name);                   
        }else{
            sprintf(name, "%s/%s",dir,sdp->d_name);  //进行拼接成绝对路径
            (*fcn)(name); //回调函数调用进入下一论判断        
        }    
    }
}

void ifFile(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("%10ld  %s\n", sbuf.st_size,name);  //普通文件,直接打印文件名
}

int main(int argc, int *agrv[])
{
    if(argc == 1)
        isFile(".");
    else
        while(--argc > 0)   //可一次查询多个目录
            isFile(*++argv);  //循环调用该函数处理各个命令传入的目录
    return 0;
}

5.重定向

dup 和 dup2函数
int dup(int oldfd); 
成功:返回一个新文件描述符;失败:-1设置errno为相应值

int dup2(int fd1, int fd2); 
# 将旧的描述符拷贝到新的,指向同一块空间,成功则返回一个新的描述符fd2
o  如果 fd2已经是被打开的文件描述符且不等于fd,则先将其关闭,然后再打开(注意关闭再打开是一个原子操作)
o  如果 fd2等于fd,则直接返回fd2(也等于fd),而不作任何操作

cat test.c > new.c
# 将test.c内容重定向输入到new.c文件中
--->  dup2(fd,STDOUT_FILENO)

dup:

dup2:

fcntl实现dup:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值