Bootstrap

Linux学习之文件IO

文件扩展名

在Linux中,扩展名对Linux内核没有实际意义,但是可以用来人为区分不同的文件,方便用户使用。

.tar, .tar.gz, .tgz, .zip, .tar.bz表示压缩文件,创建命令为tar, gzip, unzip等
.sh 文件表示shell脚本文件
.pl 表示perl语言文件
.py 表示python语言文件
.conf 表示系统服务的配置文件
.c 表示C文件
.h 头文件
.cpp 表示C++源文件
.so 表示动态库文件
.a 表示静态库文件

文件类型

Linux系统中把一切都看做文件,Linux有7中类型文件:普通文件-、目录(dierectory)文件、符号(link)链接、字符
(character)设备文件、块(block)设备文件、管道(pipe)文件、套接字(socket)文件。其中文件、目录、符号链接会占用磁盘
空间来存储,而块设备、字符设备、套接字、管道是伪文件,并不占用磁盘空间。
文件类型标识 文件类型

--普通文件
d 目录文件
l 符号链接
c 字符设备
b 块设备
p 管道
s 套接字socket

在Linux命令行中,我们通过 ls -l 命令可以查看文件的相应类型。

文件描述符

在这里插入图片描述
示例代码

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

#define MSG_STR "hello world\n"

int main(int main, char **argv)
{
    printf("%s", MSG_STR);
    fputs(MSG_STR, stdout);
    write(STDOUT_FILENO, MSG_STR, strlen(MSG_STR));

    return 0;
}

运行结果
在这里插入图片描述

文件I/O操作

在开始讲解文件I/O之前,我们首先运行下面这个示例程序,该程序将调用open()系统调用打开一个叫做test.txt的文件(如
果不存在则会创建该文件),然后调用write()系统调用将字符串 MSG_STR 写入到该文件中,之后调用read()系统调用读出该
文件里的内容

#include <stdio.h>
#include <string.h>
#include <errno.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];

    fd = open("test.txt", O_RDWR|O_CREAT|O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("Open/Create file test.txt failure");
        return 0;
    }
    printf("Open file returned file descriptor [%d]\n", fd);

    if((rv = write(fd, MSG_STR, strlen(MSG_STR))) < 0)
    {
        printf("Write %d bytes into file failure: %s\n", rv, strerror(errno));
        goto cleanup;
    }
    //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;
}

运行结果
在这里插入图片描述
代码解析
在这里插入图片描述
在这里插入图片描述

现在我们可以看到运行结果中看到返回的文件描述符为3
但是为什么打印的文件内容是乱码呢?
我们可以定位到问题出现在buf中,因为打印的内容是buf中的内容。

根据文件内容可以知道在使用buf之前我们需要对buf中的内容进行初始化,即memset(buf, 0, sizeof(buf));

但是会发现,读取到0个字节
在这里插入图片描述
这是因为文件偏移量导致的

出错处理

出错处理
出错处理的两个函数

void perror(const char *s);    //perror只能打印输出到标准输出上,所以假如我们要将错误打印到文件就不可以用这个函数
char *strerror(int errno);
```c
示例代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    char    *file_name = "/test.txt";
    int    fd = -1;

    fd = open(file_name, O_RDONLY, 0666);
    if(fd < 0)
    {
        perror("Open file failure");
        printf("Open file %s failure:%s\n", file_name, strerror(errno));
        
        return 0;
    }
close(fd);
}

运行结果
在这里插入图片描述

文件I/O操作函数

  1. open()系统调用
int open(const char *path, int oflag, ... /*mode_t mode*/);

open()系统调用用来打开一个文件,并返回一个文件描述符(file description), 并且该文件描述符是当前进程最小、未使用的
文件描述符数值。
参数: path: 要打开的文件、设备的路径
oflag: 由多个选项进行“或”运算构造oflag参数 。
必选: O_RDONLY (只读)、 O_WRONLY(只写)、 O_RDWR(读写)
可选: O_APPEND 每次写时都追加到文件的尾端。
O_CREAT 文件不存在则创建它,使用该选项需要第三个参数mode
O_TRUNC 如果文件存在,而且为只写或读写成功打开,则将其长度截取为0;
O_NONBLOCK 如果path是一个FIFO、块设备、字符特殊文件则此选项为文件的本次打开和后续的I/O操作设置非阻塞模式方式。
O_EXEC、O_SEARCH、O_CLOEXEC、O_NOCTTY…
mode: oflag带O_CREAT选项时可以用来创建文件,这时必须带该参数用来指定创建文件的权限模式,如066。 否则不
需要。使用示例代码:

int fd;
fd = open(“text.txt”, O_RDWR|O_CREAT|O_TRUNC, 0666);
fd = open(“text.txt”, O_WRONLY|O_APPEND);
  1. create()系统调用
int creat(const char *path, mode_t mode);

此函数用来创建一个新文件并返回其fd。它等价于 open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);

int fd;
fd=creat(“text.txt”, 0644);
  1. close()系统调用
int close(int fd);

该函数用来关闭一个打开的文件描述符,关闭一个文件时还会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核将会自动关闭它所有打开的文件。
4. write()系统调用

ssize_t write(int fd, const void *buf, size_t nbytes);

write()函数用来往打开的文件描述符fd指向的文件中写入buf指向的数据,其中nbytes指定要写入的数据大小。如果返回值<0则说明写入出错,譬如尝试往一个只读的文件中写入则会抛错,错误的原因系统会保存到errno变量中去。如果>0则为实际写入的数据大小。
5. lseek()系统调用

off_t lseek(int fd, off_t offset, int whence);

我们在从文件里读出内容,或往文件写如内容的时候都有一个起始地址,这个起始地址就是当前文件偏移量,当我们对文件进行读写的时候都会使文件偏移量往后偏移。这点就类似于我们打开记事本开始编辑文本时的光标,我们读或写入时从光标

所在位置开始读写,每读写一个字节都会使光标往后偏移。通过lseek()这个函数我们可以调整文件偏移量的地址。
其中 whence 可以是以下三个值:

whence位置
SEEK_SET文件头
SEEK_CUR当前位置
SEEK_END文件尾

而offset就是相对于whence 的偏移量,譬如:
lseek(fd, 0, SEEK_SET); 将文件偏移量设置到了文件开始的第一个字节上;
lseek(fd, 0, SEEK_END); 将文件偏移量设置到文件最后一个字节上;
lseek(fd, -1, SEEK_END); 将文件偏移量设置到文件最后的倒数第一个字节上;

6.read()系统调用

ssize_t read(int fd, void *buf, size_t nbytes);

read()函数用来从打开的文件描述符对应的文件中读取数据放到buf指向的内存空间中去,最多不要超过nbytes个字节,这里的nbytes一般是buf剩余的空间大小。如read成功,则返回实际读到的字节数(由nbytes或读到文件尾决定,其中EOF宏用来判断是否到了文件尾),如果返回值小于0则表示出错,如尝试读一个没有权限读的文件时就会抛错。
7. dup() 和 dup2()系统调用

int dup(int fd);
int dup2(int fd, int fd2);

这两个函数都可以用来复制一个新的文件描述符来指向fd对应的文件。这两个系统调用经常用在标准输入、标准输出、标准出错重定向。
dup()返回的新文件描述符一定是当前可用文件描述符中的最小数值;
dup2可以用fd2参数来指定新的文件描述符。如果fd2已经打开,则先关闭。如fd等于fd2, 则dup2返回fd2, 而不关闭它。
示例代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <string.h>
#include <errno.h>
#include <unistd.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 ;
    }
    
    dup2(fd, STDIN_FILENO);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);

    printf("fd = %d\n", fd);

    close(fd);

    return 0;
}

运行结果
在这里插入图片描述
解析:

dup2(fd, STDIN_FILENO); //标准输入重定向到 std.txt 文件中去
dup2(fd, STDOUT_FILENO); //标准输出重定向到 std.txt 文件中去
dup2(fd, STDERR_FILENO); //标准出错重定向到 std.txt 文件中去
  1. stat()和fstat()系统调用
int stat(const char * restrict path, struct stat *restrict buf);
int fstat(int fd, struct stat *buf);

这两个函数都是用来返回文件或目录的相关信息,只是stat()的第一个参数是文件名,而fstat()的第一个参数是文件打开的相应文件描述符。其中struct stat结构体的定义如下:

struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};

在这里插入图片描述
示例代码:

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

#if 0
struct stat
{
    dev_t        st_dev;
    ino_t        st_ino;
    mode_t        st_mode;
    ulink_t        st_nlink;
    uid_t        st_uid;
    gid_t        st_gid;
    dev_t        st_rdev;
    off_t        st_size;
    blksize_t    st_blksize;
    blkcnt_t    st_blocks;
    time_t        st_atime;
    time_t        st_mtime;
    time_t        st_ctime;
} stbuf;
#endif

int main(int argc, char **argv)
{
    struct  stat  stbuf;

    stat("stat.c", &stbuf);
    printf("File Mode: %o  Real Size:%lu B, Space Size: %lu B\n", stbuf.st_mode, stbuf.st_size, stbuf.st_blksize);

    return 0;
}

运行结果
在这里插入图片描述

1. access()系统调用

int access(const char *path, int mode); //判断文件的权限位
R_OK 测试读许可权
W_OK 测试写许可权
X_OK 测试执行许可权
F_OK 测试文件是否存在

示例代码

#include <stdio.h> 
#include <unistd.h>

#define TEST_FILE "access.c"

int main(void)
{ 
    if( access(TEST_FILE, F_OK) != 0)
    {
        printf("File %s not exist\n", TEST_FILE);
        return 0;
    }

    printf("File %s exist\n", TEST_FILE);
    
    if( access(TEST_FILE, R_OK) != 0)
    {
        printf("READ OK\n");
    }

    if( access(TEST_FILE, W_OK) != 0)
    {
        printf("Write OK\n");
    }
    if( access(TEST_FILE, X_OK) != 0)
    {
        printf("EXEC OK\n");
    }

    return 0;
}

运行结果
在这里插入图片描述

在这里插入图片描述在这里插入图片描述
示例代码

示例代码
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <dirent.h>

#define TEST_DIR "dir"
int main(int argc, char **argv)
{
    int                rv;
    int                fd1;
    int                fd2;
    DIR                *dirp;
    struct dirent   *direntp;

    /*创建文件夹dir,并设置文件爱你家权限为755*/
    if( mkdir(TEST_DIR, 0755) < 0)
    {
        printf("create directory '%s'  failure: %s\n", TEST_DIR, strerror(errno));
        return -1;
    }
    /*更改当前工作路径到文件夹dir下去*/
    if( chdir(TEST_DIR) < 0)
    {
        printf("Change directory '%s' failure: %s\n", TEST_DIR, strerror(errno));
        rv = -2;
        return -2;
    }
    /*在dir文件夹下,创建普通文本文件file2.txt,并设置其权限位为644*/
    if( (fd2 = creat("file2.txt", 0644)) < 0)
    {
        printf("Create file2.txt failure: %s\n", strerror(errno));
        rv = -4;
        goto cleanup;
    }
    /*在dir文件夹下,创建普通文本文件file1.txt,并设置其权限位为644*/
    if( (fd1 = creat("file1.txt", 0644)) < 0)
    {
        printf("Create file1.txt failure: %s\n", strerror(errno));
        return -3;
        goto cleanup;
    }

    /*更改当前工作路径到父目录去*/
    if( chdir("../") < 0)
    {
        printf("Change directory to '%s' faulure: %s\n", TEST_DIR, strerror(errno));
        rv=-5;
        goto cleanup;
    }

    /*打开dir文件夹*/
    if((dirp = opendir(TEST_DIR)) == NULL)
    {
        rv = -6;
        printf("Open %s error: %s\n", TEST_DIR, strerror(errno));
        goto cleanup;
    }

    /*列出dir里面的所有文件和文件夹*/
    while((direntp = readdir(dirp)) != NULL)
    {
        printf("Find file:%s\n", direntp->d_name);
    }

cleanup:
    if(fd1 >= 0)
    {
        close(fd1);
    }
    
    if( fd2 >= 0)
    {
        close(fd2);
    }
}

运行结果
在这里插入图片描述
在这里插入图片描述

;