ls
编写ls命令
- 列出目录的内容
- 读取并显示文件的属性
- 给出名字,判断它是目录还是文件
目录
- 目录是一种特殊的文件,它的内容一条条记录,每一个记录代表一个文件或者目录
- 目录文件永远不空,至少包含两个特殊项”.”或者”..”
操作目录的相关函数
1 | #include <sys/types.h> |
opendir返回值:成功返回一个目录流,失败返回NULL,同时errno设置为相应值readdir返回值:成功返回对应结构体的指针(该结构体被静态分配,不要使用free()释放);发生错误时返回NULL,同时errno会被相应设置;如果到达目录流的末尾,也会返回NULL(errno提前设置为0,不会发生改变)closedir返回值:0表示成功,-1表示发生错误d_type参数:
1 | DT_BLK This is a block device. |
编写ls -l
编写该命令,既需要读取目录文件,也需要根据目录文件中的名称来获取对应的文件信息
ls -l显示示例:
1 | -rw-rw-r-- 1 zhaoyan zhaoyan 1497 11月 12 14:08 a.txt |
- 模式(mode):首位表示文件类型
-:普通文件d:目录c:字符设备文件b:块设备文件l:符号链接文件(Symbolic Links)s:套接字文件p:管道文件
- 链接数(links):指该文件被引用的次数
- 文件所有者(owner):
- 组(group):文件所有者所在的组
- 大小(size):显示文件的大小,单位是字节,目录所占空间是以块(每块512字节)为单位进行分配的,所以目录的大小经常是相等的
- 最后修改时间(last-modified)
- 文件名(name)
stat()函数
进程定义一个struct stat,然后调用stat函数,让内核将文件属性放入这个数据结构中(之前目录结构体dirent是直接返回指针,为什么有这样的区别?)
该函数返回关于一个文件的信息
1 | #include <sys/types.h> |
- 返回值:如果成功返回0,否则返回-1,并且
error会设置为相应的错误类型
如何显示st_mode
st_mode是一个16位的二进制数,文件类型和权限被编码在这个数中
- 高四位:文件类型,最多可以标识16种
- 中间3位:文件的特殊属性,1表示有,0表示没有。这三位分别是
set-user-ID,set-group-ID和sticky - 最后9位是许可权限,分别对应3种用户(文件所有者,同组用户,其他用户)
所以,只需要通过stat.h文件提供的掩码即可获得
根据用户/组ID寻找name
/etc/passwd文件
1 | zhaoyan:x:1000:1000:zhaoyan,,,:/home/zhaoyan:/bin/bash |
- 该文件中每一行代表一个用户,冒号分割不同的字段。
- 每一个字段依次为:用户名、密码、用户ID、用户所属组ID、用户的全名、主目录、用户使用shell程序的路径
- 该文件并没有包括所有的用户。在分布式系统中,通常在一台所有人都能访问的主机上保存所有的用户信息,该主机称为NIS,所有的主机通过NIS进行用户身份验证。所有需要用户信息的程序也从NIS上获取。本地只保存所有用户的一个子集以备离线操作。
通过getpwuid得到完整的用户列表
通过库函数getpwuid来访问用户信息(从/etc/passwd或者NIS中获取)
1 | #include <sys/types.h> |
- 返回值:成功返回结构体指针,发生错误或者查询为空返回NULL。调用之前请将errno置为0
/etc/group文件
1 | zhaoyan:x:1000: |
每一个字段依次为组名,组密码,组ID,组中的成员列表
passwd文件中每个用户所属的组,是用户的主组(primary group)。用户还可以是其他组的成员,只需要把它的用户名添加到
/etc/group中这个组所在行的最后一个字段即可。
通过getgrgid访问组列表
1 | #include <sys/types.h> |
- 返回值:成功返回结构体指针,发生错误或者查询为空返回NULL。调用之前请将errno置为0
详解st_mode中三个特殊位
set-user-ID位
普通用户可以通过passwd来修改密码,用户密码存放在/etc/passwd中,但是可以看到普通用户并没有权限来修改该文件。
所以passwd命令有一个特殊权限s,使得其特殊属性包含set-user-ID位。SUID位告诉内核,运行这个程序时认为时由该文件所有者在运行这个程序,在这里是root,而root有权限修改/etc/passwd文件。
1 | -rw-r--r-- 1 root root 2862 10月 9 17:55 /etc/passwd |
对于一些所有用户共享的文件,普通用户只能以受限方式进行修改,那么就将该文件的所有者设置为root,同时受限命令的所有者添加’s’权限
set-group-ID位
类似于SUID,如果SGID置位,那么程序运行时认为是由该文件所属组中的某一个用户运行
sticky位
- 对于文件,使用交换(swap)技术会用到该位。现在已经被虚拟内存技术所取代
- 对于目录,例如用来存放临时文件的
/tmp,每一个用户都可以在该目录中创建/删除文件,sticky位使得该目录中的文件只能被创建者删除
特殊属性标识
许可权限位上所有者的s表示set-user-ID,组用户的s表示set-group-ID;其他组的t表示sticky被设置
设置和修改文件的属性
文件类型
- 文件类型有普通文件、目录文件、设备文件、socket文件、符号链接文件、命名管道(named pipe)文件等。
- 文件类型是在创建文件时设置的,不同的系统调用创建不同类型的文件(如creat创建一个普通文件)
- 文件已经创建,其类型就无法修改了
许可位和特殊属性位
每个文件都有9位许可权限和3位特殊属性。它们可以被chmod系统调用修改
建立文件的模式
1 | fd = creat("newfile" , 0744); |
上述参数只是请求。程序请求的许可权限还会与上”新建文件掩码”(file-creation-mask)来得到文件的最终模式。新建文件掩码umask是一个非常有用的系统变量。例如:
1 | open("a.txt" , O_RDWR | O_CREAT, 0777); |
可以看到open函数所要求的文件权限0777并没有得到满足,正好是和umask相与之后的结果。
改变文件的模式
使用系统调用chmod,该命令不会收到umask影响,文件许可和特殊属性都可以通过其进行修改。
1 | #include <sys/stat.h> |
- 返回值:成功则返回0,发生错误返回-1
文件的链接数
链接数就是文件被引用的次数(别名的数量)。
增加文件的别名(使用link)使链接数增加,减少别名(unlink)使得链接数减少。
文件所有者与组
文件所有者就是创建该文件的用户。如果通过程序创建一个文件,且程序具有SUID位,那么新文件的所有者就是程序文件的所有者
通常,新文件组和创建者的组一致。有些情况下,组会被设置为和父目录的组相同。
修改文件所有者和组
1 | #include <unistd.h> |
- 返回值:成功则返回0,失败则返回-1
或者通过Shell命令chown和chgrp也可以修改文件所有者和组
文件大小
不存在能够直接减小文件占用空间的函数
时间
每一个文件都有3个时间:最后修改(modification)时间、最后访问(access)时间和属性最后修改时间。
修改最后修改和访问时间
1 | #include <sys/types.h> |
- 返回值:成功返回0,发生错误返回-1
- 使用Shell命令
touch也可以修改这两个时间
文件名
使用系统调用rename可以修改文件/目录的名字,还可以移动文件的位置。
1 | #include <stdio.h> |
- 返回值:成功返回0,遇到错误返回-1
编程练习
ls的完善1
1.为ls02添加支持./ls02 /tmp的功能
- 原始不能支持该功能,因为在使用stat()寻找文件信息时,默认在当前目录下寻找,所以找不到指定目录下的文件信息
- 修改:对于指定目录下的文件,拼接出绝对路径就好了
- strcpy(s1,s2):字符串拷贝,直接将s2拷贝到s1串的起始位置
- strcat(s1,s2):字符串拼接,先寻找s1串的’\0’字符,然后从该位置开始拼接
ls的完善2
在文件ls01.c中实现了ls对-a -l的支持,同时支持排序,分栏显示,颜色显示
分栏显示
系统的ls支持:
1.按序显示
2.按照排序规则按列从上往下显示,当前列显示完成后转到下一列继续显示
3.每一列都是左对齐的
4.每一列的宽度都是该列最长文件名长度加2
5.列的数目要尽量保证每一行都被文件名“填充满”,而又不会导致行中最后一个文件名换行
实现
1.查询屏幕宽度,使用ioctl系统调用
1 | struct winsize size; |
2.统计所有filename的总长度,然后除以屏幕宽度,从而得到一个基础行数
3.根据行数计算列数,然后求每一列的宽度(该列中最长的filename,用col_len表示),以及这个最长的filename在第几行(用col_idx表示)。如果所有列总长小于屏幕宽,结束输出行数。否则转4
4.想要减小列总长,只要两列的最大filename能够移动到同一列,则总列长大概率能够缩小(除非一列中最长的filename有多个),所以按照这个思路,只需要第一次满足i*add_row_num >= col_idx即可求得增加的行数,然后转3.
排序
自己实现了快排,具体实现中指针的使用还需要学习
颜色显示
1 | man dircolors |
- 这样在
color文件中即可找到每种类型的文件的默认颜色值 - printf打印颜色
教训(找了2小时的BUG)
C语言指针、数组、二维、内存分配其乐无穷
1 | //改进之前 |
- !!问题:在调用
getpwuid,getgrgid等函数之后,发现filename[i]中的值神奇的改变了,明明这些系统调用不需要传入filename的。在改进之后问题就消失了。 - 猜测:改进之前,name的内存是系统调用分配的,然后在使用其他系统调用,这些内存区被改变了?
- 启示:
1.如果需要持久化系统调用给的内存,最好自己动态内存分配,在copy过来。
2.学习这种分配字符串数组的方式