文件I/O的含义
文件 I/O(输入/输出)是计算机程序中的重要概念,涉及到与文件的读取和写入操作。在C语言中,使用标准库函数来进行文件 I/O 操作。
文件和文件描述符
1.文件拓展名
· .tar,.tar.gz,.tgz,.zip,.tar.bz表示压缩文件,创建命令为tar,gzip,unzip等;
· .sh表示shell脚本文件;
· .pl表示perl语言文件;
· .py表示Python语言文件;
· .conf表示系统服务的配置文件;
· .c表示C文件;
· .h表示头文件;
· .cpp表示C++源文件;
· .so表示动态库文件;
· .a表示静态库文件;
2.文件类型
通过ls -l 命令查看文件类型
文件类型标识 | 文件类型 |
- | 普通文件 |
d | 目录文件 |
l | 符号链接 |
c | 字符设备 |
b | 块设备 |
p | 管道 |
s | 套接字socket |
3.文件描述符(file descriptor,fd)
文件描述符是在Unix-like操作系统中用于标识已打开文件或I/O设备的整数。在C语言中,文件描述符是通过int
类型的变量表示的。POSIX标准要求每次打开文件时必须使用当前进程中最小可用的文件描述符号码,因此第一次打开文件的描述符一定是3。文件描述符通常是非负整数
文件描述符 | 用途 | POSIX文件描述符 | 标准I/O文件流 |
0 | 标准输入 | STDIN_FILENO | stdin |
1 | 标准输出 | STDOUT_FILENO | stdout |
2 | 标准出错 | STDERR_FILENO | stderr |
程序编写
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define MSG_STR "Hello World\n"
int main(int argc, char **argv) {
// 使用 printf 函数向 stdout 打印字符串
printf("%s", MSG_STR);
// 使用 fputs 函数向 stdout 写入字符串
fputs(MSG_STR, stdout);
// 使用 write 函数向 stdout 写入字符串
write(STDOUT_FILENO, MSG_STR, strlen(MSG_STR));
return 0;
}
-
printf("%s", MSG_STR);
:使用printf
函数向标准输出打印格式化字符串。%s
是一个占位符,表示字符串,用于接收MSG_STR
的值。 -
fputs(MSG_STR, stdout);
:使用fputs
函数将字符串MSG_STR
写入到标准输出流stdout
。 -
write(STDOUT_FILENO, MSG_STR, strlen(MSG_STR));
:使用write
函数直接向文件描述符STDOUT_FILENO
写入字符串。这是一个较底层的系统调用,直接操作文件描述符。
最后,#define MSG_STR "Hello World\n"
定义了一个宏,用于表示要输出的字符串 "Hello World\n",这样在代码中多次使用 MSG_STR
就不需要写整个字符串了。
4.文件I/O操作
程序编写
以下示例程序将调用open()系统调用打开/创建text.txt的文件,然后再调用write()系统调用将字符串MSG_STR写入到文件中,之后在调用read()系统调用读取text.txt文件中的内容并打印;
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define BUFSIZE 1024
#define MSG_STR "Hello World\n"
int main(int argc, char *argv[]) {
int fd = -1;
int rv = -1;
char buf[BUFSIZE];
// 打开文件 test.txt,如果不存在则创建,以读写方式打开,权限为 0666
fd = open("test.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);
// 检查文件是否成功打开
if (fd < 0) {
perror("Open/Create file test.txt failure");
return 1;
}
printf("Open file returned file descriptor [%d]\n", fd);
// 将字符串 MSG_STR 写入文件
if ((rv = write(fd, MSG_STR, strlen(MSG_STR))) < 0) {
printf("Write %d bytes into file failure: %s\n", rv, strerror(errno));
goto cleanup;
}
// 将文件指针移动到文件开头
lseek(fd, 0, SEEK_SET);
// 读取文件内容到缓冲区
memset(buf, 0, sizeof(buf));
if ((rv = read(fd, buf, sizeof(buf))) < 0) {
printf("Read data from file failure: %s\n", strerror(errno));
goto cleanup;
}
printf("Read %d bytes data from file: %s\n", rv, buf);
cleanup:
// 关闭文件
close(fd);
return 0;
}
-
open("test.txt", O_RDWR | O_CREAT | O_TRUNC, 0666)
:打开文件 "test.txt",如果不存在则创建,以读写方式打开,权限为 0666。O_RDWR
表示读写模式,O_CREAT
表示如果文件不存在则创建,O_TRUNC
表示清空文件内容,0666
是文件权限。 -
write(fd, MSG_STR, strlen(MSG_STR))
:将字符串MSG_STR
写入到文件中。 -
lseek(fd, 0, SEEK_SET)
:将文件指针移动到文件开头,准备读取文件内容。 -
read(fd, buf, sizeof(buf))
:从文件中读取内容到缓冲区buf
。
出错处理
概述
系统调用出错的原因是以整形值的形式保存在errno中,整形值的形式不友好,所以通常将errno的值转成字符串的形式。经常用到的函数:perror()和strerror()
perror():后面只需要跟一个字符串参数s即可,它会将字符串打印在标准输出上,内容是:字符串s后面紧跟着冒号和字符串形式错误的原因;
缺点:错误只能打印在标准输出上,对于要进行格式化控制的输出或想写入到日志文件中时就不能处理;
strerror():用来将整形值错误errno换装成为字符串形式;
函数原型
void perror(const char *s)
char *strerror(int errno)
程序示例
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <unistd.h>
int main(int argc,char **argv)
{
char *file_name = "/test.txt";//根目录下不存在 打开会失败
int fd = -1;
fd = open(file_name,O_RDONLY,066);//打开文件,权限只读
if(fd<0)
{
perror("Open file failure");
printf("Open file %s failure:%s\n",file_name,strerror(errno));
return 0;
}
close(fd);
}
5. 文件I/O操作函数
(1).open()系统调用
open()
函数是一个用于打开文件的系统调用,用于创建或打开一个文件,并返回文件描述符(file descriptor)。在C语言中,该函数通常是通过 fcntl.h
头文件提供的。
以下是 open()
函数的基本原型:
#include <fcntl.h> int open(const char *pathname, int flags, mode_t mode);
pathname
:要打开或创建的文件的路径名。flags
:打开文件的标志,可以是以下之一或它们的组合:O_RDONLY
:只读方式打开。O_WRONLY
:只写方式打开。O_RDWR
:读写方式打开。O_CREAT
:如果文件不存在则创建。O_TRUNC
:如果文件存在并且以写入方式打开,则将其长度截断为0。O_APPEND
:以追加方式打开。- 等等,还有其他标志。
mode
:创建文件时的权限,通常是一个八进制数,例如0666
。
返回值是一个非负整数的文件描述符,如果出现错误,则返回 -1
并设置全局变量 errno
。
(2)create()系统调用
在早期的Unix系统中,create
函数是用于创建新文件的系统调用。然而,随着时间的推移,这个函数逐渐被 open
函数取代,open
函数提供了更多的灵活性。
create
函数的原型如下:
#include <fcntl.h>
int create(const char *pathname, mode_t mode);
在这里,pathname
是新文件的路径名,mode
是新文件的权限。
(3)close()系统调用
close()
函数是用于关闭文件描述符的系统调用。在C语言中,这个函数是通过 unistd.h
头文件提供的。以下是 close()
函数的基本原型:
#include <unistd.h>
int close(int fd);
fd
:要关闭的文件描述符。
close()
函数的返回值是一个整数,成功关闭文件返回0,失败返回-1,并设置全局变量 errno
来指示错误的原因。
(4)write()系统调用
write()
函数是一个用于向文件描述符写入数据的系统调用。在C语言中,这个函数是通过 unistd.h
头文件提供的。以下是 write()
函数的基本原型:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
fd
:要写入的文件描述符。buf
:包含要写入的数据的缓冲区的指针。count
:要写入的字节数。
write()
函数返回一个 ssize_t
类型的值,表示实际写入的字节数。如果出现错误,返回值为 -1,并设置全局变量 errno
来指示错误的原因。
(5)Iseek()系统调用
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
fd
:文件描述符。offset
:偏移量,表示相对于whence
参数的位置。
whence | 位置 |
SEEK_SET | 文件头 |
SEEK_CUR | 当前位置 |
SEEK_END | 文件尾 |
例:
Iseek(fd,0,SEEK_SET): 将文件偏移量设置到文件开始的第一个字节上;
Iseek(fd,0,SEEK_END): 将文件偏移量设置到文件最后一个字节上;
Iseek(fd,-1,SEEK_END): 将文件偏移量设置到文件最后的倒数第一个字节上;
我们再从文件中读取内容,或者往文件中写内容时都有一个起始地址,这个起始地址就是文件偏移量,当我们对文件进行读写的时候都是从这个文件偏移量开始。
(6)read系统调用
read()
是一个用于从文件描述符中读取数据的系统调用。在C语言中,该函数是通过 unistd.h
头文件提供的。以下是 read()
函数的基本原型:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
read()
是一个用于从文件描述符中读取数据的系统调用。在C语言中,该函数是通过 unistd.h
头文件提供的。以下是 read()
函数的基本原型:
fd
:要读取的文件描述符。buf
:包含读取数据的缓冲区的指针。count
:要读取的字节数。
read()
函数返回一个 ssize_t
类型的值,表示实际读取的字节数。如果返回值为 0,表示已经到达文件末尾。如果返回值为 -1,表示发生错误,并且 errno
变量被设置以指示错误的类型。
(7)dup()和dup2()系统调用
dup()
和 dup2()
是用于复制文件描述符的系统调用,通常在文件 I/O 操作中使用。它们的作用是创建一个新的文件描述符,该描述符与现有的文件描述符指向同一个文件表项。
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(int argc,char **argv)
{
int fd = -1;
fd = open("std.txt",O_RDWR|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
printf("Open file failure:%s\n",strerror(errno));
return 0;
}
dup2(fd,STDIN_FILENO);
dup2(fd,STDOUT_FILENO);
dup2(fd,STDERR_FILENO);
printf("fd = %d\n",fd);
close(fd);
}
6.文件夹操作相关系统调用
函数原型 | 函数说明 |
int mkdir(const char *pathname,mode_t,mode) | 创建文件夹 |
int remdir(const char *pathname) | 删除文件夹 |
DIR *opendir(const char *pathname) | 打开文件夹 |
struct dirent *readdir(DIR *dp) | 读文件夹 |
int closedir(IDR *dp) | 关闭文件夹 |
int chdir(const char *pathname) | 改变工作目录 |
#include<stdio.h>
#include<string.h>
#include<dirent.h>
#include<errno.h>
#include<sys/stat.h>
#include<sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#define TEST_DIR "dir"
int main(int argc,char **argv)
{
int rv;
int fd1;
int fd2;
DIR *dirp = NULL;
struct dirent *direntp = NULL;
struct dirent b;
/*指针初始化*/
int a;
printf("1\n");
/*创建文件夹dir,并设置文件夹权限为755*/
if(a = (mkdir(TEST_DIR, 0755)) < 0)
{
printf("a:%d\n", a);
printf("2\n");
printf("create directory %s failure : %s\n", TEST_DIR, strerror(errno));
return -1;
}
printf("mkdir successfully!\n");
/*更改当前工作路径到文件夹dir下去*/
if(chdir(TEST_DIR)<0)
{
printf("Change directory to '%s' failure : %s\n",TEST_DIR,strerror(errno));
rv = -2;
goto cleanup;
}
printf("chdir successfully!\n");
/*在dir文件夹下创建普通文本文件file1.txt,并设置权限为644*/
if(fd1 = creat("file1.txt",0644)<0)
{
printf("Creat file1.txt failure : %s\n",strerror(errno));
rv = -3;
goto cleanup;
}
/*在dir文件夹下创建file2.txt,并设置文件权限为644*/
if(fd2 = creat("file2.txt",0644)<0)
{
printf("Creat file1.txt failure : %s\n",strerror(errno));
rv = -4;
goto cleanup;
}
printf("create successfully!\n");
/*更改当前工作路径到父目录*/
if(chdir("../") < 0)
{
printf("Change directory to '%s' failure : %s\n",TEST_DIR,strerror(errno));
rv = -5;
goto cleanup;
}
printf("chdir successfully!\n");
/*打开dir文件夹*/
if((dirp = opendir(TEST_DIR)) == NULL)
{
rv = -6;
printf("opendir '%s' error : %s\n",TEST_DIR,strerror(errno));
goto cleanup;
}
printf("opendir successfully!\n");
/*lie出dir文件夹里面的所有文件和文件夹*/
while((direntp = readdir(dirp)) != NULL)
{
printf("Find file : %s\n",direntp -> d_name);
}
/*关闭所有打开的文件夹*/
closedir(dirp);
cleanup:
if(fd1 >= 0)
{
close(fd1);
}
if(fd2 >= 0)
{
close(fd2);
}
}