linux入门到精通-第九章-文件操作

参考

视频教程

文件描述符

在Linux 的世界里,一切设备皆文件。我们可以系统调用中 I/O 的函数 (I: input,输入;O: output,输出),对文件进行相应的操作 ( open()、close()、write()、read()等)。

打开现存文件或新建文件时,系统(内核)会返回一个文件描述符,文件描述符用来指定已打开的文件。这个文件描述符相当于这个已打开文件的标号,文件描述符是非负整数,是文件的标识,操作这个文件描述符相当于操作这个描述符所指定的文件。

程序运行起来后(每个进程)都有一张文件描述符的表,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符 0、1、2 记录在表中。程序运行起来后这三个文件描述符是默认打开的。

// 标准输入的文件描述名1
#define STDIN_FILENO 0 
// 标准输出的文件描述符
#define STDOUT_FILENO 1 
// 标准错误的文件描述符
#define STDERR_FILENO 2 

在程序运行起来后打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中。

在这里插入图片描述

最大打开的文件个数
Linux中一个进程最多只能打开 NR_OPEN_DEFAULT (即1024)个文件,故当文件不再使用时应及时调用close() 函数关闭文件。

  • 查看当前系统允许打开最大文件个数
cat /proc/sys/fs/file-max
  • 当前默认设置最大打开文件个数1024
ulimit -a
  • 修改默认设置最大打开文件个数为4096
ulimit -n 4096

常用文件IO函数

open函数

#include <sys/types .h>

#include <sys/stat.h>
#include <fcnt1.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags , int mode_t mode) ;

功能:
	打开文件,如果文件不存在则可以选择创建
参数:
	pathname: 文件的路径及文件名
	flags: 打开文件的行为标志,必选项 O_RDONLY,O_WRONLY,O_RDWR
	mode:这个参数,只有在文件不存在时有效,指新建文件时指定文件的权限
返回值:
	成功:成功返回打开的文件描述符
	失败: -1

flags详细说明
必选项:

取值含义
O_RDONLY以只读的方式打开
O_WRONLY以只写的方式打开
O_RDWR以可读、可写的方式打开

可选项,和必选项按位或起来

取值含义
O_CREAT文件不存在则创建文件,使用此选项时需使用mode说明文件的权限
O_EXCL如果同时指定了O_CREAT,且文件已经存在,则出错
O_TRUNC如果文件存在,则清空文件内容
O_APPEND写文件时,数据添加到文件末尾
O_NONBLOCK对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O

mode补充说明

  • 1)、文件最终权限: mode & ~umask
  • 2)、shell进程的umask掩码可以用umask命令查看
    umask:查看掩码 (补码)
    umask mode:设置掩码,mode为八进制数
    umask -S:查看各组用户的默认操作权限
取值八进制含义
S_IRWXU00700文件所有者的读、写、可执行权限
S_IRUSR00400文件所有者的读权限
S_IWUSR00200文件所有者的写权限
S_IXUSR00100文件所有者的可执行权限
S_IRWXG00070文件所有者同组用户的读、写、可执行权限
S_IRGRP00040文件所有者同组用户的读权限
S_IWGRP00020文件所有者同组用户的写权限
S_IXGRP00010文件所有者同组用户的可执行权限
S_IRWXO00007其他组用户的读、写、可执行权限
S_IROTH00004其他组用户的读权限
S_IWOTH00002其他组用户的写权限
S_IXOTH00001其他组用户的可执行权限

示例:


close函数

#include <unistd.h>

int close(int fd);
功能:
	关闭已打开的文件
参数:
	fd : 文件描述符,open()的返回值
返回值:
	成功:0
	失败: -1,并设置errno

需要说明的是,当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。

但是对于一个长年累月运行的程序(比如网络服务器),打开的文件描述符一定要记得关闭,否则随着打开的文件越来越多,会占用大量文件描述符和系统资源。

write函数

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:
	把指定数目的数据写到文件 (fd)
参数:
	文件描述符fd :
	buf数据首地址
	count :写入数据的长度 (字节)
返回值:
	成功:实际写入数据的字节个数
	失败: - 1

示例:write_demo.c

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

int main()
{
    int fd = -1;
    int ret = -1;
    
    // 1. 以写的方式打开文件,如果文件存在就报错,如果不存在则新建
    fd = open("test.txt", O_WRONLY | O_CREAT  , 0644);
    if(-1 == fd){
        perror("open");
        return 1;
    }
    printf("fd == %d \n", fd);

    // 2.写文件
    char* str = "hello world";
    ret = write(fd, str, strlen(str));
    if(-1 == ret){
        perror("write");
        return 1;
    }
    printf("write len =%d \n", ret);

    // 3.close 文件
    close(fd);

    return 0;

}

read函数

#include <unistd.h>
ssize_t read(int fd, void *buf,size_t count);
功能:
	把指定数目的数据读到内存(缓冲区)
参数
	fd:文件描述符
	buf :内存首地址
	count :读取的字节个数
返回值
	成功:实际读取到的字节个数
	失败: - 1

// 从控制台输入
char buf[SIZE];
memset(buf, 0, SIZE);
// 从控制台输入
ret = read(TDIN)FILENO, buf, SIZE);

示例

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

#define SIZE 1024

int main()
{
    int fd = -1;
    int ret = -1;
    
    // 1. 以只读的方式打开文件
    fd = open("test.txt", O_RDONLY );
    if(-1 == fd){
        perror("open");
        return 1;
    }
    printf("fd == %d \n", fd);

    // 2.读取文件
    char buf[SIZE];
    memset(buf, 0, SIZE);
    // 从文件描述符中最多读取SIZE个字节保存到buf中,实际读取字节数通过返回值返回
    ret = read(fd, buf, SIZE);
    if(-1 == ret){
        perror("read");
        return 1;
    }
    printf("read len %d, content: %s \n", ret, buf);

    // 3.close 文件
    close(fd);

    return 0;

}

lseek函数

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset,int whence);
功能:
	改变文件的偏移量
参数:
	fd:文件描述符
	offset: 根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。
	whence: 其取值如下
		SEEK_SET:从文件开头移动offset个字节
		SEEK_CUR:从当前位置移动offset个字节
		SEEK_END:从文件未尾移动offset个字节
返回值:1seek成功执行,则返回新的偏移量
	如果失败,返回-1

所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。

读写操作通常开始于 cfo,并且使 cfo 增大,增量为读写的字节数。文件被打开时,cfo 会被初始化为 0,除非使用了O_APPEND。

示例 lseek.c

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

#define SIZE 1024

int main()
{
    int fd = -1;
    int ret = -1;
    
    // 1. 以写的方式打开文件,如果文件存在就报错,如果不存在则新建
    fd = open("test.txt", O_RDWR | O_CREAT  , 0644);
    if(-1 == fd){
        perror("open");
        return 1;
    }
    printf("fd == %d \n", fd);

    // 2.写文件
    char* str = "hello world";
    ret = write(fd, str, strlen(str));
    if(-1 == ret){
        perror("write");
        return 1;
    }
    printf("write len =%d \n", ret);

    // 3.lseek操作,从文件开头偏移32
    ret = lseek(fd, 32, SEEK_SET);
     if(-1 == ret){
        perror("lseek");
        return 1;
    }
    write(fd, "1234567890", 10);

    // 读取
    char buf[SIZE];
    memset(buf, 0, SIZE);
    // 重置文件偏移量
    lseek(fd, 0, SEEK_SET);
    ret = read(fd, buf, SIZE);
    if(-1 == ret){
        perror("read");
        return 1;
    }
    printf("read len %d, content: %s \n", ret, buf);

    // 4.close 文件
    close(fd);

    return 0;

}

写进去的文件内容为
在这里插入图片描述
执行结果,虽然读取到42个字符,但事实因为遇到0就认为字符串结束,所以只打印了前面几个字符

root@sony-HP-Notebook:/usr/local/cpp_demo/file# ./a.out 
fd == 3 
write len =11 
read len 42, content: hello world 

stat函数

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
int 1stat(const char *pathname, struct stat *buf);
功能:
	获取文件状态信息
	stat和1stat的区别:当文件是一个符号链接时,1stat返回的是该符号链接本身的信息;而stat返回的是该链接指向的文件的信息
参数:
	path: 文件名
	buf: 保存文件信息的结构体
返回值:
	成功: 0
	失败: -1

stat结构体

struct stat {
	dev_t st_dev; //文件的设备编号
	ino_t st_ino; //节点
	mode_t st_mode; //文件的类型和存取的权限
	nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
	uid_t st_uid;  //用户ID
	gid_t st_gid; //组ID
	dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
	off_t st_size; //文件字节数(文件大小)
	b1ksize_t st_b1ksize; //块大小(文件系统的I/ 缓冲区大小)
	bIkcnt_t st_blocks; //块数
	time_t st_atime; //最后一次访问时间
	time_t st_mtime;//最后一次修改时间
	time_t st_ctime;//最后一次改变时间(指属性)
}

输出文件信息

示例:stat_demo.c,可以通过stat test.txt命令对比两个结果

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

int main(void)
{
    int ret = -1;
    struct stat s;
    // 获取指定文件的信息
    ret = stat("test.txt",&s);
    if (-1 == ret)
    {
        perror("stat");
        return 1;
    }
    // 文件属性信息
    // 设备编码
    printf("st_dev; %lu\n", s.st_dev);
    // 节点
    printf("st_ino: %ld\n",s.st_ino);
    // 硬链接数量
    printf("st_nlink: %lu\n",s.st_nlink);
    // 用户id
    printf("st_uid;%d\n",s.st_uid);
    // 组户id
    printf("st_gid;%d\n",s.st_gid);
    // 文件大小
    printf("st_size: %ld\n",s.st_size);
    return 0;
}

获取文件类型

获取文件类型 stat_demo2.c

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

//显示文件类型的第一个版本
int show_file_type(struct stat *s)
{
    switch(s->st_mode & __S_IFMT)
    {
        //普通文件
        case __S_IFREG:
            printf("该文件是普通文件\n");
            break;
        //目录
        case __S_IFDIR:
            printf("该文件是目录\n");
            break;
        //字符设备
        case __S_IFCHR:
            printf("该文件是字符设备\n");
            break;
        //块设备
        case __S_IFBLK:
            printf("该文件是决设备\n");
            break;
        //套接字
        case __S_IFSOCK:
            printf("该文件是套接字\n");
            break;
        //管道
        case __S_IFIFO:
            printf("该文件是管道\n");
            break;
        //符号链接
        case __S_IFLNK:
            printf("该文件是符号链接\n");
            break;
        default:
            printf("未知类型\n");
            break;

    }
    return 0;

}


//显示文件类型的第二个版本
int show_file_type_v2(struct stat *s)
{
    if(S_ISREG(s->st_mode))
    {
        printf("该文件是普通文件\n");
    }else if(S_ISDIR(s->st_mode))
    {
        printf("该文件是目录\n");
    }else if(S_ISCHR(s->st_mode))
    {
        printf("该文件是字符设备\n");
    }else if(S_ISBLK(s->st_mode))
    {
        printf("该文件是决设备\n");
    }else if(S_ISSOCK(s->st_mode))
    {
        printf("该文件是套接字\n");
    }else if(S_ISLNK(s->st_mode))
    {
        printf("该文件是符号链接\n");
    }else{
        printf("未知类型\n");
    }
    return 0;

}

int main(int argc, char **argv)
{
    int ret = -1;
    struct stat s;
    // 容错判断
    if(2 != argc )
    {
        printf("usage: error args\n");
        return 1;
    }
    // 获取指定文件的信息
    ret = stat(argv[1],&s);
    if (-1 == ret)
    {
        perror("stat");
        return 1;
    }
    // 显示文件类型
    show_file_type(&s);
    show_file_type_v2(&s);
    
    return 0;
}

运行

root@sony-HP-Notebook:/usr/local/cpp_demo/file# ./a.out /home
该文件是目录
root@sony-HP-Notebook:/usr/local/cpp_demo/file# ./a.out test.txt
该文件是普通文件
root@sony-HP-Notebook:/usr/local/cpp_demo/file# ./a.out /dev/input/mouse0 
该文件是字符设备
root@sony-HP-Notebook:/usr/local/cpp_demo/file# ./a.out /dev/sda1
该文件是决设备

获取文件权限

在这里插入图片描述

//显示文件权限
int show_file_permission(struct stat *s)
{
    // 判断文件所属者读权限
    if(s->st_mode & S_IRUSR )
    {
        printf("r");
    }else{
        printf("-");
    }
    // 判断文件所属者写权限
    if(s->st_mode & S_IWUSR )
    {
        printf("w");
    }else{
        printf("-");
    }
    // 判断文件所属者执行权限
    if(s->st_mode & S_IXUSR )
    {
        printf("x");
    }else{
        printf("-");
    }

    // 判断文件所属组读权限
    if(s->st_mode & S_IRGRP )
    {
        printf("r");
    }else{
        printf("-");
    }
    // 判断文件所属组写权限
    if(s->st_mode & S_IWGRP )
    {
        printf("w");
    }else{
        printf("-");
    }
    // 判断文件所属组执行权限
    if(s->st_mode & S_IXGRP )
    {
        printf("x");
    }else{
        printf("-");
    }

    // 判断文件其他人读权限
    if(s->st_mode & S_IROTH )
    {
        printf("r");
    }else{
        printf("-");
    }
    // 判断文件其他人写权限
    if(s->st_mode & S_IWOTH )
    {
        printf("w");
    }else{
        printf("-");
    }
    // 判断文件其他人执行权限
    if(s->st_mode & S_IXOTH )
    {
        printf("x");
    }else{
        printf("-");
    }
    printf("\n");
}

运行

root@sony-HP-Notebook:/usr/local/cpp_demo/file# ./a.out test.txt
rw-r--r--

access函数

#include <unistd.h>
int access(const char *pathname, int mode);
功能:测试指定文件是否具有某种属性
参数
	pathname: 文件名
	mode: 文件权限,4种权限
		R_OK:是否有读权限
		W_OK:是否有写权限
		X_OK:是否有执行权限
		F_OK:测试文件是否存在
返回值:
	0: 有某种权限,或者文件存在
	-1: 没有,或文件不存在

chmod函数

#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
功能:修改文件权限
参数:
	filename: 文件名
	mode: 权限(8进制数)
返回值:
	成功:0
	失败: -1

chown函数

#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);
功能:修改文件所有者和所属组
参数:
	pathname:文件或目录名
	owner: 文件所有者id,通过查看 / etc / passwd 得到所有者id
	group: 文件所属组id,通过查看 / etc / group 得到用户组id
返回值:
	成功:0
	失败: -1

truncate函数

#include <unistd.h>
#include <sys/types .h>
int truncate(const char *path, off_t length)
功能:修改文件大小参数:
	path: 文件文件名字
	length: 指定的文件大小
		a)比原来小,删掉后边的部分
		b)比原来大,向后拓展
返回值:
	成功:0
	失败:-1

link函数(硬链接)

#include <unistd.h>
int link(const char *oldpath, const char *newpath);
功能:创建一个硬链接
参数:
	o1dpath: 源文件名字
	newpath: 硬链接名字
返回值:
	成功: 0
	失败: -1

symlink函数(软链接)

#include <unistd.h>
int symlink(const char *target,const char *linkpath);
功能:创建一个软链接
参数:
	target: 源文件名字
	linkpath: 软链接名字
返回值:
	成功: 0
	失败:-1

readlink函数

#include <unistd.h>
ssize_t readlink(const char *pathname, char *buf,size_t bufsiz);
功能:读软连接对应的文件名,不是读内容(该函数只能读软链接文件)
参数:
	pathname:软连接名
	buf: 存放软件对应的文件名
	bufsiz :缓冲区大小(第二个参数存放的最大字节数)
返回值:
	成功: >0,读到buf中的字符个数
	失败:-1

unlink函数

#include <unistd.h>
int unlink(const char *pathname);
功能: 删除一个文件(软硬链接文件)
参数:
	pathname:删除的文件名字
返回值:
	成功: 0
	失败:-1

rename函数

#include <stdio.h>
int rename(const char *oldpath,const char *newpath);
功能: 把o1dpath的文件名改为newpath
参数:
	oldpath: 旧文件名
	newpath: 新文件名
返回值:
	成功: 0
	失败: -1

阻塞和非阻塞的概念

读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回

从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里

同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定

[注意]阻塞与非阻塞是对于文件而言的,而不是指ead、write等的属性以非阻塞方式打开文件

阻塞程序示例:

#include <stdio.h>
int main()
{
	char ch = -1;
	// 从标准输入获取一个字符
	ch = getchar();
	// 从标准输出
	putchar(ch);
}

示例

读写示例 file_demo.c

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

int main()
{
    int fd = -1;
    // 1. 以只读方式打开一个文件,如果文件不存在就报错
    //fd = open("txt", O_RDONLY);

    // 2. 以只写的方式打开文件,如果不存在则新建
    // fd = open("test.txt", O_WRONLY | O_CREAT , 0644);

    // 3. 以只写的方式打开文件,如果文件存在就报错,如果不存在则新建
    // fd = open("test.txt", O_WRONLY | O_CREAT  | O_EXCL , 0644);

    // 4. 以读写的方式打开文件,如果不存在则新建
    // fd = open("test.txt", O_RDWR | O_CREAT  , 0644);

    // 5. 以只写的方式打开文件,如果文件存在就清空,如果不存在则新建
    // fd = open("test.txt", O_WRONLY | O_TRUNC | O_CREAT  , 0644);

    // 6. 以只写的方式打开文件,如果文件不存在则报错,如果存在则追加写
    fd = open("test.txt", O_WRONLY | O_APPEND  , 0644);
    if(-1 == fd){
        perror("open");
        return 1;
    }
    printf("fd == %d \n", fd);

    // close 文件
    close(fd);

    return 0;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值