Linux 系统编程(二):文件、文件操作函数和相关实例

本篇文章主要简单介绍一些 Linux 系统编程中常用的函数,如open()close()read()write()等,然后使用这些函数实现一些常见的 Linux 命令,如morewhols等。这样一方面可以练习这些函数的使用,另一方面也能对这些 Linux 命令有更深的理解。

1 man 手册

在介绍函数之前,先介绍Linux man手册的用法。在各种Unix\Linux系统中都有各自的man手册,里面包括了各种系统命令、系统调用、库函数等的文档,并且对应地分成不同章节,功能十分强大。

使用方法如下

1
2
# 2 是对应章节号 open是要查询的函数或命令
man 2 open

不同章节对应的是不同类型的内容,因为有些系统调用的函数名和系统命令或其他章节中的内容重名,如系统命令write用于给另一个用户发送信息,而系统调用write()则是向文件中写入数据,所以在使用的时候最好加上对应的章节名。

输入man man可以查看各个章节内容对应如下

  • 1 - Executable programs or shell commands:系统命令
  • 2 - system calls(functions provided by the kernel):系统调用函数,
  • 3 - library calls(functions within program libraries):库函数
  • 4 - special file(usually found in /dev):特殊文件
  • 5 - file formats and convertions e.g /etc/passwd:文件格式
  • 6 - games for linux:由系统中的游戏来定义
  • 7 - Miscellaneous (including macro packages and conventions), e.g man(7), groff(7):附件和一些变量
  • 8 - System administration commands (usually only for root):系统管理的命令
  • 9 - Kernel routines [Non standard]:内核例程,非标准

不同系统显示的结果会略有差异,但前 8 个几乎都是大同小异的。

如果不知道想用函数的具体名称和章节,还可以-k选项通过关键字来查找相关函数。如我想查找可以读取目录的相关函数可以这样做

1
2
3
4
5
6
7
$ man -k read | grep directory
readdir (2) - read directory entry
readdir (3) - read a directory
readdir (3am) - directory input parser for gawk
readdir_r (3) - read a directory
# 通过结果知道readdir(3)很可能是需要的 再次使用man
$ man 3 readdir

PS:其实大部分时候都是用man来查看相应函数的头文件以及可选参数名称。

2 文件相关概念

在 Linux 系统中,一切皆文件,所以对于初学者来说,文件的打开关闭、读取写入应该是最基本、最常见的操作了。而在学习相关函数之前还需要对文件相关概念有个基本的认识。

2.1 文件的类型

Linux 下有七种文件类型,并用相应标识符来表示

文件类型标识 文件类型
- 普通文件
d 目录
l 符号链接
s(伪文件) 套接字
b(伪文件) 块设备
c(伪文件) 字符设备
p(伪文件) 管道

七种中仅有普通文件、目录、符号链接真正地占用磁盘空间,其余四种伪文件并不占用磁盘空间。

在使用ls -l命令查看当前目录下文件信息时,每行第一个字符即代表文件类型

1
2
3
4
5
6
7
8
9
$ ls -l
total 40
-rw-rw-rw- 1 choupin choupin 165 Feb 23 13:30 makefile
-rwxrwxrwx 1 choupin choupin 11576 Feb 23 14:05 mmap_r
-rw-rw-rw- 1 choupin choupin 784 Feb 23 14:05 mmap_r.c
-rwxrwxrwx 1 choupin choupin 11640 Feb 23 14:05 mmap_w
-rw-rw-rw- 1 choupin choupin 1143 Feb 23 14:05 mmap_w.c
-rw-rw-rw- 1 choupin choupin 24 Feb 23 14:05 test
-------rwx 1 choupin choupin 24 Feb 23 13:53 test.txt

2.2 文件描述符

文件描述符是用来帮助进程来对文件进行操作的,称为 File Descriptor,在代码中表现为一个int类型整数。

以下内容主要来自这里

在一个 Linux 进程启动后,会在内核空间中创建一个 PCB 控制块,PCB 内部有一个文件描述符表(File descriptor table),记录着当前进程所有可用的文件描述符,也即当前进程所有打开的文件。 这个 PCB 控制块在代码实现中表现为一个struct

除了文件描述符表,系统还需要维护另外两张表:

  • 打开文件表(open file table)
  • i-node 表(i-node table)

文件描述符表每个进程都有一个,打开文件表和 i-node 表整个系统只有一个,它们三者之间的关系如下图所示。

从本质上讲,这三种表都是结构体数组,0、1、2、73、1976 等都是数组下标。表头只是添加的注释,数组本身是没有的。实线箭头表示指针的指向,虚线箭头添加的注释。

所以文件描述符不过是一个数组的下标

通过文件描述符,可以找到文件指针,从而进入打开文件表。该表存储了以下信息:

  • 文件偏移量, 也就是文件内部指针偏移量。调用 read() 或者 write() 函数时,文件偏移量会自动更新,当然也可以使用 lseek() 直接修改。
  • 状态标志,比如只读模式、读写模式、追加模式、覆盖模式等
  • i-node 表指针。

然而要想真正读取文件,还需通过打开文件表的 i-node 指针进入 i-node 表,该表包含了诸如以下信息

  • 文件类型
  • 文件大小
  • 时间戳,包括创建时间、更新时间
  • 文件锁

对上图的进一步说明:

  • 在进程 A 中,文件描述符 1 和 20 都指向了同一个打开文件表项,标号为 23(指向了打开文件表中下标为 23 的数组元素),这可能是通过调用 dup()、dup2()、fcntl() 或者对同一个文件多次调用了 open() 函数形成的。
  • 进程 A 的文件描述符 2 和进程 B 的文件描述符 2 都指向了同一个文件,这可能是在调用 fork() 后出现的(即进程 A、B 是父子进程关系),或者是不同的进程独自去调用 open() 函数打开了同一个文件,此时进程内部的描述符正好分配到与其他进程打开该文件的描述符一样。
  • 进程 A 的描述符 0 和进程 B 的描述符 3 分别指向不同的打开文件表项,但这些表项均指向 i-node 表的同一个条目(标号为 1976);换言之,它们指向了同一个文件。发生这种情况是因为每个进程各自对同一个文件发起了 open() 调用。同一个进程两次打开同一个文件,也会发生类似情况。

对于实际编程中,我们需要特别关注两个点

  • 默认情况下,文件描述符范围为 0~1023,并每次默认使用表中可用的最小值
  • 0,1,2 默认被系统占用,0 对应标准输入STDIN_FILENO,1 对应标准输出STDOUT_FILENO,2 对应标准错误输出STDERR_FILENO

3 文件操作相关函数

这里先将文件操作相关函数做一个总结式的罗列介绍,相关 demo 放到下一章和后续文章中。

3.1 open()、creat()和 close()

1
2
3
4
5
6
7
8
#include <fcntl.h>
// 参数1是要打开的文件路径名称
// 参数2是打开方式,如O_RDONLY、O_WRONLY、O_RDWR,还可加入可选参数如O_CREATE
int open(const char *pathname, int flags);
// 参数3是在参数2使用了O_CREATE新建文件后,可设置新建文件的权限
int open(const char *pathname, int flags, mode_t mode);
// 等同于使用了O_CREATE参数的open()
int creat(const char *pathname, mode_t mode);

关于返回值, 成功返回对应的文件描述符,失败返回-1,并自动设置errno,所以在使用时,一般都要判断返回值。注意openflag参数中,读写方式是必选的,可选的参数包括了O_CREAT|O_APPEND|O_TRUNC|O_EXCL|O_NONBLOCK,详细说明见man手册。

打开的文件在程序结束前要使用close()来关闭。

1
2
3
#include <unistd.h>
// 唯一参数是要关闭的文件描述符
int close(int fd);

同样失败返回-1,成功的话返回 0。

3.2 read()和 write()

从文件中读取数据使用read()

1
2
3
4
5
#include <unistd.h>
// 参数1是读取文件的文件描述符
// 参数2是存放读取数据的缓存数组指针
// 参数3是一次读取的数据Byte数
ssize_t read(int fd, void *buf, size_t count);

成功的话返回实际读取的 byte 数,失败的话返回-1,并设置errno,若errno = EAGINEWOULDBLOCK, 说明不是read失败,而是read在以非阻塞方式读一个设备文件(网络文件),并且文件无数据。

阻塞与非阻塞是设备文件、网络文件的属性。在读取设备文件、网络文件时会产生阻塞,如终端文件/dev/tty和网络套接字。

可以使用open("dev/tty", O_RDWR | O_NONBLOCK)来设置非阻塞状态。

向文件中写入数据使用write()

1
2
3
4
5
#include <unistd.h>
// 参数1为写入文件的文件描述符
// 参数2为存放需写入数据的缓存数组指针
// 参数3位一次写入的数据byte数
ssize_t write(int fd, const void *buf, size_t count);

成功返回实际写入的 byte 数,失败时返回-1。

3.3 fcntl()

该函数可以根据文件描述符来获取和改变文件属性。称为文件控制函数。

1
2
3
4
5
#include <fcntl.h>
// 参数1为对应文件的文件描述符
// 参数2代表操作类型
// 可选参数根据参数2而相应设定
int fcntl(int fd, int cmd, ... /* arg */ );

该函数主要有 5 种功能:

  • 复制一个现有的描述符(cmd=F_DUPFD)
  • 获得/设置文件描述符标记(cmd=F_GETFD 或 F_SETFD)
  • 获得/设置文件状态标记(cmd=F_GETFL 或 F_SETFL)
  • 获得/设置异步 I/O 所有权(cmd=F_GETOWN 或 F_SETOWN)
  • 获得/设置记录锁(cmd=F_GETLK,F_SETLK 或 F_SETLKW)

参数比较复杂,后面用到时再做补充吧。

3.4 lseek()和 truncate()

lseek()函数用以改变打开文件的当前偏移量。

1
2
3
4
5
#include <unistd.h>
// 参数1为文件描述符
// 参数2为需要更改的偏移量,可取负值
// 参数3为更改的位置,SEEK_SET、SEEK_CUR、SEEK_END分别代表起始位置,当前位置以及末尾
off_t lseek(int fd, off_t offset, int whence)

成功的话返回较起始位置的偏移量,失败的话返回-1,设置errno

常用于获取、拓展文件大小,使用方法在 4.3 节。

注意两点

  • 文件读写操作使用的是同一偏移量
  • 要想真正拓展文件大小必须引起 IO 操作

而使用truncate()函数可以直接拓展文件大小

1
2
3
4
5
#include <sys/types.h>
// 参数1为文件路径,参数2为文件拓展后大小
int truncate(const char *path, off_t length);
// 参数1位文件描述符
int ftruncate(int fd, off_t length);

两个版本作用相同,只是接受参数不一样。成功返回 0,失败返回-1。

注意:如果函数设定的长度小于文件原本长度,那么超出部分将会被丢弃,反之填充\0

3.5 stat()和 lstat()

该函数用于获取文件状态,如文件大小、类型、权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <unistd.h>
// 一个用来存放文件状态的结构体
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
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; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */

struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */

#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
// 参数1为文件路径名,参数2为存放文件状态的结构体指针
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);

常使用struct statst_sizest_mode来获取文件的大小、类型和权限,st_mode的第一位就是文件类型,该成员使用位图的原理来存放类型和权限信息。后面有相关例子。

stat()lstat()的区别是一个会符号穿透,一个不会。即假如有一个符号链接file1,其指向文件file2,那么对file1使用stat()获取的是file2的状态,而使用lstat()的话获取的是符号链接file1的状态。

struct stat中有一个st_nlink成员代表该文件的链接数。在文件刚被open()creat()创建时,该文件会有 1 个链接数;而在使用close()关闭打开文件时会检查文件的链接数,如果链接数为 0,则该文件将会被隐式删除,即在使用该文件的进程结束后,系统会根据自身算法对其择机删除。

link()可以建立文件的硬链接。硬链接相当于文件的命名,相对原文件来说只是目录项dir_entry不一样,但其dir_entry中的inode是同一个,而inode的成员又指向实际的数据块,所以实际内存位置是同一个;软链接即符号链接,则相当于文件的快捷方式,其拥有不同于原文件的dir_entryinode,自然最后指向的数据块也不一致,但其数据块存放了原文件的路径。

1
2
3
#include <unistd.h>
// 参数1为原文件路径名,参数2为新建硬链接的路径名
int link(const char *oldpath, const char *newpath);

unlink()可以删除硬链接和软链接。如果是软链接直接删除,如果是硬链接,则删除后如果原文件的链接数变为 0,才会在像在上文提到的那样被隐式删除。

1
2
#include <unistd.h>
int unlink(const char *pathname);

3.7 opendir()、closedir()和 readdir()

目录本质上也是文件,只是目录文件存放的是其他文件的dir_entry结构体。

可以使用opendir()closedir()来打开和关闭目录。注意这是两个库函数,而不是系统调用函数,在man手册第 2 章。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <dirent.h>
DIR *opendir(const char *name);
int closedir(DIR *dirp);
struct dirent *readdir(DIR *dirp);

struct dirent {
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below */
unsigned short d_reclen; /* Length of this record */
unsigned char d_type; /* Type of file; not supported
by all filesystem types */
char d_name[256]; /* Null-terminated filename */
};

需要注意的是,opendir()的参数和返回值和open()略有差别。参数无需多说,是相应目录的路径;在成功时返回一个DIR*类型的目录流,失败时返回NULL

closedir()成功返回 0,失败返回-1。

readdir()接受一个目录流类型的指针,从该目录下对逐个文件读取信息,并存放在一个struct dirent中,并返回其指针。通过其返回的指针,我们就可以获取到目录中的信息。

4 系统编程实践

4.1 cp 命令的实现

cp命名用于将文件拷贝,用法如下

1
2
cp file1 file2
`

4.1.1 实现思路

其原理非常简单,流程如下

  1. 打开file1,新建文件file2
  2. 读取file1内容到缓冲区
  3. 将缓冲区内容写入file2
  4. 重复 2、3 直到file1文件被全部读取、写入

4.1.2 实现代码

实现代码如下

cp01.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define BUFFSIZE 4096
#define COPYMODE 0644

void oops(char*, char*);

int main(int argc, char* argv[]) {
// 判断参数个数是否合理
if (argc != 3) {
fprintf(stderr, "usage: %s source destination\n", *argv);
exit(1);
}

int in_fd, out_fd, n_chars;
char buf[BUFFSIZE];
// 打开原文件
if ((in_fd = open(argv[1], O_RDONLY)) == -1)
oops("Cannot open ", argv[1]);
// 新建目标文件
if ((out_fd = creat(argv[2], COPYMODE)) == -1)
oops("Cannot creat ", argv[2]);
// 使用循环重复读取
while((n_chars = read(in_fd, buf, BUFFSIZE)) > 0) {
// 写入目标文件
if (write(out_fd, buf, n_chars) != n_chars)
oops("Write error to ", argv[2]);
}

if (n_chars == -1)
oops("Read error from ", argv[1]);

if (close(in_fd) == -1 || close(out_fd) == -1)
oops("Error closing files", "");

return 0;
}

void oops(char* s1, char* s2) {
fprintf(stderr, "Error: %s", s1);
perror(s2);
exit(1);
}

编译完成以后执行一下,然后用 Linux 命令的cmp命令比较一下原文件和拷贝文件是否有不同

1
2
3
$ gcc cp01.c -o cp01
$ ./cp01 cp01.c cp02.c
$ cmp cp01.c cp02.c

没有提示信息就是没有不同,说明程序正确。

4.2 ls 命令的实现

ls 可以说是 Linux 系统中最常用的命令之一了,用来查看当前或其他目录下的文件。但 ls 有许许多多的选项,全部实现自然不太可能,也没这个必要。这里我们仅实现最基础不带选项的功能版本,但要求其可以接受指定目录作为参数。

4.2.1 实现思路

首先 ls 既然是查看目录下文件,故而必须要打开目录,读取目录,返回目录信息,由于我们这里只实现不带选项的ls版本,所以只需返回目录下的文件名称即可。

  1. 判断是否带参数,若带参数,以参数作为目录名,否则默认当前目录
  2. 使用opendir打开目录
  3. 使用readdir循环读取目录中文件,过滤...
  4. 输出循环读取到文件的名称
  5. 读取完毕后使用closedir关闭目录流

4.2.2 实现代码

ls01.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <unistd.h>
#include <dirent.h>
#include <string.h>

#include "../helper.h"
// sys_err定义在helper.h中,定义如下,后文不再赘述
/*
void sys_err(char * s1) {
perror(s1);
exit(1);
}
*/
int main(int argc, char* argv[]) {
DIR* dp;
struct dirent * sdp;

if (argc == 1) dp = opendir(".");
else dp = opendir(argv[1]);

if (dp == NULL)
sys_err("opendir error");
while ((sdp = readdir(dp))) {
if (strcmp(sdp->d_name, ".") && strcmp(sdp->d_name, ".."))
printf("%s\t", sdp->d_name);
}
printf("\n");
closedir(dp);
return 0;
}

4.3 more 命令的实现

more用于分屏查看文件内容,和cat直接打印文件内容有所不同,其可以通过接受键盘输入来逐行、下一屏、回退显示内容,如

1
$ more cp01.c

4.3.1 实现思路

其工作流程可以用伪代码描述如下

1
2
3
4
5
6
7
         open the file
+------> show 24 lines from input
| +----> print [more?] message
| | input Enter, SPACE, or q
| +---- if Enter, advance one line
+------ if SPACE
if q --> exit;

即先打开文件,以文件流作为输入,显示一屏幕内容,固定行数如 24 行的内容,然后显示提示“more?”,根据用户的输入来进行下一步操作:若用户键入回车,则获取一行内容,多显示一行;若用户键入空格,则再次获取 24 行内容来显示下一屏幕的内容;若用户键入“q”,则退出程序。

4.3.2 实现版本 1.0

在这个版本中,将显示内容和接受用户输入的操作作为两个函数do_more()see_more()来实现。用法将满足第 1 节中的 3 种用法。

在主函数中,先判断是否给定文件参数,有的话,再打开文件,将文件描述符传入do_more(),对每个文件参数执行结束后,关闭文件;do_more()函数将会从文件中读取内容,将其显示,并调用see_more()接受用户阅读内容中输入的参数。

实现代码如下

more1.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stdio.h>
#include <stdlib.h>

#define PAGELEN 24
#define LINELEN 512

void do_more(FILE*);
int see_more();

int main(int ac, char* av[]) {
FILE* fd;
// 若没有参数则从标准中接受内容,以此接受重定向内容
if (ac == 1)
do_more(stdin);
else
// 显示文件参数中的每一个文件内容
while (--ac) {
if ((fd = fopen(*(++av), "r")) != NULL) {
do_more(fd);
fclose(fd);
} else
exit(1);
}
return 0;
}

void do_more(FILE* fd) {
char line[LINELEN];
int num_of_lines = 0;
int reply;
// 以行为单位不断从文件流中获取内容
while (fgets(line, LINELEN, fd)) {
// 若已经显示了一屏幕内容,则等待用户键入命令
if (num_of_lines == PAGELEN) {
reply = see_more();
if (reply == 0) break;
num_of_lines -= reply;
}
// 逐行显示
if (fputs(line, stdout) == EOF) exit(1);
num_of_lines += 1;
}
}
// 显示内容时接受用户命令的函数
int see_more() {
int c;
printf("\033[7mmore?\033[m");
while ((c = getchar()) != EOF) {
if (c == 'q') return 0;
if (c == ' ') return PAGELEN;
if (c == '\n') return 1;
}
return 0;
}

但目前该程序存在一个问题,使用其显示重定向的内容会出现问题,如下命令

1
who | ./more1

此时在程序中,将通过标准输入stdin来读取重定向的内容,但在see_more()函数中,同样使用getchar()来从标准输入中读取用户命令,这样显然起了冲突。

解决方法是,从标准输入中读取要显示的内容,然后直接从键盘读取用户输入的命令,见下一节的实现。

4.3.3 实现版本 2.0

类 Unix 系统中,文件/dev/tty是键盘和显示器的设备描述文件,向这个文件写相当于显示在用户的屏幕上,读相当于从键盘获取用户的输入。即使程序的输入输出被重定向,程序还是可以通过这个文件与终端交换数据。

实现方式很简单,只需要改变see_more()函数,使该函数接受/dev/tty文件流而不是标准输入即可。

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stdio.h>
#include <stdlib.h>

#define PAGELEN 24
#define LINELEN 512

void do_more(FILE*);
int see_more(FILE*);

int main(int ac, char* av[]) {
if (ac == 1)
do_more(stdin);
else {
FILE* fd;
while (--ac)
if ((fd = fopen(*(++av), "r")) != NULL) {
do_more(fd);
fclose(fd);
} else
exit(1);
}
return 0;
}

void do_more(FILE* fd) {
char line[LINELEN];
int num_of_lines = 0;
int reply;
FILE* fd_tty = fopen("/dev/tty", "r");
if (fd_tty == NULL) {
perror("open tty error");
exit(1);
}
while (fgets(line, LINELEN, fd)) {
if (num_of_lines == PAGELEN) {
reply = see_more(fd_tty);
if (reply == 0) break;
num_of_lines -= reply;
}
if (fputs(line, stdout) == EOF) exit(1);
num_of_lines += 1;
}
}

int see_more(FILE* fd_tty) {
char c;
printf("\033[7m more?\033[m");
while ((c = getc(fd_tty) != EOF)) {
if (c == 'q') return 0;
if (c == ' ') return PAGELEN;
if (c == '\n') return 1;
}
return 0;
}

4.4 lseek 的使用

下面这段展示了如何通过lseek()改变偏移量来先将数据写入,再从头将写入数据读出,以及通过lseek()来改变文件大小的方法

lseek.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <string.h> // strlen
#include <unistd.h> // lseek write
#include <fcntl.h> // open
#include "../helper.h" // sys_err

int main() {
int fd;
ssize_t n_chars;
char msg[] = "it's a test file for lseek.\n";
char buf[BUFFSIZE];

if ((fd = open("testlseek.txt", O_RDWR | O_CREAT, 0644)) == -1)
sys_err("open error");

write(fd, msg, strlen(msg));
lseek(fd, 0, SEEK_SET); // 写入数据后偏移量改变,重新设定偏移量再读
while ((n_chars = read(fd, buf, BUFFSIZE))) {
if (n_chars < 0) sys_err("read error");
write(STDOUT_FILENO, buf, n_chars);
}
// 通过文件尾部到初始位置的偏移量来获取文件长度
off_t filelen = lseek(fd, 0, SEEK_END);
printf("file old length is %ld.\n", filelen);
filelen = lseek(fd, 200, SEEK_END);
write(fd, "\0", 1); // 需要写入操作才能真正改变文件长度
printf("file new length is %ld\n", (filelen + 1));
printf("Done.\n");
close(fd);
return 0;
}

5 结束语

本篇文章在先介绍了man手册的用法,这将是之后最常用的查询函数说明的方法;然后主要简单地总结了 Linux 中的文件类型,介绍了文件描述符的原理;接着对文件操作常用的相关函数做了简单记录,对一些要点做了说明;最后通过实现cpmore命令作为两个小 demo,这两个 demo 的代码参照了《Unix\Linux 编程实践教程》一书,这里还补充了lseek()函数的例子。

还有stat()等函数的使用实例将留待后续文章中补充。

-------- 本文结束 感谢阅读 --------
给我加块红烧肉吧
  • 本文标题: Linux 系统编程(二):文件、文件操作函数和相关实例
  • 本文作者: Chou Bin
  • 创建时间: 2020年02月23日 - 19时02分
  • 修改时间: 2020年02月23日 - 20时02分
  • 本文链接: http://yoursite.com/2020/02/23/apue-hm02/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!