Linux文件编程

Linux文件编程

1.文件的open/close

1.1🍟open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/*
open是一个系统函数, 只能在linux系统中使用, windows不支持
fopen 是标准c库函数, 一般都可以跨平台使用, 可以这样理解:
		- 在linux中 fopen底层封装了Linux的系统API open
		- 在window中, fopen底层封装的是 window 的 api
*/
// 打开一个已经存在的磁盘文件
int open(const char *pathname, int flags);
// 打开磁盘文件, 如果文件不存在, 就会自动创建
int open(const char *pathname, int flags, mode_t mode);

参数介绍

参数介绍:

  1. pathname: 被打开的文件文件名

  2. flags: 使用什么方式打开指定的文件,这个参数对应一些宏值,需要根据实际需求指定

    ​ 必须要指定的属性, 以下三个属性不能同时使用, 只能任选其一

    • ​ *️⃣O_RDONLY: 以只读方式打开文件

    • ​ *️⃣O_WRONLY: 以只写方式打开文件

    • ​ *️⃣O_RDWR: 以读写方式打开文件

    ​ 可选属性, 和上边的属性一起使用

    • ​ *️⃣O_APPEND: 新数据追加到文件尾部, 不会覆盖文件的原来内容

    • ​ *️⃣O_CREAT: 如果文件不存在, 创建该文件, 如果文件存在什么也不做

    • ​ *️⃣O_EXCL: 检测文件是否存在, 必须要和 O_CREAT 一起使用, 不能单独使用: O_CREAT | O_EXCL

    ​ 检测到文件不存在, 创建新文件

    ​ 检测到文件已经存在, 创建失败, 函数直接返回-1(如果不添加这个属性,不会返回-1)

  3. mode: 在创建新文件的时候才需要指定这个参数的值,用于指定新文件的权限,这是一个八进制的整数

​ 这个参数的最大值为:0777

创建的新文件对应的最终实际权限, 计算公式: (mode & ~umask)

​ umask 掩码可以通过 umask 命令查看

$ umask
0002

​ 假设 mode 参数的值为 0777, 通过计算得到的文件权限为 0775

# umask(文件掩码):  002(八进制)  = 000000010 (二进制)  
# ~umask(掩码取反): ~000000010 (二进制) = 111111101 (二进制)  
# 参数mode指定的权限为: 0777(八进制) = 111111111(二进制)
# 计算公式: mode & ~umask
             111111111
       &     111111101
            ------------------
             111111101    二进制
            ------------------
             mod = 0775   八进制  

​ 返回值:

​ 成功: 返回内核分配的文件描述符, 这个值被记录在内核的文件描述符表中,这是一个大于0的整数
​ 失败: -1

1.2 🍳close

​ 通过open函数可以让内核给文件分配一个文件描述符, 如果需要释放这个文件描述符就需要关闭文件。对应的这个系统函数叫做 close,函数原型如下:

#include <unistd.h>

int close(int fd);
  • ​ *️⃣函数参数: fd 是文件描述符, 是open() 函数的返回值

  • ​ *️⃣函数返回值: 函数调用成功返回值 0, 调用失败返回 -1

1.3 打开已存在的文件

​ 我们可以使用open()函数打开一个本地已经存在的文件, 假设我们想要读写这个文件, 操作代码如下:

// open.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main()
{
    // 打开文件
    int fd = open("abc.txt", O_RDWR);
    if(fd == -1)
    {
        printf("打开文件失败\n");
    }
    else
    {
        printf("fd: %d\n", fd);
    }

    close(fd);
    return 0;
}

编译并执行程序

$ gcc open.c 
$ ./a.out 
fd: 3		# 打开的文件对应的文件描述符值为 3
1.4 创建新文件

如果要创建一个新的文件,还是使用 open 函数,只不过需要添加 O_CREAT 属性, 并且给新文件指定操作权限。

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

int main()
{
    // 创建新文件
    int fd = open("./new.txt", O_CREAT|O_RDWR, 0664);
    if(fd == -1)
    {
        printf("打开文件失败\n");
    }
    else
    {
        printf("创建新文件成功, fd: %d\n", fd);
    }

    close(fd);
    return 0;
}
$ gcc open1.c 
$ ./a.out 
创建新文件成功, fd: 3

​ 假设在创建新文件的时候, 给 open 指定第三个参数指定新文件的操作权限, 文件也是会被创建出来的, 只不过新的文件的权限可能会有点奇怪, 这个权限会随机分配而且还会出现一些特殊的权限位, 如下:

$ $ ll new.txt 
-r-x--s--T 1 robin robin 0 Jan 30 16:17 new.txt*   # T 就是一个特殊权限
1.5 文件状态判断

在创建新文件的时候我们还可以通过 O_EXCL进行文件的检测, 具体处理方式如下:

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

int main()
{
    // 创建新文件之前, 先检测是否存在
    // 文件存在创建失败, 返回-1, 文件不存在创建成功, 返回分配的文件描述符
    int fd = open("./new.txt", O_CREAT|O_EXCL|O_RDWR);
    if(fd == -1)
    {
        printf("创建文件失败, 已经存在了, fd: %d\n", fd);
    }
    else
    {
        printf("创建新文件成功, fd: %d\n", fd);
    }

    close(fd);
    return 0;
}

编译并执行程序:

$ gcc open1.c 
$ ./a.out 
创建文件失败, 已经存在了, fd: -1

2.文件的write/read

2.1 ❤ read

read 函数用于读取文件内部数据,在通过 open 函数打开文件的时候需要指定读权限,函数原型如下:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

参数:

  1. fd: 文件描述符, open() 函数的返回值, 通过这个参数定位打开的磁盘文件

  2. buf: 是一个传出参数, 指向一块有效的内存, 用于存储从文件中读出的数据

    ​ 传出参数: 类似于返回值, 将变量地址传递给函数, 函数调用完毕, 地址中就有数据了

  3. count: buf指针指向的内存的大小, 指定可以存储的最大字节数

返回值:

  • ​ *️⃣大于0: 从文件中读出的字节数,读文件成功

  • ​ *️⃣等于0: 代表文件读完了,读文件成功

  • ​ *️⃣-1: 读文件失败了

2.2 💚write

​ write 函数用于将数据写入到文件内部,在通过 open 打开文件的时候需要指定写权限,函数原型如下:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

参数:

  1. fd: 文件描述符, open() 函数的返回值, 通过这个参数定位打开的磁盘文件
  2. buf: 指向一块有效的内存地址, 里边有要写入到磁盘文件中的数据
  3. count: 要往磁盘文件中写入的字节数, 一般情况下就是buf字符串的长度, strlen(buf)

返回值:

  • ​ *️⃣大于0: 成功写入到磁盘文件中的字节数
  • ​ *️⃣-1: 写文件失败了
2.3 文件拷贝

​ 假设有一个比较大的磁盘文件, 打开这个文件得到文件描述符fd1,然后在创建一个新的磁盘文件得到文件描述符fd2, 在程序中通过 fd1 将文件内容读出,并通过fd2将读出的数据写入到新文件中。

// 文件的拷贝
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main()
{
    // 1. 打开存在的文件english.txt, 读这个文件
    int fd1 = open("./english.txt", O_RDONLY);
    if(fd1 == -1)
    {
        perror("open-readfile");
        return -1;
    }

    // 2. 打开不存在的文件, 将其创建出来, 将从english.txt读出的内容写入这个文件中
    int fd2 = open("copy.txt", O_WRONLY|O_CREAT, 0664);
    if(fd2 == -1)
    {
        perror("open-writefile");
        return -1;
    }

    // 3. 循环读文件, 循环写文件
    char buf[4096];
    int len = -1;
    while( (len = read(fd1, buf, sizeof(buf))) > 0 )
    {
        // 将读到的数据写入到另一个文件中
        write(fd2, buf, len); 
    }
    // 4. 关闭文件
    close(fd1);
    close(fd2);

    return 0;
}

3.文件的lseek

系统函数 lseek 的功能是比较强大的, 我们既可以通过这个函数移动文件指针, 也可以通过这个函数进行文件的拓展。注意:lseek函数是用在Linux上的, 如果要使用标准C语言需要使用fseek函数才行.lseek这个函数的原型如下:

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

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

参数:

  1. fd: 文件描述符, open() 函数的返回值, 通过这个参数定位打开的磁盘文件

  2. offset: 偏移量,需要和第三个参数配合使用

  3. whence: 通过这个参数指定函数实现什么样的功能

    • *️⃣SEEK_SET: 从文件头部开始偏移 offset 个字节

    • *️⃣SEEK_CUR: 从当前文件指针的位置向后偏移offset个字节

    • *️⃣SEEK_END: 从文件尾部向后偏移offset个字节

返回值:

  • ​ 成功: 文件指针从头部开始计算总的偏移量
  • ​ 失败: -1。
3.1 🍚移动文件指针

通过对 lseek 函数第三个参数的设置, 经常使用该函数实现如下几个功能, 如下所示:

文件指针移动到文件头部

lseek(fd, 0, SEEK_SET);

得到当前文件指针的位置

lseek(fd, 0, SEEK_CUR); 

得到文件总大小

lseek(fd, 0, SEEK_END);

🍎🍎注意 注意 注意🍏🍏注意 注意 注意🍎🍎注意 注意 注意🍏🍏注意 注意 注意🍎🍎🍏🍏注意 注意 注意🍎🍎

使用lseek获取文件大小后,如果要通过**read()**函数读取文件内容到内存中,需要将文件指针挪到开头–>lseek(fd, 0, SEEK_SET);

使用lseek获取文件大小后,如果要通过**read()**函数读取文件内容到内存中,需要将文件指针挪到开头–>lseek(fd, 0, SEEK_SET);

使用lseek获取文件大小后,如果要通过**read()**函数读取文件内容到内存中,需要将文件指针挪到开头–>lseek(fd, 0, SEEK_SET);

3.2 🥚文件拓展

​ 假设使用一个下载软件进行一个大文件下载,但是磁盘很紧张,如果不能马上将文件下载到本地,磁盘空间就可能被其他文件占用了,导致下载软件下载的文件无处存放。那么这个文件怎么解决呢?

我们可以在开始下载的时候先进行文件拓展,将一些字符写入到目标文件中,让拓展的文件和即将被下载的文件一样大,这样磁盘空间就被成功抢到手,软件就可以慢悠悠的下载对应的文件了。

​ 👀注意

​ 使用 lseek 函数进行文件拓展必须要满足一下条件:

文件指针必须要偏移到文件尾部之后, 多出来的就需要被填充的部分。
文件拓展之后,必须要使用 **write()**函数进行一次写操作(写什么都可以,没有字节数要求)。

文件拓展举例:

// lseek.c
// 拓展文件大小
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    int fd = open("hello.txt", O_RDWR);
    if(fd == -1)
    {
        perror("open");
        return -1;
    }

    // 文件拓展, 一共增加了 1001 个字节
    lseek(fd, 1000, SEEK_END);
    write(fd, " ", 1); // 上面说的必须写一点数据
        
    close(fd);
    return 0;
}

查看执行执行的效果

# 编译程序 
$ gcc lseek.c

# 查看目录文件信息
$ ll
-rwxrwxr-x 1 robin robin 8808 May  6  2019 a.out*
-rwxrwxr-x 1 robin robin 1013 May  6  2019 hello.txt*
-rw-rw-r-- 1 robin robin  299 May  6  2019 lseek.c

# 执行程序, 拓展文件
$ ./a.out 

# 在查看目录文件信息
$ ll
-rwxrwxr-x 1 robin robin 8808 May  6  2019 a.out*
-rwxrwxr-x 1 robin robin 2014 Jan 30 17:39 hello.txt*   # 大小从 1013 -> 2014, 拓展了1001字节
-rw-rw-r-- 1 robin robin  299 May  6  2019 lseek.c

4.truncate/ftruncate

truncate/ftruncate 这两个函数的功能是一样的,可以对文件进行拓展也可以截断文件。使用这两个函数拓展文件比使用lseek要简单。这两个函数的函数原型如下:

// 拓展文件或截断文件
#include <unistd.h>
#include <sys/types.h>

int truncate(const char *path, off_t length);
	
int ftruncate(int fd, off_t length);

参数:

  1. path: 要拓展/截断的文件的文件名
  2. fd: 文件描述符, open() 得到的
  3. length: 文件的最终大小

文件原来size > length,文件被截断, 尾部多余的部分被删除, 文件最终长度为length
文件原来size < length,文件被拓展, 文件最终长度为length
​ 返回值: 成功返回0; 失败返回值-1

​ 👀注意

truncate() 和 ftruncate() 两个函数的区别在于一个使用文件名一个使用文件描述符操作文件, 功能相同。

不管是使用这两个函数还是使用 lseek() 函数拓展文件,文件尾部填充的字符都是 0。

5.perror

​ 在查看Linux系统函数的时候, 我们可以发现一个规律: 大部分系统函数的返回值都是整形,并且通过这个返回值来描述系统函数的状态(调用是否成功了)。在man 文档中关于系统函数的返回值大部分时候都是这样描述的:

RETURN VALUE
       On  success,  zero is returned.  On error, -1 is returned, and errno is set
       appropriately.
       
       如果成功,则返回0。出现错误时,返回-1,并给errno设置一个适当的值。

​ 👀注意

errno是一个全局变量,只要调用的Linux系统函数有异常(返回-1), 错误对应的错误号就会被设置给这个全局变量。这个错误号存储在系统的两个头文件中:

  • /usr/include/asm-generic/errno-base.h
  • /usr/include/asm-generic/errno.h

得到错误号,去查询对应的头文件是非常不方便的,我们可以通过 perror 函数将错误号对应的描述信息打印出来

#include <stdio.h>
// 参数, 自己指定这个字符串的值就可以, 指定什么就会原样输出, 除此之外还会输出错误号对应的描述信息
void perror(const char *str);	

举例: 使用 perrno 打印错误信息

// open.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    int fd = open("hello.txt", O_RDWR|O_EXCL|O_CREAT, 0777);
    if(fd == -1)
    {
        perror("open");
        return -1;
    }
        
    close(fd);
    return 0;
}

编译并执行程序

$ gcc open.c
$ $ ./a.out 
open: File exists	# 通过 perror 输出的错误信息

6.错误号

为了方便查询, 特将全局变量 errno 和错误信息描述的对照关系贴出:

6.1 Part1

信息来自头文件: /usr/include/asm-generic/errno-base.h

#define EPERM            1      /* Operation not permitted */
#define ENOENT           2      /* No such file or directory */
#define ESRCH            3      /* No such process */
#define EINTR            4      /* Interrupted system call */
#define EIO              5      /* I/O error */
#define ENXIO            6      /* No such device or address */
#define E2BIG            7      /* Argument list too long */
#define ENOEXEC          8      /* Exec format error */
#define EBADF            9      /* Bad file number */
#define ECHILD          10      /* No child processes */
#define EAGAIN          11      /* Try again */
#define ENOMEM          12      /* Out of memory */
#define EACCES          13      /* Permission denied */
#define EFAULT          14      /* Bad address */
#define ENOTBLK         15      /* Block device required */
#define EBUSY           16      /* Device or resource busy */
#define EEXIST          17      /* File exists */
#define EXDEV           18      /* Cross-device link */
#define ENODEV          19      /* No such device */
#define ENOTDIR         20      /* Not a directory */
#define EISDIR          21      /* Is a directory */
#define EINVAL          22      /* Invalid argument */
#define ENFILE          23      /* File table overflow */
#define EMFILE          24      /* Too many open files */
#define ENOTTY          25      /* Not a typewriter */
#define ETXTBSY         26      /* Text file busy */
#define EFBIG           27      /* File too large */
#define ENOSPC          28      /* No space left on device */
#define ESPIPE          29      /* Illegal seek */
#define EROFS           30      /* Read-only file system */
#define EMLINK          31      /* Too many links */
#define EPIPE           32      /* Broken pipe */
#define EDOM            33      /* Math argument out of domain of func */
#define ERANGE          34      /* Math result not representable */

6.2 Part2

信息来自头文件: /usr/include/asm-generic/errno.h

#define EDEADLK         35      /* Resource deadlock would occur */
#define ENAMETOOLONG    36      /* File name too long */
#define ENOLCK          37      /* No record locks available */

/*
 * This error code is special: arch syscall entry code will return
 * -ENOSYS if users try to call a syscall that doesn't exist.  To keep
 * failures of syscalls that really do exist distinguishable from
 * failures due to attempts to use a nonexistent syscall, syscall
 * implementations should refrain from returning -ENOSYS.
 */
#define ENOSYS          38      /* Invalid system call number */

#define ENOTEMPTY       39      /* Directory not empty */
#define ELOOP           40      /* Too many symbolic links encountered */
#define EWOULDBLOCK     EAGAIN  /* Operation would block */
#define ENOMSG          42      /* No message of desired type */
#define EIDRM           43      /* Identifier removed */
#define ECHRNG          44      /* Channel number out of range */
#define EL2NSYNC        45      /* Level 2 not synchronized */
#define EL3HLT          46      /* Level 3 halted */
#define EL3RST          47      /* Level 3 reset */
#define ELNRNG          48      /* Link number out of range */
#define EUNATCH         49      /* Protocol driver not attached */
#define ENOCSI          50      /* No CSI structure available */
#define EL2HLT          51      /* Level 2 halted */
#define EBADE           52      /* Invalid exchange */
#define EBADR           53      /* Invalid request descriptor */
#define EXFULL          54      /* Exchange full */
#define ENOANO          55      /* No anode */
#define EBADRQC         56      /* Invalid request code */
#define EBADSLT         57      /* Invalid slot */

#define EDEADLOCK       EDEADLK

#define EBFONT          59      /* Bad font file format */
#define ENOSTR          60      /* Device not a stream */
#define ENODATA         61      /* No data available */
#define ETIME           62      /* Timer expired */
#define ENOSR           63      /* Out of streams resources */
#define ENONET          64      /* Machine is not on the network */
#define ENOPKG          65      /* Package not installed */
#define EREMOTE         66      /* Object is remote */
#define ENOLINK         67      /* Link has been severed */
#define EADV            68      /* Advertise error */
#define ESRMNT          69      /* Srmount error */
#define ECOMM           70      /* Communication error on send */
#define EPROTO          71      /* Protocol error */
#define EMULTIHOP       72      /* Multihop attempted */
#define EDOTDOT         73      /* RFS specific error */
#define EBADMSG         74      /* Not a data message */
#define EOVERFLOW       75      /* Value too large for defined data type */
#define ENOTUNIQ        76      /* Name not unique on network */
#define EBADFD          77      /* File descriptor in bad state */
#define EREMCHG         78      /* Remote address changed */
#define ELIBACC         79      /* Can not access a needed shared library */
#define ELIBBAD         80      /* Accessing a corrupted shared library */
#define ELIBSCN         81      /* .lib section in a.out corrupted */
#define ELIBMAX         82      /* Attempting to link in too many shared libraries */
#define ELIBEXEC        83      /* Cannot exec a shared library directly */
#define EILSEQ          84      /* Illegal byte sequence */
#define ERESTART        85      /* Interrupted system call should be restarted */
#define ESTRPIPE        86      /* Streams pipe error */
#define EUSERS          87      /* Too many users */
#define ENOTSOCK        88      /* Socket operation on non-socket */
#define EDESTADDRREQ    89      /* Destination address required */
#define EMSGSIZE        90      /* Message too long */
#define EPROTOTYPE      91      /* Protocol wrong type for socket */
#define ENOPROTOOPT     92      /* Protocol not available */
#define EPROTONOSUPPORT 93      /* Protocol not supported */
#define ESOCKTNOSUPPORT 94      /* Socket type not supported */
#define EOPNOTSUPP      95      /* Operation not supported on transport endpoint */
#define EPFNOSUPPORT    96      /* Protocol family not supported */
#define EAFNOSUPPORT    97      /* Address family not supported by protocol */
#define EADDRINUSE      98      /* Address already in use */
#define EADDRNOTAVAIL   99      /* Cannot assign requested address */
#define ENETDOWN        100     /* Network is down */
#define ENETUNREACH     101     /* Network is unreachable */
#define ENETRESET       102     /* Network dropped connection because of reset */
#define ECONNABORTED    103     /* Software caused connection abort */
#define ECONNRESET      104     /* Connection reset by peer */
#define ENOBUFS         105     /* No buffer space available */
#define EISCONN         106     /* Transport endpoint is already connected */
#define ENOTCONN        107     /* Transport endpoint is not connected */
#define ESHUTDOWN       108     /* Cannot send after transport endpoint shutdown */
#define ETOOMANYREFS    109     /* Too many references: cannot splice */
#define ETIMEDOUT       110     /* Connection timed out */
#define ECONNREFUSED    111     /* Connection refused */
#define EHOSTDOWN       112     /* Host is down */
#define EHOSTUNREACH    113     /* No route to host */
#define EALREADY        114     /* Operation already in progress */
#define EINPROGRESS     115     /* Operation now in progress */
#define ESTALE          116     /* Stale file handle */
#define EUCLEAN         117     /* Structure needs cleaning */
#define ENOTNAM         118     /* Not a XENIX named type file */
#define ENAVAIL         119     /* No XENIX semaphores available */
#define EISNAM          120     /* Is a named type file */
#define EREMOTEIO       121     /* Remote I/O error */
#define EDQUOT          122     /* Quota exceeded */

#define ENOMEDIUM       123     /* No medium found */
#define EMEDIUMTYPE     124     /* Wrong medium type */
#define ECANCELED       125     /* Operation Canceled */
#define ENOKEY          126     /* Required key not available */
#define EKEYEXPIRED     127     /* Key has expired */
#define EKEYREVOKED     128     /* Key has been revoked */
#define EKEYREJECTED    129     /* Key was rejected by service */

/* for robust mutexes */
#define EOWNERDEAD      130     /* Owner died */
#define ENOTRECOVERABLE 131     /* State not recoverable */

#define ERFKILL         132     /* Operation not possible due to RF-kill */

#define EHWPOISON       133     /* Memory page has hardware error */

7.模仿Linux的cp命令

// 自定义模仿cp拷贝命令 mcp.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    if(argc != 3)
    {
        printf("参数个数错误");
        exit(-1);        
    }
	char* SrcFileName = argv[1];
	char* DesFileName = argv[2];
	int fdSrc = open(SrcFileName, O_RDWR);
	int fdDes = open(DesFileName, O_RDWR | O_CREAT | O_TRUNC, 0600);
	// 1. 读取已存在的文件
	if(fdSrc == -1)
	{
		// 打开文件失败
		printf("源文件读取错误\n");
		perror("Error");
		exit(-1);
	}
	if(fdDes == -1)
	{
		// 创建文件失败
		printf("目标文件读取错误\n");
		perror("Error");
		exit(-1);
	}
	// 2. 获取源文件大小
	int srcSize = lseek(fdSrc,0, SEEK_END);
	// 注意文件偏移指针挪回去
	lseek(fdSrc, 0, SEEK_SET);
	if(srcSize == -1)
	{
		// 文件大小获取失败
		printf("文件大小获取失败\n");
		perror("Error");
		exit(-1);
	}
	// 3. 保存源文件内容--字符
	char *srcCount = NULL;
	srcCount = (char*)malloc(sizeof(char) * (srcSize + 1));

	int srcReadSize = read(fdSrc, srcCount, srcSize);
	if(srcReadSize == -1)
	{
		// 文件读取失败
		printf("文件读取失败\n");
		perror("Error");
		exit(-1);
	}
	printf("开辟的空间大小:%d, 读取的数据的大小%ld\n",srcSize,strlen(srcCount));
	// 4. 写内容到目标文件
	if(srcReadSize >= 0)
	{
		int desWriteSize = write(fdDes,srcCount, strlen(srcCount) + 8);
		if(desWriteSize  == -1)
		{
			// 写目标文件错误
			printf("写目标文件错误\n");
			perror("Error");
			exit(-1);	
		}
	}
	// 5. 关闭文件
	close(fdSrc);
	close(fdDes);
	return 0;	
}

8.修改配置文件命令

// 自定义修改配置文件参数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    if(argc != 4)
    {
        printf("参数个数错误");
        exit(-1);
    }
    char* SrcFileName = argv[1];
    char* argKey = argv[2];
    char* argValue = argv[3];
    int fdSrc = open(SrcFileName, O_RDWR);
    // 1. 读取已存在的文件
    if(fdSrc == -1)
    {
        // 源文件读取错误
        printf("源文件读取错误\n");
        perror("Error");
        exit(-1);
    }
    // 2. 获取源文件大小
    int srcSize = lseek(fdSrc,0, SEEK_END);
    // 注意文件的指针挪回去
    lseek(fdSrc, 0, SEEK_SET);
    if(srcSize == -1)
    {
        // 文件大小获取失败
        printf("文件大小获取失败\n");
        perror("Error");
        exit(-1);
    }
    // 3. 保存源文件内容--字符
    char *srcCount = NULL;
    srcCount = (char*)malloc(sizeof(char) * (srcSize + 8));

    int srcReadSize = read(fdSrc, srcCount, srcSize);
    
    if(srcReadSize == -1)
    {
        // 文件读取失败
        printf("文件读取失败\n");
        perror("Error");
        exit(-1);
    }
    // 4. 查找需要key
    if(srcReadSize >= 0)
    {
        // 在srcCount中查找要替换文本的位置
        char *argKeyPos =  strstr(srcCount,argKey);
        if(argKeyPos != NULL)
        {
            // 找到argKey的位置
            // key=value的样子
            // 4.1 写数据到指针后面的值
            char *pos = argKeyPos;
            // 4.2 指针挪动到=后面
            pos = pos + strlen(argKey) + 1;
            // 4.3 循环写argValue里面的值
            char* tmp = argValue;
            while(*tmp != '\0')
             {
                *pos = *argValue;
                pos++;
                tmp++;
            }

            // 文件指针挪动到头部
            lseek(fdSrc, 0, SEEK_SET);
            // 清空源文件数据
            int clearRes = ftruncate(fdSrc,srcSize);
            if(clearRes == -1)
            {
                // 清空文件错误
                printf("清空文件错误\n");
                perror("Error");
                exit(-1);
            }else
            {
                // 写数据到文件
                int writeFlag = write(fdSrc, srcCount,srcReadSize);
                if(writeFlag == -1)
                {
                    // 写文件错误
                    printf("写文件错误\n");
                    perror("Error");
                    exit(-1);
                }
            }
        }else
        {
            printf("参数错误\n");
            perror("Error");
            exit(-1);
        }
    }
    // 5. 关闭文件
    close(fdSrc);
    return 0;
}
                                                                

配置文件Con.conf. 通过上面的命令修改配置文件里的值

配置文件如下:

										Con.conf											
ID=999
LIVE=5
HP=90
MP=78

执行编译后的代码

$ ./a.out Con.conf LIVE 9

执行该命令

$ cat Con.conf

查看修改后的结果

										Con.conf											
ID=999
LIVE=9
HP=90
MP=78

[注意] 目前只能修改一个字符, 如果输入多个字符,则会替换掉后面的字符

9.读取写入数字

// 将一个数字写进一个文件里
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>

void PrintTwoNumbers(int* num1, int* num2);

int main()
{
	int number1 = 99;
	int number2 = -1;
	PrintTwoNumbers(&number1, &number2);

	int fdWrite;// 写数据的文件描述符
	int fdRead; // 读数据的文件描述符
	char *fileName = "./test.txt";
	// 打开文件
	fdWrite = open(fileName, O_RDWR | O_CREAT, 0600);

	if(fdWrite == -1)
	{
		// 文件打开错误
		printf("文件打开错误\n");
		perror("Error");
		exit(-1);
	}
	if(fdWrite > 0)
	{
		// number写到文件中去
		int n_write = write(fdWrite,&number1,sizeof(int));
		if(n_write == -1)
		{
			printf("数字写入失败\n");
			perror("Error");
			exit(-1);
		}
		if(n_write > 0)
		{
			printf("数字写入成功\n");
			close(fdWrite);
			// 将数据读出来
			int fdRead = open(fileName, O_RDONLY, 0600);
			if(fdRead == -1)
			{
				// 文件打开失败
				printf("文件打开失败\n");
				perror("Error");
				exit(-1);
			}
			if(fdRead > 0)
			{
				// 读取内容
				int n_read = read(fdRead, &number2,sizeof(int));
				if(n_write == -1)
				{
					// 读取失败
					printf("读取文件失败\n");
					perror("Error");
					exit(-1);
				}
				if(n_read >= 0)
				{
					// 文件读取成功
					 PrintTwoNumbers(&number1, &number2);
				}
			}
		}
	
	}
	close(fdWrite);
	close(fdRead);
}

void PrintTwoNumbers(int* num1, int* num2)
{
	printf("num1 = %d, num2 = %d\n", *num1, *num2);
}



10.读取/写入结构体

既然 read函数和write函数的第二个参数都是 void buf, 那么我们在读写文件的时候, 可以传递任意类型*的数据都可,即 数字,字符,字符串,结构体等.

read函数的原型:

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

write函数的原型:

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

​ 所以,我们构造一个结构体Person, 结构体Person中有三个成员变量:mName, mAge, mMoney分别表示Person的姓名,年龄和金钱. 结构体Person中还有一个函数指针, pInfo, 该函数指针在指向函数printInfo用于输出Person的基本信息.

struct Person
{
	char * mName;
	unsigned int mAge;
	int mMoney;
	
	void (*pInfo)(char*, unsigned int, int);// 打印基本信息
};

void printInfo(char* name, unsigned int age, int money)
{
	printf("name = %s, age = %d, money = %d\n",name, age, money);
}

​ 下面是main函数的具体代码.

// 将结构体写入到文件中去
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
//	上面的结构体相关的代码放在这里

int main()
{
	// 创建结构体
	struct Person pWrite = {"Mr.Justin", 23, 999 };
	pWrite.pInfo = printInfo;
	printf("结构体pWrite:");
	pWrite.pInfo(pWrite.mName, pWrite.mAge, pWrite.mMoney);
	
	struct Person pRead;

	// 打开文件
	int fd;
	char* fileName = "Test.txt";
	fd = open(fileName, O_RDWR | O_CREAT, 0600);
	if(fd == -1)
	{
		// 打开文件失败
		perror("Error");
		exit(-1);
	}
	if(fd > 0)
	{
		// 文件打开成功
		// 写结构体到文件
		int n_write = write(fd, &pWrite, sizeof(struct Person));
		if(n_write == -1)
		{
			printf("写入文件失败\n");
			perror("Error");
			exit(-1);
		}
		if(n_write > 0)
		{
			// 结构体写入成功
			printf("结构体写入成功\n");
			// 读取结构提数据到内存
			// 文件指针挪到开头
			lseek(fd, 0, SEEK_SET);
			int n_read = read(fd,  &pRead, sizeof(struct Person));
			if(n_read == -1)
			{
				// 读取文件失败
				printf("文件读取失败\n");
				perror("Error");
				exit(-1);	
			}
			if(n_read >= 0)
			{
				// 读取文件成功
				printf("从文件读取结构体-pRead:");
				pRead.pInfo(pRead.mName, pRead.mAge, pRead.mMoney);
			}
		}
	}
    // 关闭文件描述符
	close(fd);
	return 0;
}

代码运行效果

$ gcc WriteStruct.c
$ ./a.out
									
> 结构体pWrite:name = Mr.Justin, age = 23, money = 999
> 结构体写入成功
> 从文件读取结构体-pRead:name = Mr.Justin, age = 23, money = 999

Test.txt中的内容人是看不懂的,看起来像乱码,然而是程序写和程序读的, 所以只有程序看得懂,通过vim打开Test.txt发现是这样的数据:

Hm^UnU^@^@^@^W^@^@^@ç^C^@^@4j^UnU^@^@^@

通过ls-l命令 查看具体的文件大小,发现写入的文件Test.c的大小是24个字节, 这和我们的结构体Person的大小是一样的,注意结构体的内存对齐问题!

$ ls -l

> -rwxrwxr-x 1 orangepi orangepi 13592 Aug  9 03:54 a.out
> -rw------- 1 orangepi orangepi    24 Aug  9 04:10 Test.txt
> -rw-rw-r-- 1 orangepi orangepi  1643 Aug  9 03:54 WriteStruct.c

11.C标准读写文件

​ 在linux读写文件使用的是read()和write()函数, 而标准的C语言规范使用的是fread和fwrite来读写文件, 使用方法类似, 只是需要注意的是C标准读写文件是通过文件IO流来实现的而不是文件描述符fd.

​ 下面的代码是向文件中读写结构体, 结构体代码如下

struct Person
{
	char * mName; //姓名
	unsigned int mAge; // 年龄
	int mMoney; // 金钱
	void (*pInfo)(char*, unsigned int*, int*); // 输出个人信息的函数指针

};

void printInfo(char* name, unsigned int *age, int *money)
{
	if(*age > 18)
	{
		*money += 2000; // if判断是为了验证是否将结构体从文件中读取出来
	}
	printf("name=%s, age=%d, money=%d\n",name, *age, *money);

}

main函数代码

										FRW2.c
// C标准文件流的读写
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define SIZE	1024

// 结构体相关代码放在这里

int main()
{
	char *fileName = "./Test.txt"; // 文件路径
	char *openType = "w+";	// 打开文件的方式
	FILE *fp; // 文件流
	char readBuf[SIZE] = { 0 };

	// 定义两个结构体
	struct Person Adam = {
		.mName = "Adam",
		.mAge = 16,
		.mMoney = 1024,
		.pInfo=printInfo
	};
	// 打印Adam的数据
	Adam.pInfo(Adam.mName, &(Adam.mAge), &(Adam.mMoney));
	struct Person Bob; // Bob结构体数据
	
	// 打开文件
	fp = fopen(fileName,openType);

	// 写数据--Adam的数据写入
	fwrite(&Adam, 1, sizeof(struct Person), fp);// 最好是这么写, 因为fwrite返回的结果是写入成功的元素个数
	fseek(fp, 0, SEEK_SET);
	// 读数据
	int n_read = fread(&Bob, 1, sizeof(struct Person), fp);
	if(n_read > 0)
	{
		// 修改前
		Bob.pInfo(Bob.mName,&(Bob.mAge), &(Bob.mMoney));
        // 修改读取出来的结构体
		Bob.mName = "Bob";
		Bob.mAge+=10;
		// 修改后
		Bob.pInfo(Bob.mName,&(Bob.mAge), &(Bob.mMoney));
	}

	// 关闭文件
	fclose(fp);

}

编译后运行结果, 表明可以将结构体写入到文件中,然后通过文件夹读取出来到内存里

$ gcc FRW2.c
$ ./a.out
> name=Adam, age=16, money=1024
> name=Adam, age=16, money=1024
> name=Bob, age=26, money=3024

部分内容来自爱编程的大丙,大丙老师超级好的,欢迎大家访问大丙老师的博客网站.
点击访问:爱编程的大丙,大丙老师的博客

  • 34
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值