系统 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,这两个步骤是作为一个整体来执行的,不会被中断

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/54429.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode_sql_day24(1212.查询球队积分)

描述 表: Teams ------------------------- | Column Name | Type | ------------------------- | team_id | int | | team_name | varchar | ------------------------- team_id 是该表具有唯一值的列。 表中的每一行都代表一支独立足球队。表: Matches…

【笔记】2.1 半导体三极管(BJT,Bipolar Junction Transistor)

一、结构和符号 1. 三极管结构 常用的三极管的结构有硅平面管和锗合金管两种类型。各有PNP型和NPN型两种结构。 左图是NPN型硅平面三极管,右图是PNP型锗合金三极管。 从图中可见平面型三极管是先在一块大的金属板上注入杂质使之变成N型,然后再在中间注入杂质使之变成P型,…

Note091203_Outlook邮件撤回操作

Note091203_Outlook邮件撤回操作 如图所示&#xff1a; step1: 打开outlook step2&#xff1a; 点击已发送邮件&#xff0c;选中目标撤回邮件双击打开&#xff1b; step3&#xff1a; 点击图中2框所示&#xff0c;可看见撤回操作&#xff1b; 以上

Linux操作系统 进程(3)

接上文 Linux进程优先级之后&#xff0c;我们了解到僵尸进程与孤儿进程的形成原因&#xff0c;既然是因为父进程没有接收子进程的退出状态导致的&#xff0c;那么我们该如何去获取子进程的退出状态呢&#xff1f;那本篇文章将围绕这个问题来解释进程。 环境 &#xff1a; vsco…

基于CNN的10种物体识别项目

一&#xff1a;数据导入和处理 1.导入相关包&#xff1a; import numpy as np import pandas as pd import matplotlib.pyplot as plt import tensorflow as tf2.下载数据 (x_train_all, y_train_all), (x_test, y_test) tf.keras.datasets.cifar10.load_data()# x_valid:测…

【Qt笔记】QTabWidget控件详解

目录 引言 一、基本功能 二、核心属性 2.1 标签页管理 2.2 标签位置 2.3 标签形状 2.4 标签可关闭性 2.5 标签可移动性 三、信号与槽 四、高级功能 4.1 动态添加和删除标签页 4.2 自定义标签页的关闭按钮行为 4.3 标签页的上下文菜单 五、样式设置 六、应用示例…

快速使用react 全局状态管理工具--redux

redux Redux 是 JavaScript 应用中管理应用状态的工具&#xff0c;特别适用于复杂的、需要共享状态的中大型应用。Redux 的核心思想是将应用的所有状态存储在一个单一的、不可变的状态树&#xff08;state tree&#xff09;中&#xff0c;状态只能通过触发特定的 action 来更新…

硬件基础知识

驱动开发分为&#xff1a;裸机驱动、linux驱动 嵌入式&#xff1a;以计算机技术为基础&#xff0c;软硬结合的、可移植、可剪裁的专用计算机 单片机最小单元&#xff1a;vcc gnd reset 晶振 cpu --- soc :system on chip 片上外设 所有的程序都是在soc&#xff08;cpu&…

【Android安全】Ubuntu 16.04安装GDB和GEF

1. 安装GDB sudo apt install gdb-multiarch 2. 安装GEF(GDB Enhanced Features) 官网地址&#xff1a;https://github.com/hugsy/gef 2.1 安装2021.10版本 但是在Ubuntu 16.04上&#xff0c;bash -c "$(curl -fsSL https://gef.blah.cat/sh)"等命令不好使&…

深度学习自编码器 - 收缩自编码器(CAE)篇

序言 在深度学习的浪潮中&#xff0c;收缩自编码器&#xff08; Compressive Autoencoder, CAE \text{Compressive Autoencoder, CAE} Compressive Autoencoder, CAE&#xff09;作为自编码器的一种高级形式&#xff0c;正逐步崭露头角。收缩自编码器在保留自编码器核心功能—…

【贪心算法】贪心算法一

贪心算法一 1.柠檬水找零2.将数组和减半的最少操作次数3.最大数4.摆动序列 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.柠檬水找零 题目…

【安当产品应用案例100集】017-助力软件服务商高效集成多因素认证

一、企业案例背景 在本案例中&#xff0c;某企业作为一家软件技术服务商&#xff0c;为包括银行、政府机构在内的多个行业提供定制化的软件服务。由于各个行业的安全需求各异&#xff0c;例如银行和政府机构倾向于使用UKEY进行身份验证&#xff0c;而其他企业则可能偏好使用数…

创建Django 项目

创建一个新的 Django 项目&#xff1a; django-admin startproject myproject cd myproject 在 Django 项目中创建一个新的应用&#xff1a; python manage.py startapp myapp设置数据库 编辑 myproject/settings.py 文件中的数据库设置&#xff1a; DATABASES {default:…

OJ在线评测系统 前端开发设计优化通用菜单组件二 调试用户自动登录

通用的菜单组件开发二 接下来要完善 权限功能 就是只有登录后才能进入题目查看界面 用户只能看到我们有权限的菜单 我们要在路由文件里面去操作 原理是控制路由设置隐藏 只要用户没有权限 就过滤掉隐藏 全局权限管理 实现想清楚有那些权限 /*** 权限定义*/ const ACCES…

2017年国赛高教杯数学建模A题CT系统参数标定及成像解题全过程文档及程序

2017年国赛高教杯数学建模 A题 CT系统参数标定及成像 CT(Computed Tomography)可以在不破坏样品的情况下&#xff0c;利用样品对射线能量的吸收特性对生物组织和工程材料的样品进行断层成像&#xff0c;由此获取样品内部的结构信息。一种典型的二维CT系统如图1所示&#xff0c…

瑞芯微RK3588开发板Linux系统添加自启动命令的方法,深圳触觉智能Arm嵌入式鸿蒙硬件方案商

本文适用于触觉智能所有Linux系统的开发板、主板添加自启动命令的方法&#xff0c;本次使用了触觉智能的EVB3588开发板演示&#xff0c;搭载了瑞芯微RK3588旗舰芯片。 该开发板为核心板加底板设计&#xff0c;为工业场景设计研发的模块化产品&#xff0c;10年以上稳定供货,帮助…

U盘显示未被格式化:深度解析与数据恢复指南

一、现象解析&#xff1a;U盘显示未被格式化之谜 在日常使用U盘的过程中&#xff0c;不少用户可能会遭遇一个令人头疼的问题——插入U盘后&#xff0c;系统提示“U盘未被格式化”&#xff0c;要求用户进行格式化操作以继续访问。这一突如其来的提示不仅打断了正常的工作流程&a…

Java 数据类型转换详解:隐式转换(自动转换)与强制转换(手动转换)

目录 前言 取值范围从小到大的关系&#xff1a; 隐式转换&#xff08;自动转换&#xff09; &#x1f4dc;示例 1&#xff1a;基本类型隐式转换 &#x1f4dc;示例 2&#xff1a;算术运算中的类型提升 &#x1f4dc;示例 3&#xff1a;byte、short 和 char 的自动转换 隐…

如何上传tauri项目到csdn gitcode

如何上传tauri项目到csdn gitcode 首先保证项目目录有.gitignore&#xff0c;避免不必要的文件上传分享。 gitignore文件 # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log*node_modules dist dist-ssr *.local# Editor …

【计算机基础题目】二叉树的前序中序后续遍历之间相互转换 详细例子

创作日志&#xff1a; 笔试题目&#xff0c;掌握了技巧之后这道题就是 so easy~ 一、 1、已知二叉树的 前序和中序&#xff0c;可以求出后序 2、已知二叉树的 中序和后序&#xff0c;可以求出前序 3、已知二叉树的 前序和后序&#xff0c;无法求出唯一的中序 二、求法 求法是…