Linux系统编程(三)—— 文件编程(1)目录和文件
3.1 目录和文件
- 贯穿始终的例子:做一个类似
ls
命令的实现。如myls
1、命令
-
(1)一个命令的格式:
cmd --长格式 -短格式 非选项的传参
比如ls --all
和ls -a
,这两个结果是一样的:
-
(2)为什么短格式可以,还要存在长格式呢?
可能两个单词的首字母什么的会相同。或者两个单词的缩写撞在了一起。
-
(3)如果要创建一个名字为 -a 的文件,应该如何做?
解决办法:
1)touch -- -a
命令后面加上两个减号,表示当前的命令结束;2)
touch ./-a
将路径写上,就不会被认作命令参数了。上述两种方法,对于删除 -a 这个文件也同样适用。
-
(3)注意:
ls -l
和ls -n
的区别:前者显示用户ID ,后者显示用户NAME -
(4)
/etc/passwd
和/etc/group
文件1)
/etc/passwd
文件,用户名 :group名 :用户ID :groupID
2)/etc/group
文件, 用户名:组名 :组号
2、如何获取文件属性:stat (系统调用)
-
注意:这三个函数的格式,在linux当中,很多函数的封装是沿用这样的规律的,很多命名规则都是这样的
(1)stat
: 通过名字做这件事
(2)fstat
: 通过文件描述符来做这件事情
(3)lstat
: lstat 和 stat 在参数和返回上都一样,但是他们在link的处理上是不同的
(stat面对符号链接文件时获取的是所指向文件的属性;而 lstat 面对符号链接是获取的是符号链接文件属性) -
stat 结构体长这样,包含文件的全部内容:
-
同时
stat
也是一个命令:用来显示一个文件的属性, 所以stat这个命令是用stat这个函数封装出来的。
-
例子: 显示一个文件的大小:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
static int flen(const char *fname) // static表示这个函数禁止外部扩展
{
struct stat statres;
if(stat(fname, &statres) < 0)
{
perror("stat");
exit(1);
}
return statres.st_size;
}
int main(int argc, char **argv)
{
if(argc < 2)
{
fprintf(stderr, "Usage ... \n");
exit(1);
}
printf("%d \n", flen(argv[1]));
exit(0);
}
运行结果:
- 注意:
实际上,windows中,一个文件的大小就是所占的磁盘的大小,但是在linux中,并不是这样的,一个文件所占的大小是由这个文件所占用的block大小和block数量决定的,block是指一个文件所占用的块数, 永远指的是扇区数。
3、空洞文件
我们做一个非常大的文件,但是占用磁盘空间很小,甚至不占用:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int fd;
if(argc < 2)
{
fprintf(stderr, "Usage ...\n");
exit(-1);
}
fd = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC, 0600);
if(fd < 0)
{
perror("open");
exit(-1);
}
// 5G, 将文件尾指针一下子扯过去
lseek(fd, 5LL * 1024LL * 1024LL * 1024LL - 1LL, SEEK_SET);
// 使用一次系统调用,如果没有这次系统调用,当前文件是不占用空间的
write(fd, "", 1);
close(fd);
exit(0);
}
运行这个文件,然后得到一个大文件,可以看到,虽然这个文件显示的是5G大小,但是它所占磁盘空间才8k, 很小很小。
然后cp 拷贝一下这个文件,你会发现拷贝以后的文件所占磁盘大小尾0, 为什么呢??
因为cp在拷贝的时候,会判断这个文件是否是空洞文件,如果中间有空字符,它会记住空字符的长度,但是不会存这个空字符,所以它拷贝以后的这个文件是0字节。
4、文件属性与访问权限问题
-
当执行命令
ll
的时候,如下: (红框框中的内容是文件的权限信息):
-
文件的权限信息包括:(文件的权限信息存在于
st_mode
当中,st_mode
的存放形式是以位图的形式存放的, 是16位的一个整型数。 -
为什么是16位?
(1)基本位是9位(owner读写执行权限 (421)+ group读写执行权限(421)+other读写执行权限(421));
(2)加另外3位(粘住位,set groupID位,set userID位);
(3)文件类型是7类,用二进制表示使用3位;=>加一起是15位,但是没有15位的整型数,所以是使用16位的整型数表示的。
-
st_mode存了两种类型:
(1)文件类型(分为
dcb-lsp
七种文件类型)
d
——目录;
c
——字符设备文件;
b
——块设备文件;
-
——常规文件;
l
——符号链接文件;
s
——网络套接字socket文件;
p
——管道文件(2)文件权限(当前用户owner, 当前组用户group, 其他用户other)
-
注意: st_mode是一个16位的位图,用于表示文件类型、文件访问权限、特殊权限位。
5、umask
-
umask
是一条命令,用来查看当前umask的值是多少,也可以更改当前umask的值:
-
创建文件的话,权限会满足
0666& ~umask
的表达式。这种机制的存在就是为了防止产生权限过松的文件。 -
umask还是 一个系统调用函数,如果在进程当中想要设置这个umask的值的化,可以调用umask这个函数, 如下:
6、文件权限的更改与管理 :chmod,fchmod
-
chmod
当命令时的作用是在终端上更改文件权限:
可以看到,我们将 test.c 的other权限中,加上了 w 可读的权限 -
由于 test.c 是没有执行权限的,然后我们使用
chmod a+x test.c
,给他所有用户加上可执行权限。a+x
表示的是所有用户有可执行权限,所以我们还可以使用u+x
,g+x
,o+x
-
如果在一个进程当中操作一个文件的时候,需要临时改变一个文件的权限信息,就需要
chmod( )
函数与fchmod( )
函数, 这个是两个个系统调用函数。(前者操纵的是文件路径名,后者操纵的是文件描述符)
7、粘住位( t 位)
- t 位原始的功能:给某一个当前二进制命令设置t位,设置t位的作用是把某一个命令的使用痕迹进行保留,为的是下次再装载这个模块的时候比较快。通俗的讲就是:在内存当中保留它的使用痕迹,下次装载会比较快。通常是给一个可执行的命令进行设计。
- 现如今这个设计无所谓了,因为有了page的设计,本来使用的内存块就会留在内存当中,所以这个 t 位在慢慢的淡化,现在常用于给目录设置 t 位。
- 目前目录设置t位的是
/tmp/
:
给这个目录加上t位以后,各个用户对于目录的操作,以及对目录下的文件的操作就比较特殊化了。
8、文件系统:FAT,UFS
- 文件系统:实际上是文件或者数据的存储格式问题。归根结底是为了帮助我们存储或管理文件。
- 比如说:钱存在中国银行叫人民币,存在美国银行叫美元,但是钱是一样的。数据也是一样,只是存在在了不同的文件系统当中。
(1)FAT文件系统
- FAT的实质是一个静态存储的单链表,, 具体的实际结构如下:
struct{
int next[N];
char data[N][data_size];
}
正是因为N的存在,FAT存储的文件是有限的,不能超过N个数据块。
-
拿到windows的时候,首先第一件事是要分区,为什么要分区,因为那个时候的FAT系统承载能力有限,过多的文件或者很大的文件当前承载不了,所以说要分区。
-
FAT文件系统最大的缺陷就是使用了静态单链表,单链表最大的缺陷是一个走向,指针不能往回指。如今FAT系统还是在使用的,比如说小u盘, 小SD卡,为了轻量级。
-
360内存清理: 用一个进程不停的吃内存吃内存(要内存),在抢资源,留在内存中的常用的数据块就在往swap交换分区去挪,当进程强占资源到一定程度的时候,就会立马结束,然后给用户提示,内存清理成功,实际上全都挤到交换分区中去了。当你用的时候,交换到swap分区中的数据又会回来,在自己逗自己玩(火绒 yyds)。
-
注意: 当内存换出率和换入率都在直线上升的时候,才是内存吃紧的时候。
-
FAT系统特别惧怕大文件。
(2)UFS文件系统
-
inode结构体介绍
-
如何知道哪个 inode 用了,哪个 inode 没有用?
这里其实用到了 inode 位图,有多少个 inode 就有多少个 inode 位图,这俩一一对应着。如果 inode 用了,则与其对应的 inode 位图为 1, 如果没用,则为 0 。(数据块和数据块位图也是一样的道理)
9、硬链接,符号链接
(1)硬链接:ln
-
通过:
ln 源文件 链接文件
的命令格式,可以产生硬链接 -
本质上有点类似两个指针指向同一块区域。
这里通过stat
命令可以查看到文件 big.c 的inode号以及硬链接数目。再使用命令:ln big.c big_link.c
,结果如下:
源文件的硬链接数目变成了2,再查看生成的 big_link.c 的文件属性:
可以看到 big.c 与 big_link.c 这两个文件的 inode 号其实是一样的,所以他俩其实是指向同一个文件(这就有点类似两个指针同时指向同一块内存空间一样)。 -
删掉源文件,硬链接文件依然有效:
(2)符号链接:ln -s
-
没有软连接,只有符号链接,不要搞混了
-
通过:
ln -s 源文件 符号链接文件
格式,可以产生符号链接文件 -
符号链接有点类似 Windows 上面的快捷方式。生成的符号链接文件和源文件其实是两个东西(文件属性不一样)
可以看到生成 test_s_link.c软链接文件后,源文件 test.c 的文件属性并没有改变,Links数目还是1:
再看 test_s_link.c 文件属性:
可以看到 test_s_link.c 的 inode 号与源文件已经不一样了。再注意:该符号链接文件的 Size 大小,其实就是源文件 名字的大小(这里是6字节)。 -
删掉源文件,软链接文件就无效了:
-
文件权限信息开通表示的是文件类型,
l
表示的是链接文件(本质就是符号链接文件)
(3)系统调用函数:link( )
- 命令
ln
就是由该系统调用函数link( )
封装出来的
(4)系统调用函数:unlink( )
-
注意:利用
rm
删除一个文件,实质是使该文件的硬链接数为0,没有任何进程线程打开或引用该文件,此时那块数据空间才会被释放掉。(只要还有内容引用到那数据块,那就还没有删掉,即便硬链接数目已经为0了) -
系统调用函数:unlink( )
注意:使用 unlink( )可以特别容易创建匿名文件。比如,首先利用 open( )函数产生一个文件,获得一个文件描述符 fd,然后马上 unlink( )掉这个文件。但是,该文件并没有立即被删除掉,只有当 close(fd) 后,该文件才会被彻底删除。
(5)标准库函数:remove( )
- 该函数位于 man 手册第三章,
rm
命令就是由该函数封装而来的
(6)系统调用函数:rename( )
- 该函数位于 man 手册第二章,mv命令就是由该函数封装而来的
(7)硬链接与符号链接的特点
- 硬链接与目录项是同义词,且建立硬链接有限制:不能给分区建立,不能给目录建立。
- 符号链接的优点:可以跨分区,可以给目录建立
10、utime
- 利用
ll
命令可以看到的一个时间,指的是:MTIME
系统调用函数:utime( )
- 系统调用函数:utime( ),用于修改最后一次读和修改的时间(可以修改ATIME和MTIME)
11、目录的创建和销毁
(1)系统调用函数:mkdir( )
- 命令
mkdir
就是利用mkdir( )来封装的,用于创建一个目录
(2)系统调用函数:rmdir( )
- 命令 rmdir 就是利用rmdir( )来封装的,用于删除一个目录(只能是空目录)。对于非空目录,要用递归的方式来删除
12、更改当前工作路径:chdir( )
- 命令
cd
就是由系统调用函数chdir( )
封装而来的
13、获取当前工作路径:getcwd( )
- 命令
pwd
就是由标准库函数getcwd( )
封装而来的
14、分析目录与读取目录内容
(1)认识 argc 与 argv
- argc —— 表示运行程序时,传给主函数的命令行参数个数;
- argv[ ] —— 指针数组,存指向放传给主函数的命令行参数的地址。
例如:
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("argc = %d\n", argc);
for (int i = 0; i < argc; i++)
puts(argv[i]);
return 0;
}
运行结果:
(2)glob( )函数:解析模式或通配符
-
glob( )
函数用于文件系统中路径名称的模式匹配,globfree( )
用来释放空间
-
参数:
pattern——要分析的路径;
flags——选择匹配模式,如是否排序,或者在函数第二次调用时,是否将匹配的内容追加到pglob中,不进行特殊模式匹配则写0;
errfunc——glob函数执行出错会执行的函数,出错的路径会回填到epath中,出错的原因回填到eerrno中。如不关注错误可设置为NULL
pglob —— 解析出来的结果放在这个参数里 -
返回值:成功 0 失败 非0
-
这里可以留意一下 glob_t 结构体:
gl_pathc 与 gl_pathv 有点类似于 argc 和 argv -
案例:
#include <stdio.h>
#include <stdlib.h>
#include <glob.h>
#define PAT "/home/mutouren/tmp/test*"
int main() {
glob_t globres;
int err = glob(PAT, 0, NULL, &globres);
if (err) {
printf("Error code = %d\n", err);
exit(1);
}
for (int i = 0; i < globres.gl_pathc; i++)
puts(globres.gl_pathv[i]);
globfree(&globres); // 释放空间
exit(0);
}
这段代码可以打印出:/home/mutouren/tmp/ 目录下所有以 test开头的文件
(3)opendir( ) 与 closedir( ):打开或关闭目录文件
打开目录文件
关闭目录文件 (从这里可以看出,生成的 DIR 类型的数据实在堆上的)
(4)readdir( ):读取目录文件
- 用于读取一个目录文件,保存在一个 dirent 结构体变量当中
dirent结构体的定义:
- 例子:打印 /home/mutouren/tmp 目录下的全部文件
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#define PAT "/home/mutouren/tmp"
int main() {
DIR* dp;
struct dirent* cur;
dp = opendir(PAT);
if (NULL == dp) {
perror("opendir()");
exit(1);
}
while ((cur = readdir(dp)) != NULL)
puts(cur->d_name);
closedir(dp);
return 0;
}
运行结果:
(5)du 命令
- du 命令的作用:会显示指定的目录或文件所占用的磁盘空间。
这里 test.c 所占磁盘空间为 4K,也等于stat出的文件属性中的 blocks 数目除以2
参考资料:请多多支持原博主