Linux上进行文件操作

Posted mChenys

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux上进行文件操作相关的知识,希望对你有一定的参考价值。

目录

一、系统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_IWOTHLinux:Linux 常用命令讲解

linux 2.6.43,ext3,10K RPM SAS 磁盘,在不同文件上进行 2 次顺序写入(直接 io),就像随机写入一样

linux挂载windows共享的文件夹

Python文件操作:同一个文件进行内容替换

linux怎么在代码里将每次的操作结果写入到文件中?

linux 内核源码包 .config 啥用处