系统 IO

"裸奔"层次:不带操作系统的编程

APP(应用程序)

--------------------------------

Hardware(硬件)

特点:简单,应用程序直接操作硬件(寄存器)

缺点:

1. 搞应用开发的必须要了解硬件的实现细节,能够看懂原理图

2. 无并发,不能同时运行多个程序,"单任务"

"带OS"的编程

APP(应用程序)

---------------------------------------------------

OS(操作系统):通过驱动控制硬件

---------------------------------------------------

Hardware(硬件)

特点:

1. 应用开发可以把精力放到应用的业务逻辑上,而不需要关心硬件的具体实现细节

2. 提供并发功能,允许同时运行多个应用,"多任务"

OS Operating System
操作系统是管理和分配系统软硬件资源的软件系统

linux操作系统下进行应用开发,就是调用操作系统的API函数,去操作具体的硬件,或者说是使用linux提供的服务

如:

open

close

read

write

......

linux是一个free(开源)的操作系统

**设计理念:**Everything is a file in Linux / Unix(一切皆文件)

在linux下面,操作任何东西,其实都是在操作文件,或者说,在linux下面,操作任何东西,都是通过文件的接口去操作的

在linux下面,一切都是文件(鼠标,键盘,触摸屏......)

1. 文件IO是什么?

文件IO分为系统IO和标准IO

IO:input output****对文件的输入和输出操作的基本函数接口

Linux有一个设计思想:Everything is a file in Linux (一切皆文件)

文件系统:是用来存储,组织和管理文件的一套方法和规则(NTFS,fat32,exfat......)

存储文件一般分为两个部分: 文件的属性:inode唯一标识一个文件的存在与否(文件名,文件类型,文件大小...)
文件本身的内容(用户数据)

如果一个文件存在,可以没有内容,但是必须有属性

Linux中到底如何组织和存放文件的呢?

大概步骤:

硬件:

inode (属性)------>文件的内容

linux内核中:
struct inode{} :如果没有创建这个结构体,则说明系统不识别这个硬件
用来描述一个文件的物理inode信息,系统识别到一个文件的存在,就会为它创建一个struct inode的结构体,一个文件只会唯一的对应一个struct inode

如果打开了某一个文件
使用struct file的结构体表示这个打开的文件

struct file{}用来描述一个已经打开的文件

文件状态标记 (如:O_RDONLY,O_WRONLY......)

文件的偏移量 / offset (类似"光标")

struct inode *

每一个打开的文件都会对应一个struct file

一个文件可以同时被多个进程打开,一个进程也可以同时打开多个文件
一个进程同时打开了多个文件,意味着需要保存每一个打开的文件的struct file
使用一个数组保存了所有struct file结构体的地址 (结构体指针数组)

linux为了屏蔽文件操作的具体细节,会为每一个进程创建一个" 进程文件表项 ": 保存每一个进程打开的文件

struct file * 数组

0 struct file * --->struct file ---> struct inode......

1 struct file * --->struct file ---> struct inode......

2 struct file * --->struct file ---> struct inode......

3 struct file * --->struct file ---> struct inode......

4 ......

5

......

linux提供操作文件的函数接口:

fd = open()

打开一个指定的文件,返回"进程文件表项的下标"

int

"文件描述符":在linux应用中,用来描述一个已经打开的文件,每一个打开的文件都有一个唯一的id,后续操作这个文件,都是通过该文件描述符去操作的

read(fd...)

write(fd...)

close(fd...)

......

对于用户来说,我们操作文件的时候只需要知道数组的下标,就可以去操作这个文件,这个下标在用户的眼中叫做 文件描述符

操作文件的内部流程:
数字 (文件描述符)
------->
进程文件表项的内容 (结构体指针数组)
------->
struct file
------->
struct inde
------->
硬件上面的inode (物理inode)
------->
文件本身的内容
为了方便,linux把上面所有的流程都封装起来了,用户不需要知道具体的操作细节
只需要调用OS提供给我们的API函数接口就可以了

Linux系统提供的这些用于操作文件的接口函数(如:open/read/write...) 我们称之为"系统IO"
系统IO:操作系统提供给用户操作文件的接口!!!

二. Linux中一些具体的API函数接口(系统接口)

对文件的操作步骤:

1. 打开文件 open

2. 对文件的操作(读,写....)

3. 关闭文件 close
注意:

1. 对文件的操作接口尽量不要放到共享文件夹,因为共享文件夹是windows的文件系统(有可能不兼容)
2. 系统IO提供的API函数接口有很多,只是学习了其中一小部分(注重方法和基础的API)

(1) 打开文件 (open)

cpp 复制代码
NAME
    open, openat, creat - open and possibly create a file
    打开或者创建(创建并且打开)一个文件
SYNOPSIS
     #include <sys/types.h>
     #include <sys/stat.h>
     #include <fcntl.h>
cpp 复制代码
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

pathname:要打开或者创建的文件名,带路径(如果不写路径,默认就是程序的当前路径)
      如: "/home/china/1.txt"   or  "1.txt"

flags:打开文件的标记(使用位域实现,可以添加多个标记)
        O_RDWR      read and write      读写打开
        O_RDONLY    read only           只读打开
        O_WRONLY    write only          只写打开
        以上的三个标记只能选一个(文件的打开方式)

        O_APPEND
             追加标记,打开文件的时候,文件的偏移量(光标)在文件的末尾
             默认情况下文件的偏移量在文件开头
             偏移量可以看做是文件的光标(读和写的位置)

        O_CREAT
             创建标记,如果文件不存在,则创建这个文件

        O_EXCL  
             和O_CREAT配合使用,用来测试文件是否存在
             同时指定这两个标记,文件存在则会报错,并且errno被设置为EEXIST,
             文件不存在则创建
                
        O_TRUNC 
             truncate截短,在文件打开的时候,把文件的内容清空

        O_NONBLOCK
             以非阻塞的方式打开文件,文件的默认打开方式是阻塞打开的
             阻塞:等待
                 如果文件暂时没有内容可读,read这个文件就会等待(直到有数据可读或出错)
                 如果文件暂时没有空间,write这个文件就会等待(直到有空间或出错)
             非阻塞:不等待
                 如果文件暂时没有内容可读,read这个文件就不会等待,直接返回一个错误
                 如果文件暂时没有空间,write这个文件不会等待,直接返回一个错误
        ....

        多个标记可以使用  |  连接
            (Linux中的标志大部分都是使用位域实现的,一个整数的某些bit有特殊的含义)

        例如:O_RDWR | O_CREAT | O_TRUNC 
              以读写的方式打开,文件不存在则创建,文件存在则清空内容

mode:指定文件的权限,当第二个参数中带有"O_CREAT"时,必须指定创建的文件的权限,有两种指定方式:
      1.使用OS定义的宏标识权限
        S_IRWXU  00700 user (file owner) has read, write, and execute permission
            用户拥有可读可写可执行的权限
        S_IRUSR  00400 user has read permission
        S_IWUSR  00200 user has write permission
        S_IXUSR  00100 user has execute permission
        S_IRWXG  00070 group has read, write, and execute permission
        S_IRGRP  00040 group has read permission
        S_IWGRP  00020 group has write permission
        S_IXGRP  00010 group has execute permission
        S_IRWXO  00007 others have read, write, and execute permission
        S_IROTH  00004 others have read permission
        S_IWOTH  00002 others have write permission
        S_IXOTH  00001 others have execute permission

        U/USR   用户(文件的拥有者)
        G/GRP   组用户
        O/OTH   其他用户

        S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH 
        rw-r--r--
        0644

      2.直接使用八进制数字标识权限
           0777
             111 111 111
             rwx rwx rwx 
           0664
             110 110 100
             rw- rw- r--

返回值:
      打开成功
         返回打开的文件的文件描述符(进程文件表项的下标),是一个整数
         >2  &&  int  && 未使用中的最小值
         因为操作系统会为每一个进程打开三个文件
             标准输入文件(键盘)  文件描述符  STDIN_FILENO     0 --->有缓冲区
             标准输出文件(终端)  文件描述符  STDOUT_FILENO    1 --->有缓冲区
             标准出错文件(终端)  文件描述符  STDERR_FILENO    2 --->没有缓冲区
         后序操作这个文件的时候,就可以直接使用这个数字表示

      打开失败
          返回-1,同时errno被设置  
----------------------------------------------------------------------------
man errno
#include <errno.h>
errno是一个全局变量,表示的是最后一次调用系统函数出错的错误码
vim /usr/include/errno.h
----------------------------------------------------------------------------
NAME
    perror - print a system error message
    打印系统的错误信息
SYNOPSIS
    #include <stdio.h>   
      
void perror(const char *s); 
perror可以把当前的错误码转化为对应的字符串并且打印出来
perror("用户提示性字符串");
============>
用户提示性字符串:errno转化之后的错误字符串\n   --->自动换行
cpp 复制代码
int creat(const char *pathname, mode_t mode);
    创建并且打开一个文件
int openat(int dirfd, const char *pathname, int flags);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);
// dirfd:目录的文件描述符

open("/home/china/1.txt", O_RDWR);
================>
int dirfd = open("/home/china", I_RDONLY); // 以读的方式打开一个目录
openat(dirfd, "1.txt", O_RDWR);

2. 关闭文件 (close)

cpp 复制代码
NAME
    close - close a file descriptor
        close是用来关闭fd指定的文件
SYNOPSIS
    #include <unistd.h>

int close(int fd);

fd:文件描述符

返回值:
    成功返回0
    失败返回-1,同时errno被设置

✔3. 读写文件 (read / write)

write:把数据放到文件里面去 (修改文件的内容)

cpp 复制代码
NAME
    write - write to a file descriptor
SYNOPSIS
    #include <unistd.h>

write的作用是把buf指针指向的内存的前面count个字节写入到fd表示的文件中去,
替换光标所在的内容

ssize_t write(int fd, const void *buf, size_t count);

fd:你要把内容写入到哪一个文件中去(open的返回值)
buf:指针,指向一段内存地址,存储了你要写入的数据
     为什么要使用const呢?
         从语义来说,在write函数的内部不应该通过buf去修改数据,为了增强程序的健壮性
count:字节数量,表示你要写入多少个字节

返回值:
    >0  返回实际写入到文件中的字节数量
            ps: 一般情况下,write的返回值通常等于请求写的字节数count
    =0  表示什么也没写入
    -1  表示写入失败,同时errno被设置

写入的位置位于文件的光标位置

ssize_t w = write(1, "hello,nihao", 10); // 往标准输出写入数据
// 标准输出(终端)中输出hell0,niha
// w = 10;

???

cpp 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    int w = write(1, "hello", 10);
    printf("%d\n", w);

    return 0;
}

hello%d
10
cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd = open("1.txt", O_RDWR | O_CREAT, 0777);

	char buf[100] = "hello world";
	ssize_t w = write(fd, buf, 50);
	printf("w = %ld\n", w);

	close(fd);

    return 0;
}

// 50

vim 1.txt

read:从文件中把数据拿出来

cpp 复制代码
NAME
    read - read from a file descriptor
SYNOPSIS
    #include <unistd.h>

read是从指定的文件中(fd)读取count个字节,存放到buf指向的内存空间中去
读取的位置位于文件的光标位置

ssize_t read(int fd, void *buf, size_t count); 
       
fd:文件描述符,你要从哪一个文件中读取内容(open的返回值)
buf:void*--->通用指针
    指针,指向一段可用的内存空间,表示你要把读取到的数据存放到哪一个位置,
    不能是野指针,也不能是空指针
count:字节数量,表示你要读取多少个字节
                
返回值:
    >0  返回实际读取到的数据数量(有可能小于count)
    =0  表示什么也没读到
    -1  表示读取失败,同时errno被设置

// 从标准输入中读取数据
char buf[100] = {0};
ssize_t r = read(0, buf, 100);
// buf[r - 1] = 0;
printf("r = %ld\n", r); // 回车也会读进去
printf("buf:%s\n", buf);

注意:
文件的偏移量 ("光标位置") 由内核自动维护,一般来说,打开文件的时候,offset=0
每一次成功的读和写都会让偏移量改变
你读/写了count个字节
offset += count
在读写文件的时候,要注意文件的光标所在位置

关标位置不是在开头吗???

不一定,多线程

4. 定位文件的光标 (偏移量 lseek)

cpp 复制代码
NAME
    lseek - reposition read/write file offset
SYNOPSIS
    #include <sys/types.h>
    #include <unistd.h>

定位fd表示的文件的偏移量
off_t lseek(int fd, off_t offset, int whence);

fd:你要定位的文件的文件描述符
offset:偏移量,具体的新位置需要结合第三个参数使用
whence:定义标记,有三种
       SEEK_SET        基于文件开头定位
           新位置 = 文件开头 + offset(>=0)
       SEEK_CUR        基于文件当前位置定位
           新位置 = 文件当前光标位置 + offset(可正可负)
       SEEK_END        基于文件结尾定位
           新位置 = 文件结尾 + offset(可正可负)
           负:往前面偏移
           正:往后面偏移 "留空洞"
    如:
        定位到文件开头
            lseek(fd, 0, SEEK_SET);
        定位到文件结尾
            lseek(fd, 0, SEEK_END);

返回值:
    成功返回新光标位置离文件开头的字节数量
    失败返回-1,同时errno被设置

也可以利用lseek计算文件大小
size = lseek(fd, 0, SEEK_END);

练习:利用文件IO的函数,写一个程序,实现两个普通文件的复制功能

./my_cp 1.txt 2.txt

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("USE:./mycp 1.txt 2.txt\n");
        return -1;
    }

    // 打开两个文件
    int fd1 = open(argv[1], O_RDONLY);
    if (-1 == fd1) {
        perror("open fd1 failed");
        return -1;
    }
    int fd2 = open(argv[2], O_RDWR | O_TRUNC | O_CREAT, 0777);
    if (-1 == fd2) {
        perror("open fd2 failed");
        close(fd1);
        return -1;
    }

#if 0   
    // bug:文件有可能很大,malloc不能开那么大的空间
    off_t size = lseek(fd1, 0, SEEK_END);
    printf("size = %ld\n", size);
    lseek(fd1, 0, SEEK_SET);

    char *buf = malloc(sizeof(char) * size);
    memset(buf, 0, size);

    read(fd1, buf, size);
    write(fd2, buf, size);
    free(buf);
#endif

    // 不断的读取1.txt的内容,把读取到的内容写入到2.txt
    char buf[50] = {0};
    while (1) {
        ssize_t r = read(fd1, buf, 50);
        if (0 == r) {
            // 读取完毕
            break;
        } else if (-1 == r) {
            perror("read 1.txt failed");
            break;
        }
        ssize_t w = write(fd2, buf, r);
        if (-1 == w) {
            perror("write 2.txt failed");
            break;
        }
    }
    // 关闭两个文件
    close(fd1);
    close(fd2);

    return 0;
}

总结:

1. 理解"裸奔层次"和"linux系统层次"的区别

2. linux文件操作的大致流程

3. 文件描述符是什么?

文件描述符是linux系统提供给应用,为了唯一标识一个已经打开的文件,是一个>=0的整数

4. 系统IO是什么?

linux系统提供给应用程序用来对文件input / output 操作的函数接口

5. 函数的用法不一定要记得,用的时候问man

5. 设置文件的掩码 (umask)

umask 表示创建文件时权限的掩码

创建文件时,不能指定umask中值为1的bit

**umask ------>0002 ===> 000 000 010
在创建文件的时候,不能指定umask中值为1的bit(指定了也会忽略)
mode = mode & (~umask)
umask: 0002 000 000 010
mode: 0777 111 111 111
&
~umask: 0002 111 111 101

111 111 101**

默认情况下,组用户和其他用户的写权限是不能指定的 (0022)

可以通过命令或者函数修改:

这种方式并不能永久改变 umask 值,只是改变了当前会话的 umask 值,打开一个新的 terminal 输入 umask 命令,可以看到 umask 值仍是默认的 002。要想永久改变 umask 值,则可以修改文件 /etc/bashrc,在文件中添加一行 umask 022

命令:
umask + 新的掩码
函数:

cpp 复制代码
NAME
    umask - set file mode creation mask
SYNOPSIS
    #include <sys/types.h>
    #include <sys/stat.h>

mode_t umask(mode_t mask);
  
mask:你要指定的新的文件掩码

返回值:
    返回上一次的文件掩码(设置之前的文件掩码)
    mode_t:32位的无符号整数类型

6. 获取和修改程序的当前工作路径

在linux中,任意一个程序都有一个工作路径
工作路径:
在哪一个目录里面运行这个程序,这个程序的工作路径就在哪里 (不管你的程序存储在哪一个位置)
如:
你有 /home/china/123/abc/a.out
当前在: /home/china/123/abc 运行:a.out
运行命令: ./a.out
工作路径: /home/china/123/abc

当前在: /home/china 运行:a.out
运行命令: ./123/abc/a.out
工作路径: /home/china

有什么意义呢?
你如果在a.out中写了
int fd = open("1.txt", O_RDWR | O_CREAT, 0664);
这里的 "1.txt" 是相对路径,此相对路径是相对于工作路径而言的
如果你的工作路径是: /home/china/123/abc
就会去/home/china/123/abc找1.txt

如果你的工作路径是: /home/china
就会去/home/china找1.txt

如何获取当前工作路径:

cpp 复制代码
NAME    
    getcwd, getwd, get_current_dir_name - get current working directory
SYNOPSIS
    #include <unistd.h>

把获取到的工作路径(绝对路径)保存到buf指向的内存空间(必须可用)
char* getwd(char *buf);

buf:是用来保存获取到的工作路径的

返回值:
    成功返回获取到的工作路径字符串的首地址(buf)
    失败返回NULL,同时errno被设置

警告: the `getwd' function is dangerous and should not be used.
    getwd有一个bug,不应该被使用,可能会造成内存越界
    如果buf指向的空间不够大(当前目录字符串长度超出了buf指向的空间)
    getwd就会访问buf后面的空间(造成数据会误修改)
---------------------------------------------------------------------------
getcwd是getwd的升级版本
char* getcwd(char *buf, size_t size);

buf:是用来保存获取到的工作路径的
size:指定了buf可用空间的大小,如果当前的目录长度字符串超过了size-1,这个函数就会报错

返回值:
    成功返回获取到的工作路径字符串的首地址(buf)
    失败返回NULL,同时errno被设置    
---------------------------------------------------------------------------
get_current_dir_name也是获取当前的工作路径,只不过这个函数不需要你给定空间,
会在函数内部malloc自动分配足够长的空间,保存获取到的工作路径,并且返回首地址
所以为了防止内存泄漏,使用者使用完毕之后,应该free这个空间
char* get_current_dir_name(void);

返回值:
    成功返回获取到的工作路径字符串的首地址
    失败返回NULL,同时errno被设置 

修改: 改变进程当前的工作路径

cpp 复制代码
NAME
    chdir, fchdir - change working directory
SYNOPSIS
    #include <unistd.h>

int chdir(const char *path);

path:要切换到的工作路径的目录字符串
chdir("/home/china/");
-----------------------------------------------------
int fchdir(int fd);
    
fd:要切换到的工作目录的文件描述符

如:
int fd = open("/home/china/", O_RDONLY) ;
fchdir(fd);

返回值:
    成功会改变当前的工作路径,返回0
    失败返回-1,同时errno被设置

7. 文件截短 (truncate)

cpp 复制代码
NAME
    truncate, ftruncate - truncate a file to a specified length
    截短一个文件到指定的长度
SYNOPSIS
    #include <unistd.h>
    #include <sys/types.h>

int truncate(const char *path, off_t length);
    文件必须可写

path:你要截短的文件的路径名(相对路径/绝对路径)
      绝对路径------>工作路径+path

length:截短之后的文件长度
        length < 原来的长度
            "截短":文件变成指定的长度,文件的大小改变
        length > 原来的长度 
            "留空洞",空洞的内容是无知的

返回值:
    成功返回0
    失败返回-1,同时errno被设置

int ftruncate(int fd, off_t length);
=====>
int fd = open(...); // 文件必须以可写的方式打开
ftruncate(fd, length);

8. 删除文件

cpp 复制代码
rm 删除文件 
rmdir 删除空目录

unlink // 删除一个普通文件
rmdir // 删除一个空目录
remove // 删除一个普通文件或者一个空目录

NAME
    unlink, unlinkat - delete a name and possibly the file it refers to
SYNOPSIS
    #include <unistd.h>

int unlink(const char *pathname);
// 删除一个文件的时候仅仅只是标记inode没有被使用了

pathname:要删除的那个文件的文件名(带路径)

返回值:
    成功删除返回0
    失败返回-1,同时errno被设置

我们知道Linux中文件是用inode节点来区分文件的,当我们删除一个文件的时候并不一定
系统就会释放inode节点的内容。当满足下面的要求的时候系统才会释放inode节点的内容
    (1) inode中记录指向该节点的硬链接数为0
    (2) 没有进程打开指向该节点的文件

执行unlink()函数并不一定会真正的删除文件,如果要删除的文件是硬链接文件,它先会检查
文件系统中此文件的硬链接数是否为1,如果不是1说明此文件还有其他硬链接对象,删除一个
文件的时候仅仅只是标记inode没有被使用了,只对此文件的硬链接数进行减1操作。若硬链接
数为1,并且在此时没有任何进程打开该文件,此内容才会真正地被删除掉。在有进程打开
此文件的情况下,则暂时不会删除,直到所有打开该文件的进程都结束时文件就会被删除
如果要删除的文件是符号链接(软链接)文件,则此链接会被删除
--------------------------------------------------------------------------------------
NAME
    rmdir - delete a directory
SYNOPSIS
    #include <unistd.h>

int rmdir(const char *pathname);
目录必须是空的

pathname:要删除的目录的路径名

返回值:
    成功删除返回0
    失败返回-1,同时errno被设置
------------------------------------
NAME
    remove - remove a file or directory
SYNOPSIS
    #include <stdio.h>

int remove(const char *pathname);
删除一个普通文件或者一个空目录

remove  删除一个普通文件 ------>unlink
remove  删除一个空目录   ------>rmdir

9. 获取文件的属性 (stat)

任何一个文件都有自己的属性 (inode中的内容)
man -a inode

cpp 复制代码
NAME
    stat, fstat, lstat, fstatat - get file status
        获取文件属性
SYNOPSIS
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>

stat是用来获取pathname指定的文件的属性信息,获取到的属性信息保存到statbuf指针指向
的内存中(statbuf必须指向一个可用的空间)
stat这个函数获取文件的属性信息,只需要提供文件名不需要打开它
int stat(const char *pathname, struct stat *statbuf);

pathname:你要获取哪一个文件的属性(路径名)
statbuf:结构体指针,指向一块可用的空间,用来保存获取到的文件的属性信息

返回值:
    成功返回0,失败返回-1,同时errno被设置

struct stat *statbuf = NULL;
int r = stat("1.txt", statbuf); // ERROR 指针必须指向一块可用的空间
==============>
struct stat statbuf;  
// struct stat *p = malloc(sizeof(*p));
int r = stat("1.txt", &statbuf);
---------------------------------------------------------------------------
fstat功能和stat类似,只不过需要提供文件描述符,需要提前打开文件
int fstat(int fd, struct stat *statbuf);
---------------------------------------------------------------------------
lstat功能和stat类似,只不过当pathname是一个符号链接的时候,
lstat获取的是符号链接本身的属性信息(软连接同样有自己的inode),
而stat是获取符号链接指向的那个文件的属性信息
int lstat(const char *pathname, struct stat *statbuf);

文件B是文件A的符号链接(ln -s A B):给A创建一个软连接B
    B----->A 
    stat(B)  获取的是A的inode的信息
    lstat(B)  获取的是B的inode的信息

文件的属性:

实际上Linux系统是使用一个 struct stat 的结构体保存文件的所有属性信息

cpp 复制代码
struct stat {
    dev_t     st_dev;         /* ID of device containing file */
        // 容纳该文件的设备的设备号码,文件存储在哪一个设备上面
    ino_t     st_ino;         /* Inode number */
        // 该文件的inode号码
    mode_t    st_mode;        /* File type and mode */
        // 保存了文件的权限和类型,使用位域(bit)实现
    nlink_t   st_nlink;       /* Number of hard links */
        // 硬链接数量(有多少个文件名关联这个inode)
    uid_t     st_uid;         /* User ID of owner */
        // 文件的所有者ID(文件属于哪个用户)
    gid_t     st_gid;         /* Group ID of owner */
        // 文件的组ID(文件属于哪个用户组)
    dev_t     st_rdev;        /* Device ID (if special file) */
        // 如果文件是一个特殊的设备,设备号码
    off_t     st_size;        /* Total size, in bytes */
        // 文件的大小(字节数量)
        // 普通文件:文件内容的大小
        // 对应符号链接(软链接)的文件内容是什么呢?
        //    指向的那个文件的文件名字
        // 目录文件的内存是什么?
        //    是目录项,大小为4096
            
    blksize_t st_blksize;     /* Block size for filesystem I/O */
        块大小(与具体的硬件设备有关)
    blkcnt_t  st_blocks;      /* Number of 512B blocks allocated */
        该文件占用多少块(512字节为一块)
    
    /* Since Linux 2.6, the kernel supports nanosecond
       precision for the following timestamp fields.
       For the details before Linux 2.6, see NOTES. */

    struct timespec st_atim;  /* Time of last access */
         // 最后访问时间
    struct timespec st_mtim;  /* Time of last modification */
         // 最后修改时间(修改了用户数据---即文件内容)
    struct timespec st_ctim;  /* Time of last status change */
         // 最后修改时间 (修改了属性信息,inode中的内容)

    #define st_atime st_atim.tv_sec   /* Backward compatibility */
    #define st_mtime st_mtim.tv_sec
    #define st_ctime st_ctim.tv_sec
};

解析权限和类型的方式:

cpp 复制代码
mode_t st_mode;   /* File type and mode */
    // 保存了文件的权限和类型

    st_mode使用位域实现,可以使用以下的宏去解析这个变量
       如:
            // 获取文件的属性
            struct stat st;  
            int r = stat("1.txt", &st);
            st.st_mode就保存了1.txt的文件的权限和类型

            文件的类型
                S_IFMT     0170000   bit mask for the file type bit field

                S_IFSOCK   0140000   socket  套接字文件
                S_IFLNK    0120000   symbolic link  链接文件
                S_IFREG    0100000   regular file  普通文件
                S_IFBLK    0060000   block device  块设备文件
                S_IFDIR    0040000   directory  目录文件
                S_IFCHR    0020000   character device  字符设备文件
                S_IFIFO    0010000   FIFO  管道文件

                to test for a regular file (for example)
                if ((st.st_mode & S_IFMT) == S_IFREG) {
                    // 当前文件是一个普通文件
                }

                或者:

                printf("File type:                ");
                switch (sb.st_mode & S_IFMT) {
                    case S_IFBLK:  printf("block device\n");            break;
                    case S_IFCHR:  printf("character device\n");        break;
                    case S_IFDIR:  printf("directory\n");               break;
                    case S_IFIFO:  printf("FIFO/pipe\n");               break;
                    case S_IFLNK:  printf("symlink\n");                 break;
                    case S_IFREG:  printf("regular file\n");            break;
                    case S_IFSOCK: printf("socket\n");                  break;
                    default:       printf("unknown?\n");                break;
                }

                或者:

                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(pathname, &st);
                if (S_ISREG(st.st_mode)) {
                    // 当前文件是一个普通文件
                }    
-----------------------------------------------------------------------------
                文件的权限信息
                printf("Mode:  %lo (octal)\n", (unsigned long)st.st_mode);    

                if (st.st_mode & S_IRUSR) {
                    // 用户拥有可读的权限
                } else {
                    // 用户,不拥有可读的权限
                }

        S_IRWXU  00700 user (file owner) has read, write, and execute permission
            用户拥有可读可写可执行的权限
        S_IRUSR  00400 user has read permission、
        S_IWUSR  00200 user has write permission
        S_IXUSR  00100 user has execute permission
        S_IRWXG  00070 group has read, write, and execute permission
        S_IRGRP  00040 group has read permission
        S_IWGRP  00020 group has write permission
        S_IXGRP  00010 group has execute permission
        S_IRWXO  00007 others have read, write, and execute permission
        S_IROTH  00004 others have read permission
        S_IWOTH  00002 others have write permission
        S_IXOTH  00001 others have execute permission

        U/USR   用户(文件的拥有者)
        G/GRP   组用户
        O/OTH   其他用户

不管你使用的是哪一个函数(stat/fstat/lstat)都是获取到上面结构体的信息
cpp 复制代码
注意时间格式转化

struct timespec {
    time_t   tv_sec;        /* seconds */
    // 秒,记录的是从1970年1月1日到现在的秒数
    long     tv_nsec;       /* nanoseconds */  
    // 纳秒
};

1s == 1000 ms 
1ms == 1000 us 
1us == 1000 ns 

既然你给出的是一个秒数,如何把一个秒数转化为时间呢?
#include <time.h>

char* ctime(const time_t *timep);   // 把秒数转化为时间字符串
            
timep:你要转化的秒数
       可以把当前的秒数转化为一个表示时间的字符串
            
如:
    printf("%s\n", ctime(&st.st_atim.tv_sec));
    or 
    printf("%s\n", ctime(&st.st_atime));
----------------------------------------------------------------------------
struct tm* localtime(const time_t *timep);  
可以把一个当前的秒数转化为一个表示时间的结构体

struct tm {
    int tm_sec;    /* Seconds (0-60) */
    int tm_min;    /* Minutes (0-59) */
    int tm_hour;   /* Hours (0-23) */
    int tm_mday;   /* Day of the month (1-31) */
    int tm_mon;    /* Month (0-11) */  // 须+1
    int tm_year;   /* Year - 1900 */
    int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
    int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
    int tm_isdst;  /* Daylight saving time */
};
cpp 复制代码
如何获取系统时间:

NAME
    time - get time in seconds
SYNOPSIS
    #include <time.h>

time_t time(time_t *tloc);     
=============================
time_t tm = time(NULL);
or 
time_t tm;
time(&tm);
--------------------------------------------------------------------------
如何获取更加精准的时间:
NAME
    gettimeofday, settimeofday - get / set time
SYNOPSIS
    #include <sys/time.h>

// 微秒级别的时间
int gettimeofday(struct timeval *tv, struct timezone *tz);

struct timeval {
    time_t      tv_sec;     /* seconds */
    suseconds_t tv_usec;    /* microseconds */
};

// 纳秒级别的时间
int clock_gettime(clockid_t clk_id, struct timespec *tp);
                
struct timespec {
    time_t   tv_sec;        /* seconds */
    // 秒,记录的是从1970年1月1日到现在的秒数
    long     tv_nsec;       /* nanoseconds */  
    // 纳秒
};

10. 目录操作

目录在Linux中也是文件,我们能不能按照操作普通文件的方式去操作目录呢?
普通文件:
打开文件
读写文件
关闭文件
如果可以,那么目录的内容是什么呢?

在Linux中,目录也是文件,也可以使用open打开(O_RDONLY),不能写打开,只能以读的方式打开
也会返回一个文件描述符,但是我们使用read去读取内容的时候,会失败
read failed: Is a directory

=========>在linux下面,不能使用read去读一个目录
那么目录文件应该如何操作呢?

1)目录和普通文件的区别

在Linux中,任何一个文件只要存在,就有自己的inode编号
目录文件也有自己的inode,同样保存了文件的属性信息 (stat同样可以获取属性)
但是目录文件的内容和普通文件的内容有很大的差别
普通文件的内容就是用户记录的一些用户数据
目录文件的内容记录的文件和文件之间的组织关系,叫做"目录项"
可以理解为一个"二维表格",记录着当前目录下面的文件名和inode的对应关系

在创建一个空目录的时候,系统会自动的为目录预留一个"目录项数组"
把该目录下面的所有文件(第一层)都记录在这个"数组"中

2)目录操作的API函数

cpp 复制代码
a.打开目录(opendir)

NAME
    opendir, fdopendir - open a directory
SYNOPSIS
    #include <sys/types.h>
    #include <dirent.h>

DIR* opendir(const char *name);

DIR *dir = opendir("/home/china");
------------------------------------------------------------------
DIR* fdopendir(int fd);

int fd = open("/home/china", O_RDONLY);
DIR *dir = fdopendir(fd);

name/fd:你要打开的目录的路径名/文件描述符

返回值:
    成功就会返回一个DIR指针(指向当前目录项的指针)
    失败返回NULL,同时errno被设置

在Linux中,使用DIR结构体表示一个打开的目录,至于目录里面有什么,
不需要关心,只需要知道DIR类型的指针表示一个已经打开的目录就可以了,
后序操作这个目录的时候,使用这个指针表示这个目录即可
cpp 复制代码
 b.读取目录项(readdir)

通过读取目录项,就可以知道目录中有哪些文件了
NAME
    readdir - read a directory
SYNOPSIS
    #include <dirent.h>

readdir是用来从dirp指向的目录中,读取下一个"目录项"的指针,
一个目录中有多少个"目录项",就有多少个文件

每一次调用readdir,都会给你返回一个指向目录项的指针,
并且让指针指向下一个目录项(偏移量),直到返回NULL,表示读取完毕

struct dirent* readdir(DIR *dirp);
                    
dirp:指向你要读取目录项的目录(是opendir的返回值)
                    
In the glibc implementation, the dirent structure isdefined as follows:
struct dirent {
--->ino_t          d_ino;       /* Inode number */
    // 当前读取到的目录项的inode编号
    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 */
    // 该目录项指向的文件名字(读取到的是相对路径---目录下面的文件名) 
};

注意:
    该结构体的成员,只有d_ino,和d_name两个成员是所有文件系统都支持的,
如果你想要你的代码有更好的兼容性和可移植性,在代码中尽量只使用这两个成员

返回值:
    On  success, readdir() returns a pointer to a dirent structure.  
(This structure may be statically allocated; do  not attempt to free(3) it.)
    如果成功,readdir()返回一个指向struct dirent *的结构体指针
(此结构体是静态分配的;不要试图去释放它)
    If  the  end  of  the  directory stream is reached, NULL is returned 
and errno is not changed.   
    If  an  error  occurs, NULL  is  returned and errno is set appropriately. 
     成功返回一个指向目录项(struct dirent *)的指针,读完后会返回NULL,errno不会
被设置,失败返回NULL,并且errno被设置 
cpp 复制代码
c.关闭目录(closedir)

NAME
    closedir - close a directory
SYNOPSIS
    #include <sys/types.h>
    #include <dirent.h>

int closedir(DIR *dirp);

dirp:指向你要关闭的目录的DIR结构体

返回值:
    成功返回0
    失败返回-1,同时errno被设置
cpp 复制代码
创建目录

NAME
    mkdir,  mkdirat  -  create a directory
SYNOPSIS
       #include <sys/stat.h>
       #include <sys/types.h>

int mkdir(const char *pathname, mode_t mode);

相对路径:以工作路径作为参照点

返回值:
    成功返回0
    失败返回-1,同时errno被设置

如果目录已存在,nkdir会失败,返回-1,同时errno == EEXIST

打印指定目录下所有文件的inode和名字

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>

int main(int argc, char *argv[]) {
    // 打开目录
    DIR *dirp = opendir(argv[1]);
    if (dirp == NULL) {
        perror("opendir failed");
        return -1;
    }
    // 打印指定目录下所有文件的inode和名字
    struct dirent *dp = NULL;
    while (dp = readdir(dirp)) {
        printf("inode = %ld,name = %s\n", dp->d_ino, dp->d_name);
    }
    // 关闭目录
    closedir(dirp);

    return 0;
}

三. 练习

1. 写一个简单的程序,判断一个目录中有多少个子目录 (第一级)

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    // 打开目录
    DIR *dirp = opendir(argv[1]);
    if (dirp == NULL) {
        perror("opendir failed");
        return -1;
    }
    // 保存当前的工作路径
    char cur_path[256] = {0};
    getcwd(cur_path, 256);
    // 进入目标目录(argv[1]表示的目录)
    chdir(argv[1]);
    // 获取目标目录的绝对路径
    char abs_path[256] = {0};
    getcwd(abs_path,256);
    // 回到当前的工作路径
    chdir(cur_path);

    // 打印指定目录下所有文件的inode和名字
    int num = 0;
    struct dirent *dp = NULL;
    while (dp = readdir(dirp)) {
        struct stat st;
        // 读取到的仅仅是名字,把单纯的名字变成绝对路径名
        char name[512] = {0};
        sprintf(name, "%s/%s", abs_path, dp->d_name);
        // printf("%s\n", name);
        stat(name, &st);
        if (S_ISDIR(st.st_mode)) {
            num++;
        }
    }
    printf("%d\n", num);
    // 关闭目录
    closedir(dirp);

    return 0;
}

2. 利用上面的API函数(stat)写一个程序,能够实现类似于ls -l 的功能
./myls 1.txt
能够把1.txt的所有详细信息展示出来

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <time.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("USER:./myls  filename");
        return -1;
    }
    // 获取文件的属性(stat)
    struct stat st;
    int ret = stat(argv[1], &st);
    if (ret == -1) {
        perror("stat failed");
        return -1;
    }
    // 解析文件的属性
    char buf[512] = {0}; // 定义一个字符数组,保存结果
    int r = 0;
    //文件的类型
    if (S_ISREG(st.st_mode)) {
        // 当前文件是一个普通文件
        // printf("-"); // 按照格式输出数据到标准输出(终端)
        // 用法类似于printf,sprintf是把内容输出到指定的内存地址
        // 返回实际输出的字节数量
        r += sprintf(buf, "-");
    } else if (S_ISDIR(st.st_mode)) {
        r += sprintf(buf, "d");
    } else if (S_ISCHR(st.st_mode)) {
        r += sprintf(buf, "c");
    } else if (S_ISBLK(st.st_mode)) {
        r += sprintf(buf, "b");
    } else if (S_ISFIFO(st.st_mode)) {
        r += sprintf(buf, "p");
    } else if (S_ISLNK(st.st_mode)) {
        r += sprintf(buf, "l");
    } else if (S_ISSOCK(st.st_mode)) {
        r += sprintf(buf, "s");
    }

    // 文件的权限
    if (st.st_mode & S_IRUSR) {
        // 用户拥有可读的权限
        r += sprintf(buf + r, "r");
    } else {
        // 用户,不拥有可读的权限
        r += sprintf(buf + r, "-");
    }

    (st.st_mode & S_IWUSR) ? (r += sprintf(buf + r, "w")) : (r += sprintf(buf + r, "-"));
    (st.st_mode & S_IXUSR) ? (r += sprintf(buf + r, "x")) : (r += sprintf(buf + r, "-"));

    (st.st_mode & S_IRGRP) ? (r += sprintf(buf + r, "r")) : (r += sprintf(buf + r, "-"));
    (st.st_mode & S_IWGRP) ? (r += sprintf(buf + r, "w")) : (r += sprintf(buf + r, "-"));
    (st.st_mode & S_IXGRP) ? (r += sprintf(buf + r, "x")) : (r += sprintf(buf + r, "-"));

    (st.st_mode & S_IROTH) ? (r += sprintf(buf + r, "r")) : (r += sprintf(buf + r, "-"));
    (st.st_mode & S_IWOTH) ? (r += sprintf(buf + r, "w")) : (r += sprintf(buf + r, "-"));
    (st.st_mode & S_IXOTH) ? (r += sprintf(buf + r, "x")) : (r += sprintf(buf + r, "-"));
    // 硬链接数量
    r += sprintf(buf + r, " %ld", st.st_nlink);
    // 文件属主,文件组
    struct passwd *pw = getpwuid(st.st_uid);
    r += sprintf(buf + r, " %s", pw->pw_name);

    pw = getpwuid(st.st_gid);
    r += sprintf(buf + r, " %s", pw->pw_name);
    // 文件大小
    r += sprintf(buf + r, "%8ld", st.st_size);
    // 修改时间(mtime)
    struct tm *pt = localtime(&st.st_atim.tv_sec);
    r += sprintf(buf + r, "%3d月", pt->tm_mon + 1);
    r += sprintf(buf + r, "%4d日", pt->tm_mday);
    r += sprintf(buf + r," %d:%d ", pt->tm_hour, pt->tm_min);
    // 文件名
    r += sprintf(buf + r, " %s", argv[1]);

    printf("%s\n", buf);

    return 0;
}

3. 写一个代码,可以计算一个文件夹的大小
大小定义为:该目录下面所有文件,以及目录下面的目录里面的文件.....的大小之和

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <dirent.h>
#include <string.h>

// 获取目录中文件的总大小
int getDirSize(const char *pathname) {
    int size = 0;
    // 打开目录
    DIR *dirp = opendir(pathname);
    if (dirp == NULL) {
        perror("opendir failed");
        return -1;
    }
    // 读取目录项
    struct dirent *dp = NULL;
    while (dp = readdir(dirp)) {  
        // 读取到的仅仅是名字,把单纯的名字变成绝对路径名
        char name[512] = {0};
        sprintf(name, "%s/%s", pathname, dp->d_name);
        // 排除当前目录下面的.和..
        if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) {
            continue;
        }   
    
        // printf("name:%s\n", dp->d_name);

        // 判断读取到的是否为目录文件
        struct stat st;
        int ret = stat(name, &st);
        if (ret == -1) {
            perror("stat failed");
            return -1;
        }
        if (S_ISDIR(st.st_mode)) {
            // 当前文件是目录文件,需要递归的查找子目录
            size += getDirSize(name);

        } else if (S_ISREG(st.st_mode)) {
            // 当前读取的是普通文件,计算大小
            size += st.st_size;
        }
    }
    //关闭目录
    closedir(dirp);

    return size;

}


int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("USER:./a.out  directory");
        return -1;
    }
    // 先判断是文件还是目录
    struct stat st;
    int ret = stat(argv[1], &st);
    if (ret == -1) {
        perror("stat failed");
        return -1;
    }
    if (!S_ISDIR(st.st_mode)) {
        printf("not a directory!");
        return -1;
    }

    // 把argv[1]表示的目录变成一个绝对路径
    // 保存当前的工作路径
    char cur_path[256] = {0};
    getcwd(cur_path, 256);
    // 进入目标目录(argv[1]表示的目录)
    chdir(argv[1]);
    // 获取目标目录的绝对路径
    char abs_path[256] = {0};
    getcwd(abs_path, 256);
    // 回到当前的工作路径
    chdir(cur_path);

    // 获取目录中文件的总大小
    int size = getDirSize(abs_path);
    printf("size = %d\n", size);


    return 0;
}

4. 写一个程序,能够搜索指定文件夹下面所有以.mp3或者.bmp结尾的文件
并且把搜索到的所有文件的绝对路径保存到一个带管理者结点的双向链表中
返回链表的管理者结点的地址
./a.out /home/china

BothWithLinkedListWithHead.h

cpp 复制代码
#ifndef __BOTHWITHLINKEDLISTWITHHEAD_H__
#define __BOTHWITHLINKEDLISTWITHHEAD_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 数据结点
struct node {
    char name[512]; // 数据域 存储数据
    struct node *next; // 指针域 保存逻辑上的下一个(关系)
    struct node *prev; // 指针域 保存逻辑上的上一个(关系)
};

// 头结点的数据类型 保存链表的属性
struct list {
    struct node *first; // 指向第一个数据结点
    struct node *last; // 指向最后一个数据结点
    int NodeNum; // 记录链表的长度
    /*
        其他属性
    */
};

struct list* create_list();
void print_list(struct list *list);
struct list* destroy_list(struct list *list);
void insert_node(struct list *list, char *name);

#endif

BothWithLinkedListWithHead.c

cpp 复制代码
#include "BothWithLinkedListWithHead.h"

// 创建一个链表
struct list* create_list() {
    struct list *list = malloc(sizeof(*list));
    list->first = NULL;
    list->last = NULL;
    list->NodeNum = 0;
    return list;
}

// 遍历
void print_list(struct list *list) {
    // 异常处理
    if (list == NULL || list->NodeNum == 0) {
        return ;
    }
    struct node *p = list->first; // 遍历指针
    while (p) {
        printf("%s\n", p->name);
        p = p->next;
    }
}

/*
    destroy_list:销毁带头双链表
*/
struct list* destroy_list(struct list *list) {
    // 异常处理
    if (list == NULL) {
        return NULL;
    }
    struct node *pc = list->first; // 指向被删结点
    while (pc) // 头删
    {
        list->first = list->first->next;
        if (list->first) {
            list->first->prev = NULL;
        }
        pc->next = NULL;
        free(pc);
        pc = list->first;
    }
    // 删除头结点
    list->last = NULL;
    free(list);
    list == NULL;
    return list;
}

// 尾插
void insert_node(struct list *list, char *name) {
    if (list == NULL) {
        return ;
    }
    // 创建一个新节点
    struct node *pnew = (struct node*)malloc(sizeof(*pnew));
    strcpy(pnew->name, name);
    pnew->next = NULL;
    pnew->prev = NULL;
    // 插入
    if (list->first == NULL) { // 从无到有
        list->first = list->last = pnew;
    } else { // 尾插
        list->last->next = pnew;
        pnew->prev = list->last;
        list->last = pnew;
    }
    list->NodeNum++;
}

dir.h

cpp 复制代码
#ifndef __DIR_H__
#define __DIR_H__

#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include "BothWithLinkedListWithHead.h"

int isBMP(char *filename);
int isJPG(char *filename);
void find_dir_pic(const char *dirname, struct list *list);

#endif

dir.c

cpp 复制代码
#include "dir.h"

// 判断一个文件名是否为bmp图片
int isBMP(char *filename) {
    int len = strlen(filename);
    char s[8] = {0};
    // 把最后的4个字符复制出来
    strcpy(s, (filename + len - 4));
    int x = strcmp(".bmp", s);
    if (x == 0) {
        return 1;
    } else {
        return 0;
    }
}

// 判断一个文件名是否为jpg图片
int isBMP(char *filename) {
    int len = strlen(filename);
    char s[8] = {0};
    // 把最后的4个字符复制出来
    strcpy(s, (filename + len - 4));
    int x = strcmp(".jpg", s);
    if (x == 0) {
        return 1;
    } else {
        return 0;
    }
}


/*
    find_dir_pic:把目录下面的所有图片文件的绝对路径名加入到指定的链表
    dirname:你要查找的目录名称
    list:你要把找到的图片名字加入到的链表头节点指针
*/
void find_dir_pic(const char *dirname, struct list *list) {
    // 打开目录
    DIR *dirp = opendir(dirname);
    if (NULL == dirp) {
        perror("opendir failed");
        return ;
    }
    // 读取目录项
    struct dirent *dp = NULL;
    while (dp = readdir(dirp)) {
        // 排除当前目录下的./..
        if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) {
            continue;
        }

        // printf("name:%s\n",dp->d_name); // 获取的是单纯的名字
        // 把单纯的名字变成绝对路径名
        char name[512] = {0};
        sprintf(name, "%s/%s", dirname, dp->d_name);
        // printf("%s\n", name); 
        // 判断是否为目录
        struct stat st;
        int ret = stat(name, &st);
        if (ret == -1) {
            perror("stat failed");
        }  
        if (S_ISDIR(st.st_mode)) {   
            // 递归的查找子目录
            find_dir_pic(name, list);
        } else if (S_ISREG(st.st_mode)) {
            // 判断是否为图片文件
            if (isBMP(name)) {
                insert_node(list, dp->d_name);
            }
            if (isJPG(name)) {
                insert_node(list, dp->d_name);
            }
        }
    }
    // 关闭目录
    closedir(dirp);
    return;
}

main.c

cpp 复制代码
#include "BothWithLinkedListWithHead.h"
#include "dir.h"

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("USER:./a.out  directory");
        return -1;
    }

    // 先判断是文件还是目录
    struct stat st;
    int ret = stat(argv[1], &st);
    if (ret == -1) {
        perror("stat failed");
        return -1;
    }
    if (!S_ISDIR(st.st_mode)) {
        printf("not a directory!");
        return -1;
    }

    // 创建一个链表
    struct list *list = create_list();

    // 把argv[1]表示的目录变成一个绝对路径
    // 保存当前的工作路径
    char cur_path[256] = {0};
    getcwd(cur_path,256);
    // 进入目标目录(argv[1]表示的目录)
    chdir(argv[1]);
    // 获取目标目录的绝对路径
    char abs_path[256] = {0};
    getcwd(abs_path,256);
    // 回到当前的工作路径
    chdir(cur_path);

    // 获取目录中所有的图片名称
    find_dir_pic(abs_path, list);

    // 打印链表
    print_list(list);

    // 销毁链表
    destroy_list(list);

    return 0;
}

5. 复制目录,把 src目录里面的所有文件(包括子目录)全部复制到 dest目录中

cpp 复制代码
Sasuke 20:05:02
/*
    功能:复制目录,把 src目录里面的所有文件(包括子目录)全部复制到 dest目录中
    失败返回-1,成功返回0
*/
int copy_dir(const char *src,const char *dest)
{
    int r = mkdir(dest,0777);//创建目的目录,如果目录已经存在, mkdir会失败
                            //但是这种情况并不影响我们后续的操作,所有不需要结束函数
                            //如果是其他原因导致的创建失败,那就需要结束函数
    if(-1 == r && errno != EEXIST)
    {
        printf("创建%s目录失败:%s\n",dest,strerror(errno));
        return -1;
    }
    DIR * psrc = opendir(src);
    if(NULL == psrc)
    {
        printf("打开%s文件失败:%s\n",src,strerror(errno));
        return -1;
    }
    DIR * pdest = opendir(dest);
    if(NULL == pdest)
    {
        printf("打开%s文件失败:%s\n",dest,strerror(errno));
        closedir(psrc);
        return -1;
    }
    struct dirent * p;
    //char srcpathname[100]; //可能会越界,最好是动态分配
    //char destpathname[100]; 
    int srclen;
    int destlen;
    char * srcpathname;
    char * destpathname;
    struct stat st;//用来保存获取到的文件属性信息
    while(1)//一项一项读取目录
    {
        p = readdir(psrc);
        if(NULL == p)//返回NULL,代表目录读取完毕
            break;

        //printf("%s\n",p->d_name);//可以发现,包含两个特殊目录:  .  ..
        if(strcmp(p->d_name,".")==0 || strcmp(p->d_name,"..")==0)
            continue;

        //连接原目录及该目录中的文件,组成一个完整的路径名
        srclen = strlen(src) + 1 + strlen(p->d_name) + 1;
        srcpathname = (char *)malloc(srclen);
        strcpy(srcpathname,src);
        strcat(srcpathname,"/");
        strcat(srcpathname,p->d_name);
        printf("%s\n",srcpathname);
        r = stat(srcpathname,&st);
        if(-1 == r)
        {
            perror("");
            continue;
        }
        destlen = strlen(dest) + 1 + strlen(p->d_name) + 1;
        destpathname = (char *)malloc(destlen);
        strcpy(destpathname,dest);
        strcat(destpathname,"/");
        strcat(destpathname,p->d_name);
        printf("%s\n",destpathname);

        if((st.st_mode & S_IFMT) == S_IFREG)//srcpathname是普通文件,进行复制
        {
            r = copy_file(srcpathname,destpathname);
            if(-1 == r)//该文件复制失败
            {
                printf("%s文件复制失败\n",srcpathname);
            }
        }
        else if((st.st_mode & S_IFMT) == S_IFDIR)//是子目录
        {
            //递归调用自己
            copy_dir(srcpathname,destpathname);
        }

        free(srcpathname);
        free(destpathname);
    }

//原目录  /mnt/hgfs/mnt/hgfs/CS2415F/2阶段
    // /mnt/hgfs/mnt/hgfs/CS2415F/2阶段/1文件IO
//目录目录  /home/china/test
    //  /home/china/test/1文件IO
    closedir(psrc);
    closedir(pdest);
    return 0;
}

int main(int argc,char *argv[])
{
    if(argc != 3)
    {
        printf("输入有误,需要带两个参数,原目录路径和目的目录路径\n");
        return -1;
    }
        
    int r = copy_dir(argv[1],argv[2]);
    if(-1 == r)
    {
        printf("复制失败\n");
    }
    else
    {
        printf("复制成功\n");
    }

    return 0;
}

4. dup2

cpp 复制代码
#include <unistd.h>

int dup2(int oldfd, int newfd);

功能描述:
    dup2函数将oldfd所指定的文件描述符复制到newfd上,使得newfd也指向oldfd所引用的文件
    (或套接字、管道等)
    
    如果newfd已经打开,则dup2会先关闭它,然后再进行复制操作

    如果oldfd和newfd相等,则dup2会直接返回newfd,而不会关闭它

返回值
    成功时,dup2返回newfd
    失败时,返回-1,并设置相应的errno值以指示错误类型
cpp 复制代码
#include <stdio.h>  
#include <unistd.h>  
#include <fcntl.h>  
  
int main() {  
    int fd = open("output.txt", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);  
    if (fd == -1) {  
        perror("Failed to open file");  
        return 1;  
    }  
  
    // 将文件描述符fd复制到标准输出STDOUT_FILENO  
    if (dup2(fd, STDOUT_FILENO) == -1) {  
        perror("Failed to duplicate file descriptor");  
        close(fd); // 不要忘记关闭原始的文件描述符  
        return 1;  
    }  
  
    // 此时,STDOUT_FILENO已经指向output.txt,下面的printf输出会写入该文件  
    printf("Hello, dup2!\n");  
  
    // 关闭原始的文件描述符,因为STDOUT_FILENO已经指向了相同的文件  
    close(fd);  
  
    return 0;  
}

注意事项

  1. 文件描述符的有效性 :传递给dup2的两个文件描述符必须是有效的
  2. 关闭原始文件描述符 :虽然dup2不会关闭oldfd,但在很多情况下,复制完成后关闭oldfd是一个好习惯,特别是当你不再需要它时
  3. 错误处理 :检查dup2的返回值,并在失败时适当处理错误
  4. 原子操作dup2是一个原子操作,即它先关闭newfd(如果已打开),然后将oldfd复制到newfd,这两个步骤是作为一个整体来执行的,不会被中断
相关推荐
阿巴Jun17 分钟前
【数学】线性代数知识点总结
笔记·线性代数·矩阵
茯苓gao30 分钟前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾43 分钟前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
DKPT1 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习
ST.J2 小时前
前端笔记2025
前端·javascript·css·vue.js·笔记
Suckerbin2 小时前
LAMPSecurity: CTF5靶场渗透
笔记·安全·web安全·网络安全
小憩-2 小时前
【机器学习】吴恩达机器学习笔记
人工智能·笔记·机器学习
UQI-LIUWJ3 小时前
unsloth笔记:运行&微调 gemma
人工智能·笔记·深度学习
googleccsdn3 小时前
ESNP LAB 笔记:配置MPLS(Part4)
网络·笔记·网络协议
楚肽生物小敏3 小时前
Cy5-Tyramide, Cyanine 5 Tyramide;1431148-26-3
笔记