Linux上进行文件操作
Posted mChenys
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux上进行文件操作相关的知识,希望对你有一定的参考价值。
目录
- 一、系统api与库函数的关系
- 二、open和close函数介绍
- 三、open/create函数创建文件时的权限设置
- 四、read和write函数的介绍和使用
- 五、lseek函数的介绍和使用
- 六、阻塞和非阻塞相关概念
- 七、Linux最大文件打开数
- 八、stat函数介绍和使用
- 九、access和truncate函数使用
- 十、链接函数的使用
- 十一、chown和rename函数的使用
- 十二、 chdir和getcwd函数的使用
- 十三、mkdir函数的使用
- 十四、rmdir/opendir/readdir/closedir函数的使用
- 十五、errno说明
- 十六、dup/dup2函数的使用
一、系统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),就像随机写入一样