Linux系统编程:简单文件IO操作

使用Linux的文件API,经常看见一个东西,叫做文件描述符。

什么是文件描述符?

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

(2)文件描述符就是用来区分一个程序打开的多个文件的。

(3)文件描述符的作用域就是当前进程,出了当前进程这个文件描述符就没有意义了

(4)文件描述符fd的合法范围是0或者一个正数,不可能是一个负数

(5)open返回的fd必须记录好,以后向这个文件的所有操作都要靠这个fd去对应这个文件,最后关闭文件时也需要fd去指定关闭这个文件。如果在我们关闭文件前fd丢了,那么这个文件就没法关闭了也没法读写了

1)打开与读取文件

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

int main(int argc, char const *argv[]) {

    int fd = -1; //文件描述符

    //打开文件
    fd = open( "ghostwu.txt", O_RDWR );

    if ( -1 == fd ) {
        printf("文件打开失败\n");
    }else {
        printf("文件打开成功,fd=%d\n", fd );
    }

    //读取文件
    int count = 0;
    char buf[20];
    count = read( fd, buf, 50 );
    if ( -1 == count ) {
        printf("文件读取失败\n");
    }else {
        printf("文件读取成功,实际读取的字节数目为:%d\n内容为%s\n", count, buf );
    }

    //关闭文件
    close( fd );

    return 0;
}

需要在当前目录下存在ghostwu.txt这个文件,否则打开的时候失败,这里涉及2个api

int open(const char *pathname, int flags);

open非常简单,第一个参数就是文件路径, 第二个是文件模式,在man手册中还提供了其他几种方式。

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

第一个参数为文件描述符,就是open返回的那个值

第二个参数buf用来存储从文件中读取的内容

第三个参数,表示希望从文件中读取的内容( 注:这个count数字可以随便给,最终以返回的实际数目(read的返回值)为准

2)打开与写入文件

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

int main(int argc, char const *argv[]) {

    int fd = -1; //文件描述符

    //打开文件
    fd = open( "ghostwu.txt", O_RDWR );

    if ( -1 == fd ) {
        printf("文件打开失败\n");
    }else {
        printf("文件打开成功,fd=%d\n", fd );
    }

    //写文件
    char buf[] = "I love Linux, Linux is very very good!!!";
    int count = 0;
    count = write( fd, buf, strlen( buf ) );
    if ( -1 == count ) {
        printf("文件写入失败\n");
    }else {
        printf("文件写入成功,实际写入的字节数目为:%d\n", count);
    }

    //关闭文件
    close( fd );

    return 0;
}

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

第一个参数为文件描述符,就是open返回的那个值

第二个参数buf用来存储写入的内容

第三个参数,表示希望写入的文件大小

3)open的一些flag参数

1,只读与只写权限

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

int main(int argc, char const *argv[]) {

    int fd = -1; //文件描述符

    //打开文件, O_RDONLY:只读权限,打开之后的文件只能读取,不能写入
    //打开文件, O_WRONLY:只写权限,打开之后的文件只能写入,不能读取
    // fd = open( "ghostwu.txt", O_RDONLY );
    fd = open( "ghostwu.txt", O_WRONLY );

    if ( -1 == fd ) {
        printf("文件打开失败\n");
    }else {
        printf("文件打开成功,fd=%d\n", fd );
    }

    //读取文件
    int count = 0;
    char buf[41];
    count = read( fd, buf, 38 );
    if ( -1 == count ) {
        printf("文件读取失败\n");
    }else {
        printf("文件读取成功,实际读取的字节数目为:%d\n内容为%s\n", count, buf );
    }

    //关闭文件
    close( fd );

    return 0;
}

2,清空与追加

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

int main(int argc, char const *argv[]) {

    int fd = -1; //文件描述符

    //打开文件
    //在O_RDWR模式下,对于一个已经存在的文件,且有内容,那么写入文件会覆盖对应大小的源文件内容【不是完全覆盖】
    // fd = open( "ghostwu.txt", O_RDWR );
    //在具有写入权限的文件中,使用O_TRUNC 会先把原来的内容清除,再写入新的内容
    // fd = open( "ghostwu.txt", O_RDWR | O_TRUNC );
    //在具有写入权限的文件中,使用O_APPEND 会把新内容追加到原来内容的后面
    // fd = open( "ghostwu.txt", O_RDWR | O_APPEND );

    //在具有写入权限的文件中,使用O_APPEND和O_TRUNC O_TRUNC起作用,会把原来的内容清除,再写入新的内容
    fd = open( "ghostwu.txt", O_RDWR | O_APPEND | O_TRUNC );

    if ( -1 == fd ) {
        printf("文件打开失败\n");
        return -1;
    }else {
        printf("文件打开成功,fd=%d\n", fd );
    }

    //写文件
    char buf[] = "new content";
    int count = 0;
    count = write( fd, buf, strlen( buf ) );
    if ( -1 == count ) {
        printf("文件写入失败\n");
        return -1;
    }else {
        printf("文件写入成功,实际写入的字节数目为:%d\n", count);
    }

    //关闭文件
    close( fd );

    return 0;
}

3,文件存在已否,创建文件与设置权限

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

int main(int argc, char const *argv[]) {

    int fd = -1;

    // fd = open( "ghostwu.txt", O_RDWR | O_CREAT | O_EXCL );

    /*
        文件不存在:
            创建这个文件 并打开成功
        文件存在:
            再次运行时(文件已经创建成功,存在了), 这时打开失败
    */
    // fd = open( "ghostwu.txt", O_RDWR | O_CREAT );

    fd = open( "ghostwu.txt", O_RDWR | O_CREAT | O_EXCL, 666 );

    if( -1 == fd ) {
        printf("文件打开失败,错误号:%d\n", errno );
        perror( "open" );
        return -1;
    }else {
        printf("文件打开成功\n");
    }

    close( fd );

    return 0;
}

上面用到了一个函数perror,errno和perror:

1)errno就是error number,意思就是错误号码。linux系统中对各种常见错误做了个编号,当函数执行错误时,函数会返回一个特定的errno编号来告诉我们这个函数到底哪里错了

2)errno是由操作系统来维护的一个全局变量,操作系统内部函数都可以通过设置errno来告诉上层调用者究竟刚才发生了一个什么错误

3)errno本身实质是一个int类型的数字,每个数字编号对应一种错误。当我们只看errno时只能得到一个错误编号数字,并不知道具体错在哪里,所以:linux系统提供了一个函数perror(意思print error),perror函数内部会读取errno并且将这个不好认的数字直接给转成对应的错误信息字符串,然后打印出来

4,lseek用来移动文件内部指针

简单应用:统计文件大小

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

int main(int argc, char const *argv[]) {

    if ( argc != 2 ) {
        printf("usage:%s %s\n", argv[0], "filename");
        return -1;
    }

    int fd = -1;

    fd = open( argv[1], O_RDWR );

    if( -1 == fd ) {
        printf("文件打开失败,错误号:%d\n", errno );
        perror( "open" );
        return -1;
    }else {
        printf("文件打开成功\n");
    }

    //把指针移动到文件末尾,就是文件的大小
    int count = lseek( fd, 0, SEEK_END );

    printf("文件大小为%d\n", count);

    close( fd );
    return 0;
}

------------------------------------------分割线------------------------------------------

一、同一个进程,多次打开同一个文件,然后读出内容的结果是: 分别读【我们使用open两次打开同一个文件时,fd1和fd2所对应的文件指针是不同的2个独立的指针】

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

int main(int argc, char const *argv[]) {

    int fd1 = -1;
    int fd2 = -1;
    char buf1[20] = {0};
    char buf2[20] = {0};
    int count1 = 0;
    int count2 = 0;

    fd1 = open( "ghostwu.txt", O_RDWR );

    if ( -1 == fd1 ) {
        printf("文件打开失败\n");
        perror( "open" );
        return -1;
    }else {
        printf("文件打开成功,fd1=%d\n", fd1);
    }

    count1 = read( fd1, buf1, 5 );
    if ( -1 == count1 ) {
        printf( "文件读取失败\n" );
        perror( "read" );
    }else {
        printf( "文件读取成功,读取的内容是%s\n", buf1 );
    }

    fd2 = open( "ghostwu.txt", O_RDWR );

    if ( -1 == fd1 ) {
        printf("文件打开失败\n");
        perror( "open" );
        return -1;
    }else {
        printf("文件打开成功,fd2=%d\n", fd1);
    }

    count2 = read( fd2, buf2, 10 );
    if ( -1 == count2 ) {
        printf( "文件读取失败\n" );
        perror( "read" );
    }else {
        printf( "文件读取成功,读取的内容是%s\n", buf2 );
    }

    close( fd1 );
    close( fd2 );

    return 0;
}

二、同一个进程,多次打开同一个文件,然后写入内容的结果是: 分别写,当使用O_APPEND,就是接着写

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

int main(int argc, char const *argv[]) {

    int fd1 = -1;
    int fd2 = -1;
    char buf1[] = "ghost";
    char buf2[] = "wu";
    int count1 = 0;
    int count2 = 0;

    fd1 = open( "ghostwu.txt", O_RDWR );

    if ( -1 == fd1 ) {
        printf("文件打开失败\n");
        perror( "open" );
        return -1;
    }else {
        printf("文件打开成功,fd1=%d\n", fd1);
    }

    count1 = write( fd1, buf1, strlen( buf1 ) );
    if ( -1 == count1 ) {
        printf( "文件写入失败\n" );
        perror( "write" );
    }else {
        printf( "文件写入成功,写入的内容是%s\n", buf1 );
    }

    fd2 = open( "ghostwu.txt", O_RDWR );

    if ( -1 == fd1 ) {
        printf("文件打开失败\n");
        perror( "open" );
        return -1;
    }else {
        printf("文件打开成功,fd2=%d\n", fd1);
    }

    count2 = write( fd2, buf2, strlen( buf2 ) );
    if ( -1 == count2 ) {
        printf( "文件写入失败\n" );
        perror( "write" );
    }else {
        printf( "文件写入成功,写入的内容是%s\n", buf2 );
    }

    close( fd1 );
    close( fd2 );

    return 0;
}

上面代码保持不变,再写入的时候加入flag标志:

fd1 = open( "ghostwu.txt", O_RDWR | O_APPEND );

fd2 = open( "ghostwu.txt", O_RDWR | O_APPEND );

三、 dup后的fd和原来打开文件的fd指向的是同一个文件,同时对这个文件写入时,是接着写

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

int main(int argc, char const *argv[]) {

    int fd1 = -1;
    int fd2 = -1;

    fd1 = open( "ghostwu.txt", O_RDWR );

    if ( -1 == fd1 ) {
        perror( "open" );
        return -1;
    }else {
        printf("文件打开成功:fd=%d\n", fd1);
    }

    //dup后的文件,同时write 是接着写入
    fd2 = dup( fd1 );
    printf("文件dup成功:fd=%d\n", fd2);

    //分别向fd1和fd2指向的文件写入

    char buf1[] = "ghost";
    char buf2[] = "wu";

    int count1 = -1, count2 = -1;

    while ( 1 ) {
        count1 = write( fd1, buf1, strlen( buf1 ) );
        if ( -1 == count1 ) {
            perror( "buf1->write" );
            return -1;
        }else {
            printf("buf1->文件写入成功\n");
        }

        sleep( 1 );

        count2 = write( fd2, buf2, strlen( buf2 ) );
        if ( -1 == count2 ) {
            perror( "buf2->write" );
            return -1;
        }else {
            printf("buf2->文件写入成功\n");
        }
    }

    close( fd1 );
    close( fd2 );
    return 0;
}

在linux系统中,内核占用了0、1、2这三个fd,当我们运行一个程序得到一个进程时,内部就默认已经打开了3个文件,

对应的fd就是0、1、2。分别叫stdin、stdout、stderr。也就是标准输入、标准输出、标准错误。接下来,我们把标准输出关闭,printf就不会输出,如果用dup复制原来的fd,那么新dup出来的fd就是1(对应标准输出)

之后标准输出的内容都会被写入到原来fd对应的那个文件

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

int main(int argc, char const *argv[]) {

    int fd = -1;

    fd = open( "ghostwu2.txt", O_RDWR );
    if ( -1 == fd ) {
        perror( "open" );
        return -1;
    }else {
        printf( "文件打开成功fd=%d\n", fd );
    }

    //fd=0 对应stdin  fd=1 对应 stdout  fd=2 对应stderror
    close( 1 ); //关闭fd=1的标准输出之后,printf输出看不见

    int newFd = -1;

    newFd = dup( fd ); //newFd一定是1, 因为分配后的fd从最小的没被占用的开始
    char buf[3];
    sprintf( buf, "%d", newFd ); //newFd转字符串型
    printf( "这是一段输出,由于newFd和fd关联到标准输出(newFd=1),会被写入到文件\n" );
    write( fd, buf, 1 );

    return 0;
}

相关推荐