作业
- 使用标准io完成两个文件的拷贝
#include <stdio.h>
int main(int argc, const char *argv[]){
if(argc!=3){
puts("输入有误!");
return -1;
}
FILE* fp=NULL;
FILE*fp_cp=NULL;
if((fp=fopen(argv[1],"r"))==NULL){
perror("打开1文件出错:");
return -1;
}
if((fp_cp=fopen(argv[2],"w"))==NULL){
perror("打开2文件出错:");
return -1;
}
char buff[1024];
while(fgets(buff,sizeof(buff),fp)!=NULL){
fputs(buff,fp_cp);
}
puts("拷贝成功!");
fclose(fp);
fclose(fp_cp);
return 0;
}
- 使用文件IO完成两个文件的拷贝
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
if(argc!=3)
{
puts("输入有误");
return -1;
}
int fd=-1,fd_cp=-1;
fd=open(argv[1],O_RDONLY);
if(fd==-1)
{
perror("打开文件1失败:");
return -1;
}
fd_cp=open(argv[2],O_WRONLY | O_CREAT | O_TRUNC,0664);
if(fd_cp==-1)
{
perror("打开文件2失败:");
return -1;
}
// ssize_t write(int fd, const void *buf, size_t count);
// 功能:将buf指向的地址中count个字节,写入到fd指向的文件中
// 参数1:文件描述符
// 参数2:容器起始地址,void*类型,表明可以写入任何类型的数据
// 参数3:要写入数据的个数
// 返回值:成功返回写入的字符个数,失败返回-1并置位错误码
// ssize_t read(int fd, void *buf, size_t count);
// 功 能:从fd文件中,将count个字节读取到buf对应的容器中
// 参数1:文件描述符
// 参数2:容器起始地址,void*类型,表明可以读取任何类型的数据
// 参数3:要读取数据的个数
// 返回值:成功返回读取字节的个数,失败返回-1并置位错误码
//循环写入
char buff[4096];
int res;
while((res=read(fd,buff,sizeof(buff)))>0)
{
write(fd_cp,buff,res);
}
//关闭文件
close(fd);
close(fd_cp);
}
- 将stat函数实现一遍
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义文件属性类型的数据
struct stat sb; //用于存储获得的文件属性
//调用函数的到文件属性
stat(argv[1], &sb);
switch(sb.st_mode&S_IFMT)
{
case S_IFSOCK:
{
printf("这是套接字文件\t");
}
break;
case S_IFLNK:
{
printf("这是链接文件\t");
}
break;
case S_IFREG:
{
printf("这是普通文件\t");
}
break;
case S_IFBLK:
{
printf("这是块设备文件\t");
}
break;
case S_IFDIR:
{
printf("这是目录文件\t");
}
break;
case S_IFCHR:
{
printf("这是字符设备文件\t");
}
break;
case S_IFIFO:
{
printf("这是管道文件\t");
}
break;
}
printf("%#o\t%ld\t%ld\n", sb.st_mode&0777, sb.st_size, sb.st_ino);
return 0;
}
- 将目录操作实现一遍
#include<myhead.h>
int main(int argc, const char *argv[])
{
//判断外部传参个数
if(argc != 2)
{
printf("input error\n");
printf("usage:./a.out name\n");
return -1;
}
//定义目录指针
DIR *dp = NULL;
//打开目录
if((dp = opendir(argv[1])) == NULL)
{
perror("opendir error");
return -1;
}
//读取目录中的文件或目录信息
struct dirent *sdp = NULL;
while((sdp = readdir(dp)) != NULL)
{
//输出当前文件或目录的信息
printf("inode:%10ld, size:%10d, %10s, ",\
sdp->d_ino, sdp->d_reclen, sdp->d_name);
//输出类型
switch(sdp->d_type)
{
case DT_BLK:
{
printf("b\n");
}
break;
case DT_CHR:
{
printf("c\n");
}
break;
case DT_DIR:
{
printf("d\n");
}
break;
case DT_FIFO:
{
printf("p\n");
}
break;
case DT_LNK:
{
printf("l\n");
}
break;
case DT_REG:
{
printf("-\n");
}
break;
case DT_SOCK:
{
printf("s\n");
}
break;
}
}
//关闭目录
closedir(dp);
return 0;
}
笔记
三、文件IO
- 文件IO是基于系统调用
- 程序每进行一次系统调用,就会从用户空间向内核空间进行一次切换,执行效率较慢
- 目的:由于后期进程间通信,如管道、套接字通信,都使用的是文件IO,所以引入文件IO操作的概念
3.1 文件描述符
- 件描述符本质上是一个非负整数,每个打开的文件,都会对应一个整数用于系统调用
- 每个程序打开文件的个数是有上限的,默认是1024个,可以通过ulimit -a进行查看
- 文件描述符使用原则:最小未分配原则
- 当使用open函数打开一个文件时,系统会给该文件分配一个文件描述符作为句柄
- 当一个程序运行时,默认会打开三个文件描述符,分别对应标准输入、标准输出、标准出错
#include<stdio.h>
int main(int argc, const char *argv[])
{
printf("%d\n", stdin->_fileno); //0
printf("%d\n", stdout->_fileno); //1
printf("%d\n", stderr->_fileno); //2
return 0;
}
3.2 open函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
功能:打开或可能创建一个文件
参数1:文件路径,是一个字符串表示要打开的文件
参数2:打开标识
三个必须选一个:O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写)
后面的可以以位或的方式连接,表示拥有该属性
O_CREAT:表示创建一个文件, 当第二个参数中有O_CREAT时,第三个参数必须要加上
O_APPEND:追加方式打开文件
O_TRUNC:清空文件内容
O_NONBLOCK:以非阻塞形式打开文件
O_EXCL:确保本次操作一定创建文件,如果文件已经存在,则open函数会报错,错误码为EEXIST
eg: "w": O_WRONLY | O_CREAT | O_TRUNC
"r":O_RDONLY
"a":O_WRONLY | O_APPEND | O_CREAT
"w+":O_RDWR | O_CREAT | O_TRUNC
"r+":O_RDWR
"a+":O_RDWR | O_CREAT | O_APPEND
参数3:如果第二个参数中有O_CREAT,该参数必须设置,表示文件的权限,如果不设置,该文件的权限是一个随机权限
文件最终的权限是由给定的权限 & ~umask 得到
终端的umask的值,可以通过指令 umask查看,可以通过指令 umask -n更改当前终端的umask的值
一般创建普通文件最大权限为:664
目录文件权限最大权限为:775
返回值:成功返回一个新的文件描述符,失败返回-1并置位错误码。
3.3 close函数
#include <unistd.h>
int close(int fd);
功能:关闭指定的文件描述符
参数:要关闭的文件描述符,关闭后,该文件描述符可以分配给其他文件使用
返回值:成功返回0,失败返回-1并置位错误码
#include<myhead.h>
int main(int argc, const char *argv[])
{
int fd = -1; //定义文件描述符变量
fd = open("./test.txt", O_WRONLY|O_CREAT|O_TRUNC, 0664);
if(fd == -1)
{
perror("open error");
return -1;
}
printf("fd = %d\n", fd); //?3
//关闭文件描述符
close(fd);
return 0;
}
3.4 write\read函数
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:将buf指向的地址中count个字节,写入到fd指向的文件中
参数1:文件描述符
参数2:容器起始地址,void*类型,表明可以写入任何类型的数据
参数3:要写入数据的个数
返回值:成功返回写入的字符个数,失败返回-1并置位错误码
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:从fd文件中,将count个字节读取到buf对应的容器中
参数1:文件描述符
参数2:容器起始地址,void*类型,表明可以读取任何类型的数据
参数3:要读取数据的个数
返回值:成功返回读取字节的个数,失败返回-1并置位错误码
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义文件描述符变量
int fd = -1;
//以只写的形式打开文件
if((fd = open("./test.txt", O_WRONLY|O_CREAT|O_TRUNC, 0664)) ==-1)
{
perror("open error");
return -1;
}
printf("fd = %d\n", fd); //3
char buf[128] = "hello world ni hao I love China\n";
//将字符串写入到文件
int res = write(fd, buf, strlen(buf));
printf("res = %d\n", res); //
//关闭文件
close(fd);
//以只读的形式打开文件
if((fd = open("./test.txt", O_RDONLY)) ==-1)
{
perror("open error");
return -1;
}
printf("fd = %d\n", fd); //3
char rbuf[128] = "";
res = read(fd, rbuf, sizeof(rbuf));
printf("res = %d\n", res); //?
write(stdout->_fileno, rbuf, res);
//关闭文件
close(fd);
return 0;
}
练习:使用read、wriet完成两个文件的拷贝
#include<myhead.h>
int main(int argc, const char *argv[])
{
//判断传入的文件个数
if(argc != 3)
{
printf("input file error\n");
printf("usage:./a.out srcfile dstfile\n");
return -1;
}
//定义文件描述符变量
int srcfd, dstfd;
//以只读的形式打开源文件
if((srcfd = open(argv[1], O_RDONLY)) ==-1)
{
perror("open srcfile error");
return -1;
}
//以只写的形式打开目标文件
if((dstfd = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0664)) ==-1)
{
perror("open dstfile error");
return -1;
}
//不断得将源文件中的内容读出,并写入的目标文件中
//直到源文件全部读取结束
char buf[128] = "";
while(1)
{
memset(buf, 0, sizeof(buf)); //将容器清空
int res = read(srcfd, buf, sizeof(buf)); //从源文件中读取数据
write(dstfd, buf, res); //将数据写入目标文件
//对读取的数据个数进行判断
if(res == 0)
{
break;
}
}
//关闭两个文件
close(srcfd);
close(dstfd);
printf("拷贝成功\n");
return 0;
}
3.5 光标移动(lseek)
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
功能:移动光标位置
参数1:要移动光标的文件描述符
参数2:偏移量
>0:表示向后偏移
=0:表示不偏移
<0:表示向前偏移
参数3:偏移的起始位置
SEEK_SET:从文件开头偏移
SEEK_CUR:从文件光标当前位置偏移
SEEK_END:从文件末尾开始偏移
返回值:成功返回光标当前位置,失败返回(off_t)-1并置位错误码
lseek = fseek + ftell
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义文件描述符变量
int fd = -1;
//以只读的形式打开图片文件
if((fd = open("./bb.bmp", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}
//读取图像的大小
//偏移2个自己
lseek(fd, 2, SEEK_SET);
//读取数据
unsigned int size;
read(fd, &size, sizeof(unsigned int));
printf("size = %d\n", size); //?
size = lseek(fd, 0, SEEK_END); //将光标定位在结尾,将光标位置返回
printf("size = %d\n", size); //?
//关闭文件
close(fd);
return 0;
}
3.6 关于文件描述符的拷贝问题
1> 使用赋值的情况直接拷贝,多个文件描述符变量,共享同一个文件光标,通过一个文件描述符进行移动,所有的文件描述符的光标都会改变
#include<myhead.h>
int main(int argc, const char *argv[])
{
//以只读的形式打开一个文件
int fd1 = -1;
if((fd1 = open("./test.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}
printf("fd1 = %d\n", fd1); //3
int fd2 = fd1; //对文件描述符直接进行拷贝
//使用fd1将光标后移10个字节
lseek(fd1, 12, SEEK_SET);
//从fd2中读取数据
char buf[10] = "";
read(fd2, buf, sizeof(buf)-1);
printf("buf = %s\n", buf); //如果输出的是hello开头的,说明fd1和fd2不共享文件光标
//如果输出的是ni hao开头的,说明fd1和fd2共享同一个文件光标
return 0;
}
2> 使用dup函数,完成文件描述符的拷贝
#include <unistd.h>
int dup(int oldfd);
功能:通过旧的文件描述符,拷贝出一个新的文件描述符,新文件描述符遵循最小未分配原则
参数:旧文件描述符
返回值:新文件描述符,失败返回-1并置位错误码
执行后,新旧两个文件描述符都指向同一个打开的文件,共享文件光标位置
#include<myhead.h>
int main(int argc, const char *argv[])
{
//以只读的形式打开一个文件
int fd1 = -1;
if((fd1 = open("./test.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}
printf("fd1 = %d\n", fd1); //3
int fd2 = dup(fd1); //拷贝旧文件描述符,产生一个新文件描述符
printf("fd2 = %d\n", fd2); //4
//使用fd1将光标后移10个字节
lseek(3, 12, SEEK_SET);
//从fd2中读取数据
char buf[10] = "";
read(4, buf, sizeof(buf)-1);
printf("buf = %s\n", buf); //如果输出的是hello开头的,说明fd1和fd2不共享文件光标
//如果输出的是ni hao开头的,说明fd1和fd2共享同一个文件光标
return 0;
}
3> 使用dup2函数完成两个文件描述符的拷贝
int dup2(int oldfd, int newfd);
功能:通过拷贝旧的文件描述符到新的文件描述符中
参数1:旧文件描述符
参数2:新文件描述符,如果newfd已经指向了某个已经打开的文件,则在进行拷贝之前,先将其关闭
返回值:成功返回新的文件描述符,失败返回-1并置位错误码
注意:newfd不是使用最小为分配原则,因为newfd在调用之前可能已经指向某个文件,调用后,无论是newfd函数oldfd都指向oldfd指向的文件
通过该方式复制的文件描述符,依然共享同一个文件的光标
#include<myhead.h>
int main(int argc, const char *argv[])
{
//以只读的形式打开一个文件
int fd1 = -1;
if((fd1 = open("./test.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}
//定义一个文件描述符变量,打开另一个文件
int fd2 = -1;
if((fd2 = open("./bb.bmp", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}
printf("fd1 = %d\n", fd1); //3
printf("fd2 = %d\n", fd2); //4
//执行文件描述符拷贝工作
dup2(fd1, fd2);
//使用fd1将光标后移10个字节
lseek(3, 12, SEEK_SET);
//从fd2中读取数据
char buf[10] = "";
read(4, buf, sizeof(buf)-1);
printf("buf = %s\n", buf); //如果输出的是hello开头的,说明fd1和fd2不共享文件光标
//如果输出的是ni hao开头的,说明fd1和fd2共享同一个文件光标
return 0;
}
4> 多次使用open函数完成对同一个文件的打开时,不同的文件描述符使用的是独立的光标
#include<myhead.h>
int main(int argc, const char *argv[])
{
//以只读的形式打开一个文件
int fd1 = -1;
if((fd1 = open("./test.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}
//定义一个文件描述符变量,打开另一个文件
int fd2 = -1;
if((fd2 = open("./test.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}
printf("fd1 = %d\n", fd1); //3
printf("fd2 = %d\n", fd2); //4
//使用fd1将光标后移10个字节
lseek(fd1, 12, SEEK_SET);
//从fd2中读取数据
char buf[10] = "";
read(fd2, buf, sizeof(buf)-1);
printf("buf = %s\n", buf); //如果输出的是hello开头的,说明fd1和fd2不共享文件光标
//如果输出的是ni hao开头的,说明fd1和fd2共享同一个文件光标
return 0;
}
四、文件属性获取(stat)
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
功能:将给定的文件的相关属性,通过statbuf返回出来
参数1:要获取属性的文件路径是一个字符串
参数2:文件属性结构体指针,需要传递一个文件属性类型的结构体变量
文件属性结构体类型:
struct stat {
dev_t st_dev; /* ID of device containing file */ 设备id号
ino_t st_ino; /* Inode number */ 文件的inode号
mode_t st_mode; /* File type and mode */ 文件的类型和权限
nlink_t st_nlink; /* Number of hard links */ 硬链接数
uid_t st_uid; /* User ID of owner */ 用户id号
gid_t st_gid; /* Group ID of owner */ 组id号
dev_t st_rdev; /* Device ID (if special file) */ 特殊文件的设备号
off_t st_size; /* Total size, in bytes */ 总大小,以字节为单位
blksize_t st_blksize; /* Block size for filesystem I/O */ 块的大小
blkcnt_t st_blocks; /* Number of 512B blocks allocated */ 块的个数
};
关于文件类型和文件权限的介绍
使用 st_mode & S_IFMT后的到文件的类型
S_IFSOCK 0140000 socket 套接字文件类型
S_IFLNK 0120000 symbolic link 链接文件类型
S_IFREG 0100000 regular file 普通文件类型
S_IFBLK 0060000 block device 块设备文件类型
S_IFDIR 0040000 directory 目录文件类型
S_IFCHR 0020000 character device 字符设备文件类型
S_IFIFO 0010000 FIFO 管道文件类型
使用 st_mode & 0777后得到文件的权限
返回值:成功返回0,失败返回-1并置位错误码
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义文件属性类型的数据
struct stat sb; //用于存储获得的文件属性
//调用函数的到文件属性
stat(argv[1], &sb);
switch(sb.st_mode&S_IFMT)
{
case S_IFSOCK:
{
printf("这是套接字文件\t");
}
break;
case S_IFLNK:
{
printf("这是链接文件\t");
}
break;
case S_IFREG:
{
printf("这是普通文件\t");
}
break;
case S_IFBLK:
{
printf("这是块设备文件\t");
}
break;
case S_IFDIR:
{
printf("这是目录文件\t");
}
break;
case S_IFCHR:
{
printf("这是字符设备文件\t");
}
break;
case S_IFIFO:
{
printf("这是管道文件\t");
}
break;
}
printf("%#o\t%ld\t%ld\n", sb.st_mode&0777, sb.st_size, sb.st_ino);
return 0;
}
五、目录相关操作
5.1 opendir函数
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
功能:打开一个指定的目录,并返回该目录的目录指针
参数:要打开的目录,是一个字符串
返回值:成功返回目录指针,失败返回NULL并置位错误码
5.2 closedir函数
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
功能:关闭一个已经打开的目录指针
参数:目录指针
返回值:成功返回0,失败返回-1并置位错误码
5.3 readdir函数
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
功能:读取指定目录中的下一个文件或目录的信息
参数:目录指针
返回值:成功返回当前文件或目录的信息,失败返回NULL并置位错误码
struct dirent {
ino_t d_ino; /* Inode number */ //当前目录或文件的inode号 8字节
off_t d_off; /* Not an offset; see below */ 光标偏移量 8字节
unsigned short d_reclen; /* Length of this record */ 当前记录的大小 2字节
unsigned char d_type; /* Type of file; not supported 文件类型 1字节
by all filesystem types */
char d_name[256]; /* Null-terminated filename */ 文件名
};
关于文件或目录类型
DT_BLK This is a block device.
DT_CHR This is a character device.
DT_DIR This is a directory.
DT_FIFO This is a named pipe (FIFO).
DT_LNK This is a symbolic link.
DT_REG This is a regular file.
DT_SOCK This is a UNIX domain socket.
DT_UNKNOWN The file type could not be determined.
#include<myhead.h>
int main(int argc, const char *argv[])
{
//判断外部传参个数
if(argc != 2)
{
printf("input error\n");
printf("usage:./a.out name\n");
return -1;
}
//定义目录指针
DIR *dp = NULL;
//打开目录
if((dp = opendir(argv[1])) == NULL)
{
perror("opendir error");
return -1;
}
//读取目录中的文件或目录信息
struct dirent *sdp = NULL;
while((sdp = readdir(dp)) != NULL)
{
//输出当前文件或目录的信息
printf("inode:%10ld, size:%10d, %10s, ",\
sdp->d_ino, sdp->d_reclen, sdp->d_name);
//输出类型
switch(sdp->d_type)
{
case DT_BLK:
{
printf("b\n");
}
break;
case DT_CHR:
{
printf("c\n");
}
break;
case DT_DIR:
{
printf("d\n");
}
break;
case DT_FIFO:
{
printf("p\n");
}
break;
case DT_LNK:
{
printf("l\n");
}
break;
case DT_REG:
{
printf("-\n");
}
break;
case DT_SOCK:
{
printf("s\n");
}
break;
}
}
//关闭目录
closedir(dp);
return 0;
}