linux上进行文件操作(代码片段)

mChenys mChenys     2022-12-02     498

关键词:

目录

一、系统api与库函数的关系

linux系统默认启动程序时会打开stdin/stdout/stderr ,我们可以直接使用通过<unistd.h>库中的close方法可以指定关闭上面的标准输入/输出和错误, 例如close(1)表示关闭标准输出.

通过open命令可以打开一个输出,此时如果标准输出关闭了,那么open命令可以替换标准输出.
通过<stdio.h>库中的fflush方法可以刷新标准输入和输出的缓存区buffer,例如 fflush(stdout);

二、open和close函数介绍

通过man 2 open/close可以查看open/close函数的用法,或者在编写代码的时候,在命令模式下光标移动到函数名上,按下2(表示第2章) 再按下K 就可以跳到这个函数的介绍了.

2.1 open方法介绍


open方法表示打开一个文件描述符,其中pathname是文件名

flags的必选项(必选项只能选其中一个):

  • O_RDONLY :read-only
  • O_WRONLY:write-only
  • O_RDWR:read/write

flags的可选项有:

  • O_APPEND(追加)
  • O_CREAT(若文件不存在则创建)
  • O_EXCL(必须和O_CREAT一起使用,若文件存在的报错)
  • O_NONBLOCK(非阻塞)

mode:表示权限位,最终是(mode & ~umask)

可选项是可以和必选项结合使用的

返回值:是返回最小的可用的的文件描述符,假设存在stdin/stdout/stderr都存在,那么open返回最小是3.因为0,1,2被占用了.失败时返回-1.

案例-通过open简单实现一个touch命令的功能

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>

int main(int argc,char* args[])

	if(argc !=2)
	
		printf("./touch filename\\n");
		return -1;
	
	int fd = open(args[1],O_RDONLY|O_CREAT,0666);// 0666是mode,后面会介绍
	close(fd);
	return 0;

2.2 close方法介绍


close表示关闭一个文件描述符.返回值,成功返回0,失败返回-1.

三、open/create函数创建文件时的权限设置

首先了解一下 umask 命令,该命令用来设置限制新文件权限的掩码。当新文件被创建时,其最初的权限由文件创建掩码决定。简单地来说,umask和open()及creat()函数的权限码(mode_t mode参数)共同决定你的新建文件的权限。具体关系为mode & ~umask。

下面通过简单的程序来验证它们之间的关系。
由于open()和creat()创建文件,结果一致,我们直接采用creat()函数:

#include<stdio.h>
#include<fcntl.h>
#include<sys/stat.h>

int main()

	if(creat("1.log",0777)<0)
	
		printf("创建文件失败!\\n");
	else
	
		printf("创建文件成功!\\n");
	
	return 0;

编译执行后,查看生成文件1.log的权限

权限是775,这和我们的期望777不符,为什么呢? 这是因为creat和open创建文件时的权限是 mode & ~umask的结果, mode虽然是0777,但是普通用户的umask是0002,如下所示:

所以~umask (umask取反,也就是0002取反)的结果是7775, 套公式mode & ~umask 也就是0777 & 7775 的结果是0775, 也就是1.log文件的最终权限是rwx rwx r-x

那么如何避免umask的影响呢?
我们可以使用umask函数,将值设置为0000,那么~umask的值就变成7777了, 由于7的二进制是111, 不会影响&操作的结果. 将上面的代码修改如下:

#include<stdio.h>
#include<fcntl.h>
#include<sys/stat.h>

int main()

	umask(0000);//将umask的值临时修改为0000,这个不会修改系统的umask默认值.

	if(creat("1.log",0777)<0) //临时修改了umask值后,设置的最终权限结果就是0777
	
		printf("创建文件失败!\\n");
	else
	
		printf("创建文件成功!\\n");
	
	return 0;

重新编译执行,查看1.log的权限就变成了777了

当然如果只是创建普通文件的话,没必要添加执行权限,一般就是666权限就可以了.也就是rw-rw-rw-

umask的值表示的权限是u/g/o所"不具备"的权限,它是Linux的默认权限,root用户是0022,表示root用户创建文件或者文件夹时,g(所属组)和o(其他组)将不具备w权限,普通用户是0002,表示普通用户创建目录或者文件时,o(其他组)将不具备w权限.
所以,有了umask的限制之后,我们通过库函数设置的权限就可以符合Linux下使用命令创建文件的权限效果一样.例如普通用户在Linux上使用touch命令创建的文件的权限就是664, 那么我们使用库函数创建文件就可以将mode设置为0666,这样0666与7775的结果就是0664 ,6和5的二进制&的结果就是4.
当然,如果你觉得计算太麻烦, 那么我们其实根据umask规则也可以知道,普通用户的o是没有w权限,所以mode设置为0666的最后结果就是0664 (0666-0002)
除非你需要明确添加w权限,那么你可以通过umask函数临时去掉限制.

四、read和write函数的介绍和使用

4.1 read函数介绍

read函数需要导入这些头文件

#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

函数原型如下

fd:文件描述符
buf:缓冲区
count:缓冲区大小
ssize_t:返回值,返回读取的实际字节长度. -1表示失败, 0 表示读到文件末尾

4.2 write函数介绍


fd:文件描述符
buf:缓冲区
count:缓冲区大小,注意字符串不需要考虑’\\0’所占的1个字节,例如"abc"字符串,对应的count就是3
ssize_t:返回值,成功返回写入的实际字节数, -1表示失败, 0表示未写入.

注意:write完后,指针的标记是停留在最后一个字节的,此时如果直接用read来读是没法读取内容的.

4.3 如何使用

例如实现一个cat命令的功能

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main(int argc,char* args[])

	if(argc !=2)
	
		printf("usage: ./cat filename");
		return -1;
	
	int fd = open(args[1],O_RDONLY);
	
	char buf[1024] = 0;
	int len = -1;
	while((len = read(fd,buf,sizeof(buf)))!=0)
	
		//将读取的内容输出到屏幕
		write(STDOUT_FILENO,buf,len); //其中STDOUT_FILENO是stdout的宏定义,值是1,表示标准输出
	

	close(fd);

	return 0;

编译后,执行效果如下:

五、lseek函数的介绍和使用

5.1 介绍

作用类似fseek函数,用于设置当前文件读或者写的位置,参数介绍如下:

fd:文件描述符
offset:偏移量,通常是0,结合whence来使用,用来定位到文件的开头,文件的当前和文件的结束位置.
whence: 表示从什么位置开始设置,有3个值,

  • SEEK_SET(文件开头位置)
  • SEEK_CUR(文件当前读写的位置)
  • SEEK_END(文件结束位置)

off_t :成功返回当前位置到开始位置的长度(字节数),失败返回-1.

5.2 如何使用

如果要实现open() … write()… read()…close() 方式来操作同一个文件的话,那么write()和read()之间就需要用到lseek()了.因为write()完后,标记位置的指针已经指向文件末尾了,此时需要用lseek()重置到开头位置,然后read()才能读到内容.

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main(int argc, char* args[])

	int fd = open("./test2.txt",O_RDWR|O_CREAT,0666); //打开./test2.txt,若不存在则创建
	//先写,输出11个字节,包括了\\n
	write(fd,"helloworld\\n",11);
	
	//重置到开始位置
	lseek(fd,0,SEEK_SET);

	//后读
	char buf[1024] = 0;
	int len = read(fd,buf,sizeof(buf));
	if(len)
		
		//显示到屏幕 fd=STDOUT_FILENO
		write(STDOUT_FILENO,buf,len);
	
     //最后关闭
	close(fd);

	return 0;


另外使用lseek还能用来获取文件的大小, 因为lseek的返回值就是从文件开头到lseek定位的位置之间的长度(字节数), 假设直接调用lseek移动到文件末尾,那么返回的就是文件的大小了.
例如:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>

int main(int argc,char* args[])

	if(argc != 2)
	
		printf("Usage: ./len filename");
		return -1;
	

	int fd = open(args[1],O_RDONLY);
	int len = lseek(fd,0,SEEK_END);
	printf("file len is: %d\\n",len);
	close(fd);

编译执行,查看文件的大小,刚好和ll命令查看的一样

六、阻塞和非阻塞相关概念

当使用read函数读设备或者管道或者网络的时候就会出现阻塞现象.
下面通过输入输出设备来模拟, 对应的位置是/dev/tty

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>

int main()

	char buf[1024] = 0;
	int len = 0;
	int fd = open("/dev/tty",O_RDWR); // 设置读写来源于设备的输入输出
	while(1)
	
		int len = read(fd,buf,sizeof buf); //由于读取的是设备,所以read会阻塞在此,直到用户输入内容
		if(len)
		
			printf("buf is : %s\\n",buf);
		

		printf("haha\\n");//这句话虽然在while true里面,但是由于read阻塞了,所以并不会一直输出
	
	close(fd);
	return 0;


从结果可以看出,输入内容后,read才会走下一步,然后再次循环又停在了read函数处等等用户继续输入,这种现象就是阻塞

如果不想被阻塞该怎么处理?
只需要将read的flag新增一个O_NONBLOCK即可,如下所示:

int fd = open("/dev/tty",O_RDWR|O_NONBLOCK);

或者使用<fcntl.h>中的fcntl函数.

6.1 fcnl函数


cmd参数可选有:

  • F_GETFL ,如果用这个那么可以省略第3个参数
  • F_SETFL ,如果用这个那么第三个参数必须是int类型

用法如下:

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>

int main()

	char buf[1024] =0;
	int len = 0;
	int fd = open("/dev/tty",O_RDONLY);

	//1.先获取标记
	int flags = fcntl(fd,F_GETFL);
	//2.添加非阻塞标记
	flags |= O_NONBLOCK;
	//3.重新设置标记
	fcntl(fd,F_SETFL,flags);

	while(1)
	
		int len = read(fd,buf,sizeof buf);
		if(len)
		
			printf("buf is:%s\\n",buf);
		
		printf("haha\\n");
	

	close(fd);
	return 0;

七、Linux最大文件打开数

在Linux下有时会遇到Socket/File : Can’t open so many files的问题。其实Linux是有文件句柄限制的,而且Linux默认一般都是1024(阿里云主机默认是65535)。在生产环境中很容易到达这个值,因此这里就会成为系统的瓶颈。

使用ulimit -a 或者 ulimit -n

open files (-n) 1024 是linux操作系统对一个进程打开的文件句柄(文件描述符表)数量的限制(也包含打开的套接字数量。

7.1 修改单个进程的最大文件句柄

通过下面的命令可以

ulimit -SHn 10000

其实ulimit 命令身是分软限制和硬限制,加-H就是硬限制,加-S就是软限制。默认显示的是软限制,如果运行ulimit 命令修改时没有加上-H或-S,就是两个参数一起改变。

软限制和硬限制的区别?

硬限制就是实际的限制,而软限制是警告限制,它只会给出警告。

要想ulimits 的数值永久生效,必须修改配置文件/etc/security/limits.conf
在该配置文件中添加

* soft nofile 65535   
* hard nofile 65535  

echo "* soft nofile 65535"  >> /etc/security/limits.conf

echo "* hard nofile 65535"  >> /etc/security/limits.conf

其中 * 表示所用的用户

7.2 修改系统所有进程的文件局柄

上面的修改只是对一个进程打开的文件句柄数量的限制,我们还需要设置系统的总限制才可以。

假如,我们设置进程打开的文件句柄数是1024 ,但是系统总线制才500,所以所有进程最多能打开文件句柄数量500。从这里我们可以看出只设置进程的打开文件句柄的数量是不行的。所以需要修改系统的总限制才可以。

echo 6553560 > /proc/sys/fs/file-max

上面是临时生效方法,重启机器后会失效;

永久生效方法:

修改 /etc/sysctl.conf, 加入

fs.file-max = 6553560重启生效

通过下面代码可以验证这个文件句柄的限制

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

int main()

	int num = 0;
	char filename[128] = 0;
	while(1)
	
		sprintf(filename,"temp_%04d",num++); //%04表示不够4位用0补齐.
		if(open(filename,O_RDONLY|O_CREAT,0666)<0)
		
			perror("open error");
			break;
		
	
	return 0;


如下图所示一共生成了temp_0000~temp_1020个文件,也就是1021个,为啥不是1024呢,因为文件描述符表中0 ,1 ,2 默认打开程序的时候就被stdin/stdout/stderr占用了. 所以加上这3个刚好就是1024个.

八、stat函数介绍和使用


这个函数用于获取文件的信息,主要指文件的属性,在C基础(七)文件操作也有介绍过

参数:

  • pathname:文件名
  • statbuf:传出参数,需要外部定义一个struct stat类型的变量,然后传入地址.

返回值:
成功返回0,失败返回-1

stat结构体的成员信息如下:

如何使用?

#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>

int main(int argc,char* args[])

	if(argc !=2)
	
		printf("Usage: ./stat filename\\n");
		return -1;
	

	struct stat st; //定义一个结构体
	
	//调用stat函数,参数1是文件名,参数2是上面定义的结构体的地址,函数内部会将获取的信息封装到st变量中
	stat(args[1],&st); 

	return 0;


编译源文件

gcc -o stat statUsage.c -g

然后通过gdb调用启动程序查看statUsage.c文件的stat的内容.

对应的内容就是stat命令中的内容:

8.1 通过stat函数实现ll命令的功能

首先需要介绍如何获取用户名,通过getpwuid函数实现

需要传递uid参数,用户id,返回的是一个passwd的结构体指针

然后是获取组信息,通过getgrgid函数来实现

需要传递gid参数,组id,返回的是一个group的结构体指针

然后通过localtime函数可以查看本地时间

返回的是tm结构体

接下来就是要实现下图效果的编码了

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<time.h>
#include<pwd.h>
#include<grp.h>

int main(int argc ,char * args[])


	if(argc !=2)
	
		printf("Usage: ./ll filename");
	

	struct stat st;
	stat(args[1],&st);

	char stmode[11] = 0;
	memset(stmode,'-',10);

	//文件类型
	if(S_ISREG(st.st_mode)) stmode[0]='-';//普通文件
	if(S_ISDIR(st.st_mode)) stmode[0]='d';//文件夹
	if(S_ISCHR(st.st_mode)) stmode[0]='c';//字符设备文件
	if(S_ISBLK(st.st_mode)) stmode[0]='b';//块设备文件
	if(S_ISFIFO(st.st_mode))stmode[0]='p';//管道文件
	if(S_ISLNK(st.st_mode))stmode[0]='l';//软连接文件
	if(S_ISSOCK(st.st_mode))stmode[0]='s';//socket文件

	//权限描述 user
	if(st.st_mode & S_IRUSR) stmode[1] = 'r';
	if(st.st_mode & S_IWUSR) stmode[2] = 'w';
	if(st.st_mode & S_IXUSR) stmode[3] = 'x';
	//权限描述符 group
	if(st.st_mode & S_IRGRP) stmode[4] = 'r';
	if(st.st_mode & S_IWGRP) stmode[5] = 'w';
	if(st.st_mode & S_IXGRP) stmode[6] = 'x';
	//权限描述符 other
	if(st.st_mode & S_IROTH) stmode[7] = 'r';
	if(st.st_mode & S_IWOTH查看详情  

linuxmac日常入门命令行使用——文件以及文件夹操作(代码片段)

linux\\mac日常入门命令行使用——文件以及文件夹操作对于没有接触过命令行的人,对于命令行还是非常恐惧的。我能够理解,但是我个人认为,大可不必。如果不是要深入研究shell,否则,日常使用其实一点... 查看详情

linux分配新的账号(代码片段)

为了让其他人在服务器上进行工作,同时不破坏原有的文件,我们通常会分配一个新的账号来用。流程如下:useraddusername-m(-m相当于会在home目录下自动创建对应的用户目录)passwdusername(为新用户设置密码)usermod-s/bin/bashu... 查看详情

第四章python的文件操作(代码片段)

...再存储(write方法执行时)。所以open命令也是在2进制的基础上进行存储的。4.1文件基本操作obj=open(file='路径',mode='模式',encoding='编码')obj.write()#可写模式下ob 查看详情

c++笔记--linux编程-linux文件和输入输出文件和目录操作(代码片段)

目录Linux文件概念Linux系统上的文件部分类型说明文件描述符使用文件描述符打开和关闭文件描述符示例读写文件描述符read函数例子write函数例子使用fstat获取文件信息文件和目录操作库函数和系统调用打开和关闭文件mode说明读... 查看详情

markdown如何在语义上进行标记(代码片段)

查看详情

在同一个hive表上进行多次压缩(代码片段)

我有一个按年/月分区的Hive表,它包含至少7年的数据。我想要做的是通过Snappy压缩最新的数据(比如1到1岁),但通过更好的压缩技术(如gzip等)来压缩旧数据。如何在Hive中执行此操作?答案您可以使用不同的压缩设置覆盖不... 查看详情

text在真实设备上进行离子调试(代码片段)

查看详情

scoop-在windows命令行上进行程序安装(代码片段)

2019-01-28 22:49:21资料来源自Scoop官方网站以及github上的帮助文档如果有疑惑或者觉得文章有错误请留言以帮助改正 Scoop--Acommand-lineinstallerforWindows是开发人员用来安装程序用的,使用命令行进行操作。安装前提(所需要的环... 查看详情

docker基本介绍和操作(代码片段)

...于2013年3月以Apache2.0授权协议开源,主要项目代码在GitHub上进行维护。Docker使用Google公司推出的Go语言进行开发实现,基于Linux内核的cgrou 查看详情

rust交叉编译树莓派程序(代码片段)

...树莓派程序使用rust写树莓派程序时,如果直接在树莓派上进行编译,速度非常慢,如果是zero那更加是慢到受不了。因此最好是能通过开发机编译完后,直接放到树莓派上运行。由于开发机上的cpu架构、操作系统和目标机不同,... 查看详情

linux下安装代码统计工具git_stats(代码片段)

...、提交次数、修改文件数、代码行数、注释量在时间维度上进行统计,亦可按各文件类型进行简单的统计,非常方便.虽然以代码行数来衡量项目或者程序员并不是一件靠谱的事,但是从统计角度看趋势对于技术管理人员还是很... 查看详情

linux统计文件行数(代码片段)

统计install.log文件的行数的命令:wc-linstall.log或者catinstall.log|wc-l 查看详情

php在wordpress上进行php调试的少数功能(代码片段)

查看详情

操作文件和目录(代码片段)

 当操作文件、目录时,可在命令行下面输入操作系统提供的各种命令来完成,如dir、cp等其实操作系统提供的命令只是简单地调用了操作系统提供的接口函数,Python内置的os模块也可以直接调用操作系统提供的接口函数 ... 查看详情

iptables教程(代码片段)

...理解其含义了。本文环境:PC:Ubuntu18iptables操作均在docker上进行。docker上的系统为centos6.9。更新时间:2020.6.26工作原理基本组成想要掌 查看详情

sh在ubuntu14.04上进行rtorrent安装/更新(代码片段)

查看详情

sh在ubuntu14.04上进行rtorrent安装/更新(代码片段)

查看详情