当前位置: 首页 > news >正文

系统 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)

NAMEopen, openat, creat - open and possibly create a file打开或者创建(创建并且打开)一个文件
SYNOPSIS#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>
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 permissionS_IWUSR  00200 user has write permissionS_IXUSR  00100 user has execute permissionS_IRWXG  00070 group has read, write, and execute permissionS_IRGRP  00040 group has read permissionS_IWGRP  00020 group has write permissionS_IXGRP  00010 group has execute permissionS_IRWXO  00007 others have read, write, and execute permissionS_IROTH  00004 others have read permissionS_IWOTH  00002 others have write permissionS_IXOTH  00001 others have execute permissionU/USR   用户(文件的拥有者)G/GRP   组用户O/OTH   其他用户S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH rw-r--r--06442.直接使用八进制数字标识权限0777111 111 111rwx rwx rwx 0664110 110 100rw- 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
----------------------------------------------------------------------------
NAMEperror - print a system error message打印系统的错误信息
SYNOPSIS#include <stdio.h>   void perror(const char *s); 
perror可以把当前的错误码转化为对应的字符串并且打印出来
perror("用户提示性字符串");
============>
用户提示性字符串:errno转化之后的错误字符串\n   --->自动换行
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)

NAMEclose - close a file descriptorclose是用来关闭fd指定的文件
SYNOPSIS#include <unistd.h>int close(int fd);fd:文件描述符返回值:成功返回0失败返回-1,同时errno被设置

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

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

NAMEwrite - 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;
???
#include <stdio.h>
#include <unistd.h>int main() {int w = write(1, "hello", 10);printf("%d\n", w);return 0;
}hello%d
10
#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:从文件中把数据拿出来

NAMEread - 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)

NAMElseek - 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

#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.txtchar 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 + 新的掩码
函数:
  

NAMEumask - 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 

如何获取当前工作路径:

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被设置 

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

NAMEchdir, 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)

NAMEtruncate, 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:你要截短的文件的路径名(相对路径/绝对路径)绝对路径------>工作路径+pathlength:截短之后的文件长度length < 原来的长度"截短":文件变成指定的长度,文件的大小改变length > 原来的长度 "留空洞",空洞的内容是无知的返回值:成功返回0失败返回-1,同时errno被设置int ftruncate(int fd, off_t length);
=====>
int fd = open(...); // 文件必须以可写的方式打开
ftruncate(fd, length);

 8. 删除文件

rm 删除文件 
rmdir 删除空目录unlink // 删除一个普通文件
rmdir // 删除一个空目录
remove // 删除一个普通文件或者一个空目录NAMEunlink, 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,并且在此时没有任何进程打开该文件,此内容才会真正地被删除掉。在有进程打开
此文件的情况下,则暂时不会删除,直到所有打开该文件的进程都结束时文件就会被删除
如果要删除的文件是符号链接(软链接)文件,则此链接会被删除
--------------------------------------------------------------------------------------
NAMErmdir - delete a directory
SYNOPSIS#include <unistd.h>int rmdir(const char *pathname);
目录必须是空的pathname:要删除的目录的路径名返回值:成功删除返回0失败返回-1,同时errno被设置
------------------------------------
NAMEremove - remove a file or directory
SYNOPSIS#include <stdio.h>int remove(const char *pathname);
删除一个普通文件或者一个空目录remove  删除一个普通文件 ------>unlink
remove  删除一个空目录   ------>rmdir

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

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

NAMEstat, 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创建一个软连接BB----->A stat(B)  获取的是A的inode的信息lstat(B)  获取的是B的inode的信息

文件的属性:

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

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 */// 文件的大小(字节数量)// 普通文件:文件内容的大小// 对应符号链接(软链接)的文件内容是什么呢?//    指向的那个文件的文件名字// 目录文件的内存是什么?//    是目录项,大小为4096blksize_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 nanosecondprecision 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
};

解析权限和类型的方式:

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 fieldS_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 permissionS_IXUSR  00100 user has execute permissionS_IRWXG  00070 group has read, write, and execute permissionS_IRGRP  00040 group has read permissionS_IWGRP  00020 group has write permissionS_IXGRP  00010 group has execute permissionS_IRWXO  00007 others have read, write, and execute permissionS_IROTH  00004 others have read permissionS_IWOTH  00002 others have write permissionS_IXOTH  00001 others have execute permissionU/USR   用户(文件的拥有者)G/GRP   组用户O/OTH   其他用户不管你使用的是哪一个函数(stat/fstat/lstat)都是获取到上面结构体的信息
注意时间格式转化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) */  // 须+1int 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 */
};
如何获取系统时间:NAMEtime - 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);
--------------------------------------------------------------------------
如何获取更加精准的时间:
NAMEgettimeofday, 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函数

a.打开目录(opendir)NAMEopendir, 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类型的指针表示一个已经打开的目录就可以了,
后序操作这个目录的时候,使用这个指针表示这个目录即可
 b.读取目录项(readdir)通过读取目录项,就可以知道目录中有哪些文件了
NAMEreaddir - 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被设置 
c.关闭目录(closedir)NAMEclosedir - close a directory
SYNOPSIS#include <sys/types.h>#include <dirent.h>int closedir(DIR *dirp);dirp:指向你要关闭的目录的DIR结构体返回值:成功返回0失败返回-1,同时errno被设置
创建目录NAMEmkdir,  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和名字

#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.  写一个简单的程序,判断一个目录中有多少个子目录 (第一级)

#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的所有详细信息展示出来

#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. 写一个代码,可以计算一个文件夹的大小
            大小定义为:该目录下面所有文件,以及目录下面的目录里面的文件.....的大小之和

#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

#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

#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

#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

#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

#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目录中

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文件IOclosedir(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

#include <unistd.h>int dup2(int oldfd, int newfd);功能描述:dup2函数将oldfd所指定的文件描述符复制到newfd上,使得newfd也指向oldfd所引用的文件(或套接字、管道等)如果newfd已经打开,则dup2会先关闭它,然后再进行复制操作如果oldfd和newfd相等,则dup2会直接返回newfd,而不会关闭它返回值成功时,dup2返回newfd失败时,返回-1,并设置相应的errno值以指示错误类型
#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,这两个步骤是作为一个整体来执行的,不会被中断

相关文章:

系统 IO

"裸奔"层次&#xff1a;不带操作系统的编程 APP(应用程序) -------------------------------- Hardware(硬件) 特点&#xff1a;简单&#xff0c;应用程序直接操作硬件(寄存器) 缺点&#xff1a; 1. 搞应用开发的必须要了解硬件的实现细节&#xff0c;能够看懂原理图…...

Mysql InnoDB 存储引擎简介

InnoDB 存储引擎是 Mysql 的默认存储引擎&#xff0c;它是由 Innobase Oy 公司开发的 Mysql 为什么默认使用 InnoDB 存储引擎 InnoDB 是一款兼顾高可靠性和高性能的通用存储引擎 在 Mysql 5.5 版本之前&#xff0c;默认是使用 MyISAM 存储引擎&#xff0c;在 5.5 及其之后版…...

驾校预约学习系统的设计与实现

摘 要 伴随着信息技术与互联网技术的不断发展&#xff0c;人们进到了一个新的信息化时代&#xff0c;传统管理技术性没法高效率、容易地管理信息内容。为了实现时代的发展必须&#xff0c;提升管理高效率&#xff0c;各种各样管理管理体系应时而生&#xff0c;各个领域陆续进到…...

Python--读取文件时出现的报错

在使用 Python 读取文件时&#xff0c;尤其是涉及到文件编码的场景&#xff0c;常常会遇到编码解码问题。常见的编码问题主要发生在尝试解码不同编码格式的文件时&#xff0c;比如将使用 GBK 编码的文件按 UTF-8 解码&#xff0c;或者相反。 常见编码错误及其原因&#xff1a;…...

基于http请求的一种安全校验认证方案记录

目录 需求简述 设计方案 参考代码 可优化点 需求简述 日常的开发对接过程中&#xff0c;经常会遇到需要给其他合作伙伴或者其他系统通过接口的方式提供数据&#xff0c;或者有些接口是需要提供通用能力出去的。 从安全的角度考虑&#xff0c;我们往往需要给接口加一些安全校…...

链动321模式开发系统解析源码

链动321模式是一种结合了区块链技术、动态激励机制与“321”运营模式的新型电商架构。该模式通过激励用户分享和推广&#xff0c;实现用户、企业和平台的共赢&#xff0c;具有独特的商业逻辑和高效的运营机制。以下是对链动321模式的详细解析&#xff1a; 系统特点 裂变迅速&am…...

TypeScript 快速上⼿ (3:装饰器)

目录 一、简介 二、类装饰器 基本语法 应用举例 关于返回值 关于构造类型 替换被装饰的类 三、装饰器工厂 四、装饰器组合 五、属性装饰器 基本语法 关于属性遮蔽 应用举例 六、方法装饰器 基本语法 应用举例 七、访问器装饰器 基本语法 应用举例 八、参数装…...

el-input设置后缀显示单位并阻止滚轮微调

项目中收集form表单信息时&#xff0c;有时会需要在el-input后面显示单位&#xff0c;效果如图&#xff1a; 当然&#xff0c;我们可以直接在输入框后面加上单位&#xff0c;但直接给输入框上加单位不管是视图上还是用户体验上看起来都要好一点 element-plus / element-ui给我…...

Redis Key的过期策略

Redis 的过期策略主要是指管理和删除那些设定了过期时间的键&#xff0c;以确保内存的有效使用和数据的及时清理。 具体来说&#xff0c;Redis 有三种主要的过期策略&#xff1a;定期删除&#xff08;Scheduled Deletion&#xff09;、惰性删除&#xff08;Lazy Deletion&#…...

数据结构:时间复杂度与空间复杂度

目录 算法效率时间复杂度大O渐进表示法时间复杂度计算案例 空间复杂度空间复杂度案例 复杂度算法题 算法效率 算法在编写成可执行程序后&#xff0c;运⾏时需要耗费时间资源和空间(内存)资源 。因此衡量⼀个算法的好坏&#xff0c;⼀般是从时间和空间两个维度来衡量的&#xf…...

C语言实现贪吃蛇小游戏

✅博客主页:爆打维c-CSDN博客​​​​​​ &#x1f43e; &#x1f539;分享c语言知识及代码 &#x1f43e; 目录 游戏展示视频 一、项目准备工作 二、功能实现分析 1.游戏开始 a.设置本地化、创建窗口、标题 b.隐藏光标,封装定位光标的函数 c.打印欢迎界面及提示信息 …...

深入解析包裹信息管理系统:关系型数据库逻辑数据模型设计、超类实体与派生属性探讨

目录 案例 【题目】 【问题 1】(14分) 【问题 2】(6分) 【问题 3】(5分) 【答案】 【问题 1】解析 【问题 2】解析 【问题 3】解析 案例 阅读下列说明&#xff0c;回答问题 1 至问题 3。 【题目】 某企业委托软件公司开发包裹信息管理系统&#xff0c;以便于对该企业…...

Cyber Weekly #24

赛博新闻 1、OpenAI发布最强模型o1 本周四&#xff08;9月12日&#xff09;&#xff0c;OpenAI宣布推出OpenAIo1系列模型&#xff0c;标志着AI推理能力的新高度。o1系列包括性能强大的o1以及经济高效的o1-mini&#xff0c;适用于不同复杂度的推理任务。新模型在科学、编码、数…...

Java多线程面试精讲:源于技术书籍的深度解读

写在前面 ⭐️在无数次的复习巩固中&#xff0c;我逐渐意识到一个问题&#xff1a;面对同样的面试题目&#xff0c;不同的资料来源往往给出了五花八门的解释&#xff0c;这不仅增加了学习的难度&#xff0c;还容易导致概念上的混淆。特别是当这些信息来自不同博主的文章或是视…...

【Elasticsearch系列七】索引 crud

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

快速生成服务器响应json-server的安装和使用

json-server介绍地址:https://www.geeksforgeeks.org/json-server-setup-and-introduction/ 1.json-server是什么? 基于自定义的json文件,快速生成服务端响应,可用于前端调试接口 2.安装和卸载json-server 2.1 安装: 使用npm命令: npm install -g json-server 2.2 卸载 npm …...

增强LinkedList实现瑞士轮赛制编排

前言 LinkedList底层虽然是基于链表实现&#xff0c;但是由于其对底层节点进行了封装&#xff0c;导致无法操作底层Node对象。这也为使用上带来了很多不便&#xff0c;比如我之前遇到的一个需求&#xff1a;将n个队伍按照瑞士轮进行编排&#xff0c;组成n/2个队伍&#xff0c;…...

C++编译环境(IDE)推荐及安装

IDE是什么 嗨嗨嗨&#xff0c;我又来水博文了 今天来给大家推荐几款好用的IDE IDE是集成开发环境&#xff08;Integrated Development Environment&#xff09;的缩写&#xff0c;是一种软件应用程序&#xff0c;提供了用于软件开发的各种工具和功能&#xff0c;包括代码编辑…...

Android 12系统源码_窗口管理(八)WindowConfiguration的作用

前言 在Android系统中WindowConfiguration这个类用于管理与窗口相关的设置&#xff0c;该类存储了当前窗口的显示区域、屏幕的旋转方向、窗口模式等参数&#xff0c;应用程序通过该类提供的信息可以更好的适配不同的屏幕布局和窗口环境&#xff0c;以提高用户体验。 一、类定…...

已读论文创新点合集

系列文章目录 文章目录 系列文章目录一、《LAMM: Label Alignment for Multi-Modal Prompt Learning》二、《MaPLe: Multi-modal Prompt Learning》三、《Learning to Prompt for Vision-Language Models》CoOp 一、《LAMM: Label Alignment for Multi-Modal Prompt Learning》…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

django filter 统计数量 按属性去重

在Django中&#xff0c;如果你想要根据某个属性对查询集进行去重并统计数量&#xff0c;你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求&#xff1a; 方法1&#xff1a;使用annotate()和Count 假设你有一个模型Item&#xff0c;并且你想…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...