Linux 文件IO
    1标准C库IO库和LINUX/ IO函数的对比
    站在内存的角度考虑IO 文件输入:读取文件到内存中 文件输出:将内存中数据写到文件中
    标准C IO函数是跨平台的(qt也是跨平台的) 跨平台的方式
    1类似JAVA虚拟机 程序都是跑在虚拟机中,VM自动调用对应OS的系统调用
    2 标准C调用了不同平台的系统API 标准C在封装时就考虑了不同平台
    标准C调用了LINUX/ IO的API
    man 3 fopen可以在linux中查看标准C库的函数 比如这条命令就直接找到fopen函数的声明位置
    C库 fopen打开txt 返回FILE fp文件指针 FILE是个结构体其中有3个部分比较重要
    1文件描述符(文件句柄)(整形值):指向当前打开的文件
    2文件读写指针:维护两个指针 读指针和写指针
    3 IO缓存区(所有标准C IO都是带缓冲区的) 写是写入缓冲区中,当缓冲区慢调用一次Linux IO函数(系统调用函数)将数据写入磁盘 缓冲区就是为了提高程序的执行效率(写入磁盘是对硬件操作很慢,将缓冲区内的数据一次性写入降低了写磁盘的频率 效率更高)
    将缓冲区数据(内存)写入磁盘的3种情况 1手动调用fflush 强制将缓冲区数据写入磁盘2缓冲区满了3正常关闭文件(a fclose b return(main) c exit(main) 这3种情况算正常关闭)
    *缓冲区默认(8192字节 约为8k 可以调整大小 但不建议调整)

    1 Linux系统编程入门2 IO,虚拟地址空间,文件描述符,IO函数 - 图1
    1CIO库


    在网络通信是因为时效性,我们希望写入就马上发出不要有缓冲,所以在网络通信时我们采用Linux IO来实现发送

    CIO与LinuxIO的关系
    CIO调用系统调用(Linux IO) CIO先将缓冲区写满(或手动flush) 再调用一次LinuxIO(内核)write写磁盘
    CIO调用一次LinuxIO read先将缓冲区读满(一次读1k) 供用户多次读取(用户一次读1个字节) buf1 和buf2是用户缓冲区和C标准库的I/O缓冲区不同 读写缓冲区是同一块(libio.h)

    1 Linux系统编程入门2 IO,虚拟地址空间,文件描述符,IO函数 - 图2
    2CIO与系统调用

    FILE文件指针的定义在c库的—>FILE.h(usr/include/x86_64-linux-gnu/bits/types)
    —>libio.h _IO_FILE结构体
    1文件描述符(文件句柄)(整形值):指向当前打开的文件
    int _fileno; 由linux系统调用返回 通过这个值来定位具体是哪个文件
    2文件读写指针:维护两个指针 读指针和写指针
    _IO_write_base(写空间的开始位置) _IO_write_ptr(当前写指针的位置)
    _IO_write_end(写空间的结束位置) _IO_read_base(读空间的开始位置)
    _IO_read_ptr(当前读指针的位置) _IO_read_end(读空间的结束位置)
    3 IO缓存区(所有标准C IO都是带缓冲区的)
    _IO_buf_base(缓冲区的开始位置) _IO_buf_end(缓冲区的结束位置)

    *2虚拟地址空间


    1 Linux系统编程入门2 IO,虚拟地址空间,文件描述符,IO函数 - 图3
    3虚拟地址空间


    虚拟地址空间实际不存在,是为了让地址不连续的空间能被利用起来。每个可执行程序被运行后(进程!!!为分配资源的最小单位)都对应一个虚拟内存空间,就是上面这幅图。空间大小由OS位数决定32位机大小为2^32 约为4G,64位机的大小为2^48。以32机为例,0-3G为用户空间,3G-4G(高地址)为内核空间。每个虚拟地址空间的数据地址都会被CPU的MMU(内存管理单元)映射到真实的物理内存地址。最多4G实际不会占用4G。NULL与nullptr的地址都在受保护地址。命令行参数为argc,argv,环境变量在linux中输入env就可看到所有环境变量。
    普通用户对内核的读写权限都没有。

    4文件描述符

    1 Linux系统编程入门2 IO,虚拟地址空间,文件描述符,IO函数 - 图4
    4文件描述符

    虚拟地址空间中的内核空间中,由这个进程(跑起来的程序)的信息PCB。PCB中有一个数组称为文件描述符表(默认长1024),每个文件描述符都能定位1个文件,前三个符被默认占用标准输入输出错误默认状态为打开,这三个默认描述符对应三个文件,Linux中所有的东西都为文件,磁盘是文件,屏幕是文件,键盘是文件,显卡是文件,所有的硬件设备最终都会被虚拟为一个文件(设备文件),OS通过这个文件对硬件进行管理,用户可通过文件对程序进行管理。这三个默认描述符对应的是当前终端/dev/tty设备文件。同一个文件可被多次打开,多次打开后返回的FILE*中的文件描述符的值是不一样的。当使用fclose(c)或close(系统调用)时会将这个文件描述符释放等待下个文件打开再使用。文件描述符的分配规则是在表中找一个没被占用的最小值文件描述符给当前打开的文件。

    5 LINUX系统IO函数(系统调用)
    一、open (c fopen调用这个open)
    int open(const char* pathname,int flags)
    int open(const char* pathname,int flags,mode_t mode)
    LINUX系统IO: man 2 函数名 C IO:man 3 函数名

    1. /*
    2. man 2 open
    3. #include <sys/types.h>//flags宏在这个头文件
    4. #include <sys/stat.h>//flags宏在这个头文件里也有
    5. #include <fcntl.h>//函数声明在这个头文件中
    6. //C语言中没有函数重载 通过可变参数实现了这种效果
    7. int open(const char *pathname, int flags);//打开一个已经存在的文件
    8. 参数:
    9. -pathname:要打开的文件路径
    10. -flags:对文件的操作权限设置 以及其他的一些设置
    11. O_RDONLY,O_WRONLY,O_RDWR 三个宏分别对应只读,只写,读写。
    12. 这三个设置是互斥的,1次open只能选1个且必须选1个。
    13. 返回值: 在man 2中输入 /return value 定位到返回值解释
    14. 返回新的文件描述符fd(整型用于定位),如果发生错误返回-1并且errno将被设置为1个合适的值
    15. errno:属于LINUX系统函数库的一个全局变量,记录的是最近一次的错误号
    16. perror(标准C):打印errno对应的错误描述
    17. #include <stdio.h>
    18. void perror(const char *s);
    19. -s:用户描述,比如hello,最终输出的内容是 hello:xxxxx(具体的错误描述)
    20. int open(const char *pathname, int flags, mode_t mode);//创建一个新的文件
    21. */
    22. #include <sys/types.h> //flags宏在这个头文件
    23. #include <sys/stat.h> //flags宏在这个头文件里也有
    24. #include <fcntl.h> //函数声明在这个头文件中
    25. #include <stdio.h> //perror
    26. #include <unistd.h> //close
    27. int main()
    28. {
    29. //第一种用法打开已经存在的文件
    30. int fd = open("a.txt", O_RDONLY); //在当前路径下打开以存在的a.txt
    31. if (fd == -1)
    32. perror("open:");
    33. //读写操作 待写
    34. close(fd); //关闭一个文件描述符 并使这个文件描述符不再指向文件 等待被再启用
    35. return 0;
    36. }
    37. //gcc open.c -o open
    38. //./open
    39. //open: No Such Directory
    1. /*
    2. man 2 open
    3. int open(const char *pathname, int flags, mode_t mode);//创建一个新的文件
    4. 参数:
    5. -pathname:要创建的文件路径(名称)
    6. -flags:对文件的操作权限设置 以及其他的一些设置
    7. O_RDONLY,O_WRONLY,O_RDWR 三个宏分别对应只读,只写,读写。
    8. 这三个设置是互斥的,1次open只能选1个必选项。
    9. O_APPEND 不覆盖在尾部继续追加
    10. O_CREAT 如果文件不存在则创建新文件
    11. 等的可选项
    12. 在有多个选项时通过|按位或连接如O_RDWR | O_CREAT
    13. flags是int型占4个字节 为32位其中每一位就是一个标志位每一位代表一种情况(1种标记 可读 可写 可读写 创建新文件)
    14. -mode:
    15. 8进制数,表示用户\组对创建出的新文件的操作权限
    16. 在路径下ll后可看到
    17. drwxrwxr-x 第一个字符表示文件的类型 后续字符每3个一组表示权限
    18. r表示可读 w表示可写 x表示可执行
    19. 文件的类型-当前用户对本文件权限-当前用户组对本文件权限-其他组对本文件的权限
    20. 0777(0开头为8进制) 真正被创建的文件权限为 mode&~umask (~取反)
    21. 0777 第一个7为当前用户对本文件权限: r=1(可读) w=1(可写) x=1(可执行) rwx=111 二进制转为8进制为7 余下的两个7同理
    22. 在终端直接输入umask,可以看到umask值为0002(也是8进制) 普通用户0002 root用户0022
    23. ~0002=0775 0777&0775->111111111&111111101->111111101(0775) 普通用户的其他组不可以写本文件
    24. 0022则 当前组将没有写本文件的权限 其他组也不可以写本文件
    25. umask的设置 直接终端 umask 0011就可修改umask为后续的数字 只在当前终端有效
    26. #include <sys/types.h>
    27. #include <sys/stat.h>
    28. mode_t umask(mode_t mask);//程序里也可以手动设置
    29. S_IRWXU 00700 user (file owner) has read, write, and execute permission
    30. S_IRUSR 00400 user has read permission
    31. S_IWUSR 00200 user has write permission
    32. S_IXUSR 00100 user has execute permission
    33. S_IRWXG 00070 group has read, write, and execute permission
    34. S_IRGRP 00040 group has read permission
    35. S_IWGRP 00020 group has write permission
    36. S_IXGRP 00010 group has execute permission
    37. S_IRWXO 00007 others have read, write, and execute permission
    38. S_IROTH 00004 others have read permission
    39. S_IWOTH 00002 others have write permission
    40. S_IXOTH 00001 others have execute permission
    41. 也可以通过按位或|将mode的权限给表达出来
    42. mode为文件本身的权限在创建时给定,flags为程序本次打开对文件的操作,
    43. 如果mode设为不可写,则本次打开就算falgs设为写操作 也不能写
    44. */
    45. //gcc create.c -o create
    46. //./create
    47. //在当前目录下生成create.txt 权限为-rwxrwxr-x 为0775 其他组不可写
    48. //create: No Such Directory
    49. #include <sys/types.h> //flags宏在这个头文件
    50. #include <sys/stat.h> //flags宏在这个头文件里也有
    51. #include <fcntl.h> //函数声明在这个头文件中
    52. #include <stdio.h> //perror
    53. #include <unistd.h> //close
    54. int main()
    55. {
    56. //创建一个新的文件
    57. int fd = open("create.txt", O_RDWR | O_CREAT, 0777); //O_RDWR 0777 umask值为0002 最终权限为0775
    58. if (fd == -1)
    59. perror("create:");
    60. //读写操作 待写
    61. close(fd); //关闭一个文件描述符 并使这个文件描述符不再指向文件 等待被再启用
    62. return 0;
    63. }


    二、read write
    实现一个拷贝txt文件的程序

    1. /*
    2. man 2 read
    3. #include <unistd.h>//在这个头文件里 unix_stander
    4. ssize_t read(int fd, void *buf, size_t count);
    5. 参数:
    6. -fd:文件描述符 open的返回值 可通过文件描述符操作具体的一个函数(从命令行中读可设置为0)
    7. -buf:读到数据具体存放的地方(指定存在内存中这个地方) 给个数组首地址 数组具体多大自己给但小于要读的字节数会返回0
    8. -count:读取的字节数(是请求读取的字节数,读上来的数据保 存在buf中,同时文件的当前读写位置向后移)
    9. 返回值:
    10. 读成功会返回读到的数据的字节数,如果在调read之前已到达文件末尾,则这次read返回0,出现错误返回-1并设置errorno
    11. buf是个传出参数。指定读count个字节的数据,这个conut个字节的数据会被保存在buf中我们read后可再继续操作读出来的数据
    12. 我们可以指定一个较小的buf(不需要buf必须等于或大于被读文件的大小),指定读取sizeof(buf)个字节每次将buf给读满,然后我们将读到的数据拿去处理,处理完后再继续读,直到将文件读完
    13. 如果sizeof(buf)<count会导致文件被当写满buf后会继续往后写入野内存直到写满读到的总字节数这是很危险的,所以记住buf的大小要大于等于count,最后一次读到的数据可能会小于count因为读不满提前结束了返回0
    14. 读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。
    15. 从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,
    16. 如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。
    17. 线程变为阻塞态
    18. man 2 write
    19. #include <unistd.h>
    20. ssize_t write(int fd, const void *buf, size_t count);
    21. 参数:
    22. -fd:文件描述符 open的返回值 可通过文件描述符操作具体的一个函数(输出(写)到命令行中可设置为1)
    23. -buf:常量指针 buf指向的数据不能修改(其实也符合write的逻辑 buf是要写入文件的内容
    24. 相当于范本 范本怎么能改呢) 范本数据(往文件中具体要写的数据)
    25. -count:要写多少个字节的数据 从buf中取多少个字节数据向fd文件中写
    26. 返回值:
    27. 成功返回buf中被写入fd文件的字节数,返回0表示没有数据被写入fd文件中,出现错误返回-1并设置errorno
    28. 如果count大于sizeof(buf),那么会把缓冲区后面的内存中的数据写进去,只不过这些数据我们是不确定的,是野内存,
    29. 操作野内存有可能会产生问题,所以一般不会这么去做。缓冲区中有多少数据,我们就写多少数据即可。
    30. */
    31. #include <unistd.h> //read write
    32. #include <stdio.h>
    33. #include <sys/types.h> //open flags宏在这个头文件
    34. #include <sys/stat.h> //open flags宏在这个头文件里也有
    35. #include <fcntl.h> //open close函数声明在这个头文件中
    36. //通过ll查看english.txt文件的大小为54个字节
    37. int main()
    38. {
    39. //通过open打开english.txt
    40. int srcfd = open("./english.txt", O_RDONLY); //打开源文件 只给读的权限
    41. if (srcfd == -1)
    42. {
    43. perror("open_fail:");
    44. return -1;
    45. }
    46. //创建一个新的文件
    47. int dstfd = open("./dst.txt", O_WRONLY | O_CREAT, 0664); //只写和创造
    48. if (dstfd == -1)
    49. {
    50. perror("open_fail:");
    51. return -1;
    52. }
    53. //读写
    54. char buf[2] = {0}; //故意给个很小的buf 指定一个较小的buf(不需要buf必须等于或大于被读文件的大小)
    55. int read_how_many_bits = 0;
    56. while ((read_how_many_bits = read(srcfd, buf, sizeof(buf))) > 0) //read返回-1表示错误 返回0表示读完
    57. { //大于0则表示文件还没读完
    58. write(dstfd, buf, read_how_many_bits); //将读来的buf中的数据写入新的文件
    59. //read_how_many_bits就是实际读到多少字节 读到多少就写多少
    60. }
    61. //关闭文件
    62. close(dstfd);
    63. close(srcfd);
    64. //gcc txt_copy.c -o copy
    65. return 0;
    66. }


    三、lseek
    对应标准c —seek

    1. /*
    2. 标准c fseek
    3. #include <stdio.h>
    4. int fseek(FILE *stream, long offset, int whence);
    5. linux系统调用
    6. #include <sys/types.h>//宏
    7. #include <unistd.h>//unix标准中声明
    8. off_t lseek(int fd, off_t offset, int whence);
    9. 通过lseek对文件中的文件读写指针进行移动
    10. 参数
    11. -fd open得到的文件描述符
    12. -offset 偏移量
    13. -whence 指定的标记 (和标准C中指定值是一样的)
    14. SEEK_SET 参数offset 即为新的读写位置.
    15. The file offset is set to offset bytes.
    16. SEEK_CUR 以目前的读写位置往后增加offset 个位移量.
    17. The file offset is set to its current location plus offset bytes.
    18. SEEK_END 将读写位置指向文件尾后再增加offset 个位移量
    19. The file offset is set to the size of the file plus offset bytes.
    20. 当whence 值为SEEK_CUR 或SEEK_END 时, 参数offet 允许负值的出现.
    21. 返回值
    22. 返回偏移后的文件指针位置
    23. 欲将读写位置移到文件开头时:lseek( fd, 0, SEEK_SET);
    24. 欲将读写位置移到文件尾时:lseek( fd, 0, SEEK_END);//可以直接获得这个文件的大小
    25. 想要取得目前文件位置时:lseek( fd, 0, SEEK_CUR);
    26. 拓展文件的大小 lseek( fd, 100, SEEK_END);//为文件增加100字节的大小
    27. 比如在下载时提前占用要下载文件大小的空间以免被其他进程占用,之后再慢慢往文件中写数据
    28. */
    29. //通过ll查看english.txt文件的大小为54个字节
    30. #include <sys/types.h> //open flags宏在这个头文件 lseek宏在这个头文件
    31. #include <sys/stat.h> //open flags宏在这个头文件里也有
    32. #include <fcntl.h> //open close函数声明在这个头文件中
    33. #include <unistd.h> //unix标准中声明
    34. #include <stdio.h>
    35. //gcc lseek.c -o lseek
    36. int main()
    37. {
    38. int fd = open("english.txt", O_RDWR);
    39. if (fd == -1)
    40. {
    41. perror("open:");
    42. return -1;
    43. }
    44. //拓展文件的大小
    45. int ret = lseek(fd, 100, SEEK_END);
    46. if (ret == -1)
    47. {
    48. perror("lseek:");
    49. return -1;
    50. }
    51. //写入空数据 需要写入一次数据 拓展的大小才会生效要不然还是原来那么大
    52. write(fd, "", 1);
    53. //155 = 54 + 100 +写入的空(1)
    54. close(fd);
    55. return 0;
    56. }


    四、stat,lstat

    1 Linux系统编程入门2 IO,虚拟地址空间,文件描述符,IO函数 - 图5

    0-11位为1表示有这个权限,为9表示没有权限。每个段下有其对应的宏值,比图S_IRUSR
    00400(8进制)->100(user read为1) 000(group) 000(other)。特殊权限位 setGID设置组id….。文件类型120000(8进制)->1010 000 000 000 000(二进制)表示前4位为1010则为符号链接
    1010(文件类型) 000(特殊权限位) 000(user) 000(group) 000(others)
    当我们获取到了文件的mode参数想要判断这个文件是否有相应的权限 需要将得到的mode与想判断权限的宏按位与来判断,只要结果不为0 则有这个权限。
    判断类型就不是用按位与了,因为文件类型符占四位二进制,
    (st_mode&S_IFMT)==要判断的文件类型宏 S_IFMT = 0170000(8进制)—>1111 000 000 000 000
    只筛选出前四位来判断。

    1. /*
    2. man 2 stat
    3. #include <sys/types.h>
    4. #include <sys/stat.h>
    5. #include <unistd.h>
    6. int stat(const char *pathname, struct stat *statbuf);
    7. int lstat(const char *pathname, struct stat *statbuf);
    8. ln -s a.txt b.txt 将b.txt软连接到a.txt上 b.txt->a.txt 之后再打开b.txt实际上是打开a.txt
    9. 用stat(b.txt)实际上获取到的是链接到的a.txt文件的信息而非b.txt信息(软连接指向的文件的信息)
    10. 通过lstat(b.txt)获取到是b.txt信息(软连接文件的信息)
    11. 参数:
    12. -pathname 文件路径
    13. -statbuf 结构体指针 为传出参数(保存着获取到的信息)
    14. 返回值:
    15. 成功返回0 失败返回-1并设置errno
    16. struct stat {
    17. dev_t st_dev;//文件的设备编号
    18. ino_t st_ino;//节点 Inode
    19. mode_t st_mode;//文件的类型和存取权限
    20. //st_mode为16位整数 具体看图
    21. nlink_t st_nlink;//连接到该文件的硬连接数
    22. uid_t st_uid;//用户ID
    23. gid_t st_gid;//组ID
    24. dev_t st_rdev;//设备文件的设备编号
    25. off_t st_size;//文件大小 字节数
    26. blksize_t st_blksize;//块大小 每块512字节
    27. blkcnt_t st_blocks; //块数
    28. struct timespec st_atim;//最后一次访问时间
    29. struct timespec st_mtim;//最后一次修改时间
    30. struct timespec st_ctim;//最后一次改变时间 指属性
    31. #define st_atime st_atim.tv_sec
    32. #define st_mtime st_mtim.tv_sec
    33. #define st_ctime st_ctim.tv_sec
    34. };
    35. 返回文件信息在statbuf中给开发者
    36. linux中有stat命令
    37. stat english.txt
    38. 文件:english.txt
    39. 大小:155 块:8 IO 块:4096 普通文件 一个块为4096字节一共占用了8块
    40. 设备:801h/2049d Inode:1084266(linux中用于标识文件) 硬链接:1
    41. 权限:(0664/-rw-rw-r--) Uid(用户id):( 1000/ wen) Gid(组id):( 1000/ wen)
    42. 最近访问:2021-03-31 21:45:17.481908115 +0800
    43. 最近更改:2021-03-31 21:45:17.421908568 +0800
    44. 最近改动:2021-03-31 21:45:17.421908568 +0800
    45. 创建时间:-
    46. */
    47. #include <sys/types.h>
    48. #include <sys/stat.h>
    49. #include <unistd.h>
    50. #include <stdio.h>
    51. int main()
    52. {
    53. struct stat statbuf;
    54. int ret = stat("english.txt", &statbuf);
    55. if (ret == -1)
    56. {
    57. perror("stat:");
    58. return -1;
    59. }
    60. print("size:%ld\n", statbuf.st_size); //打印大小
    61. return 0;
    62. }


    五、模拟实现ls -l命令

    1. /*模拟实现ls-l指令
    2. ./app english.txt
    3. -rw-rw-r-- 1 wen wen 155 4月 6 21:35 english.txt
    4. struct stat {
    5. dev_t st_dev;//文件的设备编号
    6. ino_t st_ino;//节点 Inode
    7. mode_t st_mode;//文件的类型和存取权限
    8. //st_mode为16位整数 具体看图
    9. nlink_t st_nlink;//连接到该文件的硬连接数
    10. uid_t st_uid;//用户ID
    11. gid_t st_gid;//组ID
    12. dev_t st_rdev;//设备文件的设备编号
    13. off_t st_size;//文件大小 字节数
    14. blksize_t st_blksize;//块大小 每块512字节
    15. blkcnt_t st_blocks; //块数
    16. struct timespec st_atim;//最后一次访问时间
    17. struct timespec st_mtim;//最后一次修改时间
    18. struct timespec st_ctim;//最后一次改变时间 指属性
    19. #define st_atime st_atim.tv_sec
    20. #define st_mtime st_mtim.tv_sec
    21. #define st_ctime st_ctim.tv_sec
    22. };
    23. */
    24. #include <sys/types.h>
    25. #include <sys/stat.h>
    26. #include <unistd.h>
    27. #include <stdio.h>
    28. #include <pwd.h> //getpwuid
    29. #include <grp.h> //getgrgid
    30. #include <time.h> //ctime
    31. #include <string.h> //strncpy strlen
    32. int main(int argc, char *argv[])
    33. {
    34. if (argc < 2)
    35. {
    36. //一般一定会有一个参数 是可执行文件的路径
    37. //再加上跟上的文件名 一共至少为两个参数 模拟才会生效
    38. //所以这里判断一下参数个数小于2个就return
    39. printf("%s filename\n", argv[0]); //argv[0]为执行文件的路径
    40. return -1;
    41. }
    42. struct stat statbuf;
    43. //通过stat获得文件的参数
    44. int ret = stat(argv[1], &statbuf); //argv[1]为传入的文件路径
    45. if (ret == -1)
    46. {
    47. perror("stat:");
    48. return -1;
    49. }
    50. //获取文件类型和文件权限
    51. char perms[11] = {0}; //1位文件类型+9位文件权限+一位/0
    52. //具体文件类型和权限在st_mode中可以具体看word 22页的图
    53. // (st_mode & S_IFMT) == 要判断的文件类型宏
    54. // S_IFMT = 0170000(8进制)-- > 1111 000 000 000 000 只筛选出前四位来判断(前四位就指定了文件类型)
    55. //
    56. switch (statbuf.st_mode & S_IFMT) //
    57. {
    58. case S_IFLNK: //链接文件
    59. perms[0] = 'l';
    60. break;
    61. case S_IFDIR: //目录
    62. perms[0] = 'd';
    63. break;
    64. case S_IFREG: //普通文件
    65. perms[0] = '-';
    66. break;
    67. case S_IFBLK: //块文件
    68. perms[0] = 'b';
    69. break;
    70. case S_IFCHR: //字符设备
    71. perms[0] = 'c';
    72. break;
    73. case S_IFSOCK: //套接字文件
    74. perms[0] = 's';
    75. break;
    76. case S_IFIFO: //管道文件
    77. perms[0] = 'p';
    78. break;
    79. default:
    80. perms[0] = '?'; //未知文件
    81. break;
    82. }
    83. //获取文件的访问权限分为3组
    84. //文件所有者
    85. perms[1] = statbuf.st_mode & S_IRUSR ? 'r' : '-'; //用户读权限 有这个权限的话按位与表达式不为0
    86. perms[2] = statbuf.st_mode & S_IWUSR ? 'w' : '-'; //用户写权限 有这个权限的话按位与表达式不为0
    87. perms[3] = statbuf.st_mode & S_IXUSR ? 'x' : '-'; //用户执行权限 有这个权限的话按位与表达式不为0
    88. //文件所在组权限
    89. perms[4] = statbuf.st_mode & S_IRGRP ? 'r' : '-'; //所在组读权限 有这个权限的话按位与表达式不为0
    90. perms[5] = statbuf.st_mode & S_IWGRP ? 'w' : '-'; //所在组写权限 有这个权限的话按位与表达式不为0
    91. perms[6] = statbuf.st_mode & S_IXGRP ? 'x' : '-'; //所在组执行权限 有这个权限的话按位与表达式不为0
    92. //其他人
    93. perms[7] = statbuf.st_mode & S_IROTH ? 'r' : '-'; //其他人读权限 有这个权限的话按位与表达式不为0
    94. perms[8] = statbuf.st_mode & S_IWOTH ? 'w' : '-'; //其他人写权限 有这个权限的话按位与表达式不为0
    95. perms[9] = statbuf.st_mode & S_IXOTH ? 'x' : '-'; //其他人执行权限 有这个权限的话按位与表达式不为0
    96. //硬连接数
    97. int linknum = statbuf.st_nlink;
    98. //文件所有者
    99. // extern struct passwd *getpwuid(__uid_t __uid);
    100. // struct passwd
    101. // {
    102. // char *pw_name; /* Username. 我们需要的用户名称 */
    103. // char *pw_passwd; /* Password. */
    104. // __uid_t pw_uid; /* User ID. */
    105. // __gid_t pw_gid; /* Group ID. */
    106. // char *pw_gecos; /* Real name. */
    107. // char *pw_dir; /* Home directory. */
    108. // char *pw_shell; /* Shell program. */
    109. // };
    110. char *fileUser = getpwuid(statbuf.st_uid)->pw_name; //只能得到uid而非用户名称 需要通过getpwuid得到
    111. //文件所在组 同理
    112. char *fileGrp = getgrgid(statbuf.st_gid)->gr_name;
    113. //文件大小
    114. long int fileSize = statbuf.st_size;
    115. //获取修改时间
    116. char *time = ctime(&statbuf.st_mtime); //从1970 1.1 0:0到当前的秒数 通过ctime变为当前时间
    117. //时间自带换行 要将这个换行去掉
    118. char mtime[512] = {0};
    119. strncpy(mtime, time, strlen(time) - 1); //-1就是去掉最后的这个换行符
    120. char buf[1024];
    121. sprintf(buf, "%s %d %s %s %ld %s %s", perms, linknum, fileUser, fileGrp, fileSize, mtime, argv[1]);
    122. printf("%s\n", buf);
    123. //向buf中添加内容 %s文件类型和文件权限 %d连接数 %s所有者 %s所在组 %d文件大小 %s修改时间 %s文件名
    124. return 0;
    125. }
    126. //gcc ls-l.c -o ls 生成可执行文件ls