文件编程
一、Linux文件
Linux下,一切皆文件,在Linux下对设备和目录的操作都等同于对文件的操作。
文件分类:普通文件、设备文件、目录文件、 链接文件;
文件描述符:文件描述符是一个非负的整数,它是一个索引值,并指向在内核中每个进程打开文件的记录表。
当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。
二、Linux系统调用及用户编程接口API
系统调用:操作系统提供给用户的一组“特殊”接口,用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务。
为什么不能直接访问内核?
为了更好地保护内核,将程序的运行空间分为内核态和用户态,它们分别运行在不同的级别上。因此,用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间的函数。
举例来说,当编写内核文件时,倘若出现段错误,整个系统就会重启,这点并不像我们平时编写普通程序出现段错误仅仅关闭程序那么简单。
三、系统调用函数
这里提醒读者,要多用man去查看函数的原型声明、参数类型、返回值
1.创建(不常用)
creat
函数原型:
int creat(const char *pathname, mode_t mode)
参数:
pathname;文件名(路径)
mode:创建模式,如S_IRWXU、S_IRUSR、S_IWUSR、S_IXUSR
例如:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
int ret;
ret = creat("hello.txt", S_IRWXU | S_IRWXG);
if(-1 == ret)
{
perror("creat");
exit(1);
}
return 0;
}
2.打开
open函数用来打开或创建文件,在打开或创建同时可以指定文件的属性及用户的权限等各种参数。
函数原型声明:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数:
pathname:文件名(路径)
flags:打开的方式(只读、只写、读写)
mode:创建时定义的用户权限
当打开一个文件时,要用close()将其关闭;
例如:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
int fd, ret;
fd = open("hello.txt", O_RDWR | O_CREAT | O_EXCL, S_IRWXU);
if(-1 == fd)
{
perror("open");
exit(1);
}
close(fd);
return 0;
}
这里说明一下O_CREAT、O_EXCL通常组合使用,使用O_EXCL必须是创建不存在的文件,否则会出现错误
3.写
write:把count个字节从buf指向的缓冲区写到文件描符fd所指向的文件中,返回值为实际写入的字节数;
函数原型声明:
ssize_t write(int fd, const void *buf, size_t count);
例如:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
int main()
{
int fd, ret;
char buf[100] = "hello world!";
fd = open("hello.txt", O_RDWR | O_CREAT, S_IRWXU);
if(-1 == fd)
{
printf("Errno : %d\n", errno);
perror("open");
exit(1);
}
ret = write(fd, buf, sizeof("hello world!"));
if(-1 == ret)
{
perror("write");
exit(1);
}
close(fd);
return 0;
}
4.读
read:从文件描述符fd所指定的文件中,读取count个字节到buf所指向的缓冲区,返回值为实际读取的字节数。
函数原型声明:
ssize_t read(int fd, void *buf, size_t count);
例如:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
int main()
{
int fd, ret;
char buf[100] = {0};
fd = open("hello.txt", O_RDWR | O_CREAT, S_IRWXU);
if(-1 == fd)
{
printf("Errno : %d\n", errno);
perror("open");
exit(1);
}
ret = read(fd, buf, sizeof(buf));
if(-1 == ret)
{
perror("read");
exit(1);
}
printf("Read is :%s\n", buf);
close(fd);
return 0;
}
5.定位
lseek:将文件赌侠指针相对whence移动offset个字节。操作成功时,返回文件指针相对于文件头的位置。
函数原型声明:
off_t lseek(int fildes, off_t offset, int whence);
练习:使用main命令行参数实现cp命令:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd1, fd2, ret;
char buf[50] = {0};
fd1 = open(argv[1], O_RDWR);
if(-1 == fd1)
{
perror("open1");
exit(1);
}
fd2 = open(argv[2], O_RDWR | O_CREAT, O_RDWR);
if(-1 == fd2)
{
perror("open2");
exit(1);
}
while((ret = read(fd1, buf, sizeof(buf))) != 0 )
{
ret = write(fd2, buf, ret);
if(ret < 0)
{
perror("write");
}
memset(buf, 0, sizeof(buf));
}
close(fd1);
close(fd2);
return 0;
}
标准库函数
c库函数的文件操作时独立于具体的操作系统平台的,不管是在DOS、Windows、LINUX还是VxWorks中都是这些函数。
不带缓存的I/O是文件描述符操作、带缓存的I/O是针对流的。
标准I/O库就是带缓存的I/O,它由ANSI C标准说明。当然标准I/O最终都会调用上面的I/O例程。
标准I/O提供缓存的目的就是减少调用read和write的次数,它对每个I/O流自动进行缓存管理。
三种缓存类型:全缓存、行缓存、无缓存;
printf就是一个行缓存,遇到换行(\n)才会刷新缓存
for(i = 0; i < 5; i++)
{
printf("%d ", i);
sleep(1);
}
对于这个代码,应该每隔一秒输出一个数,其实不然、上述结果会在5s后输出0 1 2 3 4 ;原因在于printf是行缓存,只有换行才会刷新缓冲区,只有写成printf(“%d\n”, i);,才会有间隔打印的效果。
fopen
FILE *fopen(const char *path, const char *mode);
参数:
path:打开的文件名(路径)
mode:打开模式
r, rb:只读模式打开,文件必须存在
r+,rb+,r+b:读写模式打开,文件必须存在
w,wb:只写模式打开,如果文件不存在则创建、存在则清空重写
w+,wb+,w+b:读写模式打开,如果文件不存在则创建、存在则清空重写
a,ab:只能在文件末尾追加数据,不存在则创建
a+,ab+,a+b:读和追加模式打开,不存在则创建
例如:
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *fp = fopen("hello.txt", "a");
if(NULL == fp)
{
perror("fopen");
exit(1);
}
fclose(fp);
return 0;
}
fread
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
stream:为已打开的文件指针
ptr:指向欲存放读取进来的数据空间,读取的字符数以参数size*nmemb来决定
返回值:返回实际读取到的字符数
通常情况下,我们将size写成1;
例如:
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *fp;
char buf[100] = {0};
size_t ret;
fp = fopen("hello.txt", "r");
if(NULL == fp)
{
perror("fopen");
exit(1);
}
ret = fread(buf, 1, sizeof(buf), fp);
if(0 == ret)
{
perror("fread");
exit(1);
}
printf("Read From Txt : %s\n", buf);
return 0;
}
fwrite
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
stream:为已打开的文件指针
ptr:指向欲写入的数据地址
返回值:返回实际写入的字节数目
例如:
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *fp;
char buf[50] = "I am from shanghai";
size_t ret;
fp = fopen("hello.txt", "a");
if(NULL == fp)
{
perror("open");
exit(1);
}
ret = fwrite(buf, 1, sizeof(buf), fp);
if(0 == ret)
{
perror("fwrite");
exit(1);
}
fclose(fp);
return 0;
}
练习:实现cat命令功能
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
FILE *fp;
size_t ret;
char buf[50] = {0};
fp = fopen(argv[1], "r");
if(NULL == fp)
{
perror("fopen");
exit(1);
}
while((ret = fread(buf, 1, sizeof(buf) - 1, fp)) != 0)
{
printf("%s", buf);
memset(buf, 0, sizeof(buf));
}
fclose(fp);
return 0;
}