02 Linux编程-文件

1、文件描述符

        对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或者创建一个新文件时,内核向进程返回一个文件描述符。当读写一个文件时,用open和creat返回的文件描述符标识该文件,将其作为参数传递给read和write。

        按照惯例,UNIX shell使用文件描述符0与进程的标准输入相结合,文件描述符1与标准输出相结合,文件描述符2与标准错误输出相结合。STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO这几个宏代替了0、1、2这几个数字。

        文件描述符,这个数字在一个进程中表示一个特定含义,当我们open一个文件时,操作系统在内存中构建了一些数据结构来表示这个动态文件,然后返回给应用程序一个数字作为文件描述符,这个数字就和我们内存中维护的这个动态文件的这些数据结构绑定上了,以后我们应用程序如果要操作这个动态文件,只需要用这个文件描述符区分。

        文件描述符的作用域就是当前进程,出了这个进程文件描述符就没有意义了。open函数打开文件,打开成功返回一个文件描述符,打开失败,返回-1。

2、打开/创建文件

        要包含头文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcnt.h>
//函数原型
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
//pathname:要打开的文件名(含路径,缺path则为当前路径)
//flags:O_RDONLY只读打开 O_WRONLY只写打开 O_RDWR可读可写打开
    //当我们附带了权限之后,打开的文件就只能按照这种权限来操作
    //以上这三个常数中应当只指定一个,下列常数是可选择的
        //eg: open("./file1", O_RDWR | O_CREAT, 0600);
        //O_CREAT 若文件不存在,则创建它。使用此选项时,需要同时说明第三个参数mode,用其说明该新文件的存取许可权限
        //O_EXCL 如果同时指定了O_CREATE,而文件已经存在,则出错
        //O_APPEND 每次写时都加到文件的尾端
        //O_TRUNC 属性去打开文件时,如果这个文件中本来是有内容的,而且为只读或者只写成功打开,则将其长度截断为0
//mode:一定是在flags中使用了O_CREAT 标志,mode记录待创建的文件的访问权限

 3、写入文件

        要包含头文件

#include <unistd.h>
//函数原型
ssize_t write(int fd, const void *buf, size_t count);
//fd 文件描述符,打开的文件
//
//

4、关闭文件

         要包含头文件

#include <unistd.h>
//函数原型
int close(int fd);
//fd 文件描述符,打开的文件
//
//

 5、读取文件

        要包含头文件

#include <unistd.h>
//函数原型
ssize read(int fd, void *buf, size_t count);
//fd 文件描述符,打开的文件
//
//

         注意,文件打开会有光标,写文件和读文件都是从光标所在处开始的,一开始打开文件,光标在文件开头,写完文件不关闭文件时,这时光标一般在文件的末尾,那么此时读取文件就是从末尾开始读了,会什么都读不到,那么在读之前可以将光标移动至开头或者将文件关闭重新打开。

6、移动文件光标位置

        要包含头文件

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
//fd:文件描述符,打开的文件
//offset:偏移值
    //相对whence的一个偏移值
//whence:
    //SEEK_SET:文件的头
    //SEEK_END:文件的尾巴
    //SEEK_CUR:当前光标位置

7、文件编程的一般步骤

        打开/创建文件--读取/写入文件--关闭文件

        (1)在Linux中要操作一个文件,一般是先open打开一个文件,得到文件描述符,然后对文件进行读写操作(或其他操作),最后是close关闭文件即可。

        (2)强调一点:我们对文件进行操作时,一定要先打开文件,打开成功之后才能操作,如果打开失败,就不用进行后边的操作了,最后读写完成后,一定要关闭文件,否则会造成文件损坏。

        (3)文件平时是存放在块设备中的文件系统文件中的,我们把这种文件叫静态文件,当我们去open打开一个文件时,linux内核做的操作包括:内核在进程中建立一个打开文件的数据结构,记录下我们打开的这个文件;内核在内存中申请一段内存,并且将静态文件的内容从块设备中读取到内核中特定地址管理存放(叫动态文件)。

        (4)打开文件以后,以后对这个文件的读写操作,都是针对内存中的这一份动态文件的,而并不是针对静态文件的。当然我们对动态文件进行读写以后,此时内存中动态文件和块设备文件中的静态文件就不同步了,当我们close关闭动态文件时,close内部内核将内存中的动态文件的内容去更新(同步)块设备中的静态文件。

        (5)为什么这么设计,不直接对块设备直接操作?因为块设备本身读写非常不灵活,是按块读写的,而内存是按字节单位操作的,而且可以随机操作,很灵活。

8、使用文件编程的例子

8.1 实现linux cp命令

#将a文件复制到b文件
cp a.c b.c    

        实现这个功能的前提要先理解参数,在上一个命令中,有3个参数,cp、a.c和b.c,在main函数中要把参数写完整(int argc, char **argv),这3个参数就是int型的argc,argv是个二级指针,是个数组的指针,这个指针里的每一项都是一个数组。

#include <stdio.h>

int main(int argc, char **argv)
{
	printf("total params:%d\n", argc);
	printf("No.1 param:%s\n", argv[0]);
	printf("No.2 param:%s\n", argv[1]);
	printf("No.3 param:%s\n", argv[2]);
	
	return 0;
}

#编译之后运行 ./a.out a.c b.c 指令可得以下结果
total params:3
No.1 param:./a.out
No.2 param:a.c
No.3 param:b.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
	int fdSrc;
	int fdDes;
	char *readBuf = NULL;

	
	if(argc != 3){
		printf("params error!\n");
		exit(-1);	//直接退出程序,不执行后续的程序
	}
	
	fdSrc = open(argv[1], O_RDWR);
	int size = lseek(fdSrc, 0, SEEK_END);	//计算文件有多少字符
	lseek(fdSrc, 0, SEEK_SET);	//光标重新放到开头
	readBuf = (char *)malloc(sizeof(char) * size + 8);	//多加一点内存,防止不够
	int n_read = read(fdSrc, readBuf, 1024);
	
	fdDes = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0600);
	int n_write = write(fdDes, readBuf, strlen(readBuf));
	
	close(fdSrc);
	close(fdDes);
	
	return 0;
}

8.2 对配置文件的修改

//将下列配置进行修改
SPEED = 5;
LENG = 100;
SCORE = 90;
LEVEL = 95;
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
	int fdSrc;
	int fdDes;
	char *readBuf = NULL;

	
	if(argc != 2){
		printf("params error!\n");
		exit(-1);	//直接退出程序,不执行后续的程序
	}
	
	fdSrc = open(argv[1], O_RDWR);
	int size = lseek(fdSrc, 0, SEEK_END);	//计算文件有多少字符
	lseek(fdSrc, 0, SEEK_SET);	//光标重新放到开头
	readBuf = (char *)malloc(sizeof(char) * size + 8);	//多加一点内存,防止不够
	int n_read = read(fdSrc, readBuf, 1024);
	
	//找到LENG=字符串第一次出现的位置
	char *p = strstr(readBuf, "LENG=");
	if(p == NULL){
		printf("not found\n");
		exit(-1);
	}
	
	//替换参数
	p = p + strlen("LENG=");
	*p = '5';
	
	lseek(fdSrc, 0, SEEK_SET);	//光标重新放到开头
	int n_write = write(fdSrc, readBuf, strlen(readBuf));
	
	close(fdSrc);

	
	return 0;
}

8.3 使用文件写入一个结构体

        写入一个结构体只需要按照文件函数的参数类型传入对应的参数即可,可参照将一个整形数写入文件。新写入的文件,人眼看着可能是乱码,但是程序读出来是正确的,如果想要看着也正常,可能需要一些别的操作。

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

int main(int argc, char **argv)
{
	int fd;

	int data = 100;
	int data2 = 0;

	fd = open('./file1', O_RDWR);

	int n_write = write(fd, &data, sizeof(int));
	
	int n_read = read(fd, &data2, sizeof(int));
	
	lseek(fd, 0, SEEK_SET);
	
	printf("read %d\n", data2);
	
	close(fd);

	
	return 0;
}

9、linux下的文件编程和标准库的文件编程区别

9.1 来源

        open是UNIX系统调用函数,包括LINUX等,返回的是文件描述符(File Descriptor),它是文件在文件描述符表里的索引。而fopen是C语言标准库函数,在不同的系统中应该调用不同的内核API,返回的是一个指向文件结构的指针。C语言的库函数还是需要调用系统API实现的。

9.2 移植性

        fopen是C标准函数,因此拥有良好的移植性,而open是UNIX系统调用,移植性有限。

9.3 适用范围

        open返回文件描述符,而文件描述符是UNIX系统下的一个重要概念,UNIX下的一切设备都是以文件的形式操作,如网络套接字。硬件设备等。在一些特殊情况下,如涉及驱动、内核等,只能使用open等函数。open和fopen都可以用来操纵普通正规文件。

9.4 文件IO层次

        open是属于低级IO函数,fopen是高级IO函数。低级和高级的简单区分标准就是:谁离系统内核更近,谁就低级。低级文件IO运行在内核态,高级文件IO运行在用户态。

9.5 缓冲

        缓冲文件系统的特点是在内存开辟一个“缓冲区”,为程序的每一个文件使用,对文件的操作都在缓冲区进行,内存缓冲区越大,操作外存的次数就少,执行速度就更快,效率更高。而非缓冲文件系统则是借助文件结构体指针来对文件进行管理,通过文件指针来对文件进行访问,是基于操作系统的功能对文件进行读写,是系统级的输入输出。

        open无缓冲,与read、write等配合使用,而fopen有缓冲,与fread、fwrite等配合使用。在顺序访问文件时,使用fopen函数由于在用户态下有了缓冲,减少了在文件读写操作时在用户态和内核态之间的切换,比直接使用open函数效率要高;而若是随机访问文件则是相反。

10、使用fopen读写文件

#include <stdio.h>
#include <string.h>

int main()
{
	//函数原型 FILE *fopen(const *path, const char *mode);
	//函数原型 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
	//函数原型 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
	
	FILE *fp;
	char *str = "hello go";
	char readbuf[120] = {0};
	
	fp = fopen("./file1.txt", "w+");
	
	fwrite(str, sizeof(char), strlen(str), fp);
	//fwrite(str, sizeof(char) * strlen(str), 1, fp);
	
	fseek(fp, 0, SEEK_SET);
	
	fread(readbuf, sizeof(char), strlen(str), fp);
	
	fclose(fp);
	
	printf("read data:%s\n", readbuf);
	
	return 0;
}

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值