Linux学习(一):文件操作
目录
VFS
不想看也可以直接调到文件操作部分
1、虚拟文件系统
在学习Linux文件操作之前,我们需要对VFS有一个大概的了解
VFS(Virtual Filesystem Switch):虚拟文件系统或虚拟文件系统转换
VFS称为内核的子系统,VFS提供一个统一的接口,一个具体文件系统想要被Linux支持,就必须按照这个接口编写自己的操作函数,并且将自己的细节对内核其他子系统隐藏起来。
2、VFS主要作用
a)支持多种具体文件系统之间的相互访问;
b)接受用户层的系统调用,比如open,link,write等;
c)对具体文件系统的数据结构进行抽象,以一种统一的数据结构进行管理
d)接受内核其他子系统的操作请示
3、Linux系统当中文件系统逻辑关系架构
4、VFS在实际系统调用常用操作
mount()/umount(), sysfs(), chroot(), chdir()/fchdir()/getcwd(), mkdir()/rmdir(), readlink()/symlink(), chown()/fchown()/lchown, select()/poll(), flock()。
5、VFS核心数据结构(对象)
a) 超级块(superblock)对象————每一个文件系统都有一个超级块对象
b) 索引节点(inode)对象————每一个文件都有一个索引节点对象,每一个索引节点对象都有一个索引节点编号,这个编号唯一可以标识某一个文件系统当中指定的文件
c) 目录项(dentry)对象
d) 文件(file)对象
当前学习阶段了解这么多应该就够了,后续的内容还没有接触到
文件操作
文件信息查看
查看文件详细信息的指令——stat filename
如图所示,查看当前目录的文件信息
大小: 在Linux中一切皆文件,因此目录也是文件,也有大小,在这里,该目录的大小为4096,注意这里的大小不是这个文件的容量大小,如果需要查看文件或者文件夹的大小,需要用到du -sh命令
Linux系统中,目录(directory)也是一种文件。打开目录,实际上就是打开目录文件。目录文件的结构非常简单,就是一系列目录项(dirent)的列表。每个目录项,由两部分组成:所包含文件的文件名,以及该文件名对应的inode号码。所以这里的大小并不是文件容量的大小
块: 硬盘的最小存储单位叫做“扇区”(Sector)。每个扇区储存512字节(相当于0.5KB)。“块”的大小,最常见的是4KB,即连续八个 sector组成一个 block。
设备: 设备编号。
Inode: Inode编号,所有文件都对应有一个Inode编号。
硬链接: 硬链接数,就是有多少种方式,可以访问到当前目录或者文件。文件的硬链接数一般是1(绝对路径)。目录的硬链接数至少是2(绝对路径或者cd .),目录包含的子目录越多硬链接数越多(子目录下进去上级cd …)。
权限: 以(0664/-rw-rw-r–)为例,先看后面-rw-rw-r–中分为四组,分别为- rw- rw- r-- 。第一组,代表文件类型,比如说目录的第一组为d,意为directory,目录,第二组代表用户权限,第三组代表用户所属组权限,第四组代表其他权限。后三组主要有三个权限内容,分别是r w x,意为可读,可写,可执行。前面的数字是后面权限的一种表示方式。r代表4,w代表2,x代表1 所以4为r–,5为r-x,6为rw-,7为rwx,以此类推。
更改文件权限
首先可以使用ls -l 查看权限
方法一
如图所示,使用ls -查看123.txt的文件权限可以看出权限为 -rw-rw-r–, 接下来,我们可以使用chmod修改文件的权限。
首先,使用chmod前缀,然后的格式为 修改对象 +/- 对应的权限,+为允许该权限,-为禁止该权限,请看以下案例:
在这里 u代表user即用户,所以这里的u-w指的是禁止用户的写入权限,而给予用户写入权限可以用u+w。
u----用户,g----所属组,o----其他,a----所有身份。w----写入,r----读取,x----执行。
方法二
如图所示,123.txt 文件已经没有任何权限,现在我们想要给用户添加可读和可执行的权限,除了方法一之外还有什么方法呢。我们可以不用chmod u+r 123.txt; chmod u+x 123.txt。而是采取覆盖权限的方法。
如图所示,
可见,u=rx 的意思为用户的权限被r-x覆盖。
方法三
r----4,w----2,x----1。请记住三个权限所对应的数字
所以有:
0对应—
1对应–x
2对应-w-
3对应-wx
4对应r–
5对应r-x
6对应rw-
7对应rwx
由此可以看出,三个权限的排列组合一共有8个,于是我们可以用这些数字来表示所有权限
现在,我们有一个没有任何权限的文件123.txt
现在我们想要为它的用户加上可读权限,给它的所属组添加可读可写权限,给它的其他添加所有权限
所以现在有 chmod 467 123.txt
这便是数字法修改权限
c语言查询文件权限
我们也可以用c语言的代码来实现查询权限:
代码
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
typedef unsigned int u32;
int main(int argc, char *argv[])
{
u32 i, mask = 0700;
struct stat fileAttribute;
static char *filePermissions[] = {"---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"};
if (argc > 1)
{
if ((stat(argv[1], &fileAttribute) != -1))
{
printf("%s的权限为:", argv[1]);
for (i = 3; i; --i)
{
printf("%3s", filePermissions[(fileAttribute.st_mode & mask) >> (i - 1) * 3]);
mask >>= 3;
}
printf("\n");
}
}
else
printf("请输入需要查询的文件\n");
return 0;
}
编译指令:gcc -g filename.c -o filename
结果如图所示:
代码解析
在解析之前,需要了解C语言位运算的一些知识,详细可见C语言位运算
这里不做赘述
main函数中的参数argc和argv是什么意思可以参考这个视频
main函数中的参数
fileAttribute.st_mode —— 代表文件权限 ,如果已经理解上面修改权限的第三种方法,那么应该可以知道,其实在该属性之中权限便是以数字的形式存储。注意在该代码中使用了mask=0700,和mask>>=3操作所以每次的权限对比,都是和7在进行与运算。
我们逐行分析代码中的for循环部分:
首先看第一个循环,即i=3时。
fileAttribute.st_mode & mask具体是什么意思呢
我们以一个文件为例子,假设该文件的权限为rw- r-- -wx
那么有
fileAttribute.st_mode: | 6 | 4 | 3 |
---|---|---|---|
mask: | 7 | 0 | 0 |
如果你已经知道了位与运算的含义,那么应该清除,6和7的位与运算是先将它们转换为二进制再进行比较。
首先看6和7,就是110和111,比较后最终结果仍然是110,然后看后续的比较,可知全部为000。
比较过后的结果为110000000,然后再进行右移操作,右移六位,可以得到110。也就是6,filePermissions数组的下标为6时就是rw-。
然后mask右移3位,原来的700变为70。再继续同样的操作,直至i=0时。
c语言查看文件的其他信息
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
typedef unsigned int u32;
int main(int argc, char *argv[])
{
u32 i, mask = 0777;
struct stat fileAttribute;
static char *filePermissions[] = {"---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"};
if (argc > 1)
{
if ((stat(argv[1], &fileAttribute) != -1))
{
printf("%s的权限为:", argv[1]);
for (i = 3; i; --i)
{
printf("%3s", filePermissions[(fileAttribute.st_mode & mask) >> (i - 1) * 3]);
mask >>= 3;
}
printf("\n");
char *fileType;
if (S_ISREG(fileAttribute.st_mode)) // 判断是否为普通文件
fileType = "regular file";
else if (S_ISDIR(fileAttribute.st_mode)) // 目录文件
fileType = "directory file";
else if (S_ISFIFO(fileAttribute.st_mode)) // 管道文件
fileType = "fifo file";
else if (S_ISCHR(fileAttribute.st_mode)) // 字符设备文件
fileType = "character file";
printf("文件类型:%s\n", fileType);
printf("大小:%ld\n", fileAttribute.st_size);
printf("块:%ld\n", fileAttribute.st_blocks);
printf("块大小:%ld\n", fileAttribute.st_blksize);
printf("设备:%ld\n", fileAttribute.st_dev);
printf("inode:%ld\n", fileAttribute.st_ino);
printf("硬链接数:%ld\n", fileAttribute.st_nlink);
printf("uid:%d\n", fileAttribute.st_uid);
printf("gid:%d\n", fileAttribute.st_gid);
}
}
else
printf("请输入需要查询的文件\n");
return 0;
}
主要记住里面参数,结构体成员和相应的函数。
文件查找
文件名查找
格式:find 查找目录 -name 文件名
如图所示,在FindRoute目录下有两个txt文件和两个.c文件,现在我们在Linux目录下找到FindRoute中的123.txt,过程与结果见下图
同理,我们再找到所有.c文件,在使用*通配符时需要用\进行转义
查找以0到9开头的文件
文件大小查找
查找当前目录大于5k的文件
查找当前目录下大于5k 小于 2M的文件
文件类型查找
查找当前目录下所有普通文件
查找当前目录下所有目录文件
按照文件内容查找
先前已经在FindRoute文件夹中的2.c 和 3.c 中分别写入了222222和333333
我们在FindRoute的上级目录中按照这两个文件的内容查找
查找当前目录下包含字符串“#include”的文件
文件压缩和解压
gzip压缩gunzip解压
可见在当前目录下有两个文件夹(蓝色)一个可执行文件(绿色)以及一个普通文件(白色).
现在我们将白色文件压缩
这个红色文件就是压缩后的文件,可以看到,压缩后,原文件消失,保留原文件的压缩方式接下来再说,我们先看看如何解压该文件
通过gunzip命令解压
bzip2压缩bunzip2解压
使用zip压缩解压
如图,将myStat和permission.c文件压缩在一起,且原文件没有被删除
再将其解压缩
这里因为原文件没有被删除,所以会问一下是否替换原文件或者重命名。
文件操作缓冲区
标准输入:stdin —— 句柄 0
标准输出:stdout —— 句柄 1
标准错误:stderr —— 句柄 2
这里的句柄就是整数,不用想的太复杂。这种句柄通过VFS可以找到对应的文件,指向以上的几种操作。
什么是缓冲区
计算机在执行输入输出的过程中,通常是先把数据存在内存中,然后再直接从内存中取数据,这样减少了对硬盘的直接操作,提升了计算机处理速率。
做个比喻: 现在有生产商和购买者。购买者到生产商那里去买商品,但是购买者的购买速度远大于生产商的生产速度,这样就会出现供不应求的现象。其实在这里可以看出,生产商对应计算机中的低速设备(硬盘之类的),购买商对应计算机中的高速设备(CPU之类的),而高速设备与低速设备之间的不匹配会造成计算机性能无法被完全释放,所以仓库(内存)应运而生,购买商在买不到商品时可以先去买其他商品,然后生产商赶紧生产放在仓库中,然后等下次购买商来到时快速购买。
这个比喻如果有些不合适的地方,希望知道的人可以帮忙指出。但是缓冲区的大致意思就是在输入输出设备之间,用来存储数据,使得高速设备和低速设备之间可以充分配合,发挥出最佳性能。
三种缓冲区
缓冲区有三个典型:
1、全缓冲
典型代表:对磁盘文件的读写
2、行缓冲
典型代表:标准输入和标准输出
3、无缓冲
典型代表:标准错误
c语言设置缓冲区
代码
代码逻辑比较简单,且注释中已经有解释,因此不做赘述
#include <stdio.h>
#include <string.h>
#include <errno.h> //标准错误相关函数
// setbuf(FILE *stream, char* buf); 写入到参数1的输出在参数2的缓冲区中,当参数2的缓冲区满或fflush时,参数2的内容才会写入参数1中
// setvbuf(FILE *stream, char* buf, int mode, size_t size); mode指代三种缓冲区,size指代缓冲区大小
int main(int argc, char *argv[])
{
FILE *pf;
char msg1[] = "hello world\n";
char msg2[] = "hello\nworld";
char buff[128];
//==============================================================================================================
memset(buff, 0, sizeof(char) * 128); // 清空缓冲区buff
if ((pf = fopen("no_buf1.txt", "w")) == NULL)
{
perror("file open failure!\n");
return 1;
}
setbuf(pf, NULL); // 设置无缓冲
fwrite(msg1, 8, 1, pf); // 将msg1的内容写入pf中,写8个字符,写1次
printf("test setbuff(no buff)! check no_buf1.txt\n");
printf("now buff data is : %s \n", buff);
printf("enter the enter to continue!\n");
getchar();
fclose(pf);
//==============================================================================================================
memset(buff, 0, sizeof(char) * 128); // 清空缓冲区buff
if ((pf = fopen("no_buf2.txt", "w")) == NULL)
{
perror("file open failure!\n");
return 1;
}
setvbuf(pf, NULL, _IONBF, 0); // 1: 期望缓冲区的文件指针。2:缓冲区的地址。3、缓冲区的类型。4、缓冲区大小
fwrite(msg1, 8, 1, pf); // 将msg1的内容写入pf中,写8个字符,写1次
printf("test setbuff(no buff)! check no_buf2.txt\n");
printf("now buff data is : %s \n", buff);
printf("enter the enter to continue!\n");
getchar();
fclose(pf);
//===============================================================================================================
memset(buff, 0, sizeof(char) * 128); // 清空缓冲区buff
if ((pf = fopen("l_buf.txt", "w")) == NULL)
{
perror("file open failure!\n");
return 1;
}
setvbuf(pf, buff, _IOLBF, 128); // 1: 期望缓冲区的文件指针。2:缓冲区的地址。3、缓冲区的类型。4、缓冲区大小
fwrite(msg2, sizeof(msg2), 1, pf); // 将msg1的内容写入pf中,写8个字符,写1次
printf("test setbuff(line buff)! check l_buf.txt\n");
printf("now buff data is : %s \n", buff);
printf("enter the enter to continue!\n");
getchar();
fclose(pf);
//===============================================================================================================
memset(buff, 0, sizeof(char) * 128); // 清空缓冲区buff
if ((pf = fopen("f_buf.txt", "w")) == NULL)
{
perror("file open failure!\n");
return 1;
}
setvbuf(pf, buff, _IOFBF, 128); // 1: 期望缓冲区的文件指针。2:缓冲区的地址。3、缓冲区的类型。4、缓冲区大小
fwrite(msg2, sizeof(msg2), 1, pf); // 将msg1的内容写入pf中,写8个字符,写1次
printf("test setbuff(full buff)! check f_buf.txt\n");
printf("now buff data is : %s \n", buff);
printf("enter the enter to continue!\n");
getchar();
fclose(pf);
//===============================================================================================================
return 0;
}
运行结果分析
我们先一步一步来看
打开两个终端,一个运行代码,一个查看创建的
因为没有缓冲区,所以输出的内容直接写入到no_buf1.txt中
**注意:**这里的world没有显示完全,是因为之前设置中只设置写入8个字符。
第二次运行结果和第一次一样,因为都没有设置缓冲区
第三次运行结果和第一次不再一样,只输出了一个hello
在分析原因之前我们先看看msg2的内容:
char msg2[] = "hello\nworld";
我们可以看出hello后面就是一个换行符\n,而行换行符刷新缓冲区,输出到设备中的条件就是换行,所以一开始hello都一直在缓冲区中,直到遇到换行符时,缓冲区刷新,输出到l_buf.txt中。
因此,当我们再次运行,程序执行到fclose时,缓冲区再次刷新,此时helloworld应该全部输出,我们再摁下enter时,可以看到:
果然,helloworld全部写入l_buf.txt中
但是我们再查看f_buf.txt中的内容
可以发现内容为空,这是因为第四步我们设置的是全缓冲区,不手动刷新缓冲区,它就一直只存在于缓冲区内,不会写入到f_buf文件中。我们再次摁下enter键时,代码执行到fclose函数,缓冲区刷新,此时缓冲区中的内容已经完全写入f_buf中:
ps: 除了fclose,可以使用fflush函数刷新缓冲区。
通过这段代码,可以对三种缓冲区有一个大致的了解
本人也只是初学,如果有些地方有问题,欢迎各位指出