一、标准IO
(一) 文件基础
1. 概念
一组相关数据的有序集合
2. 文件的类型
-
常规文件 r
最常使用的一类文件,其特点是不包含有文件系统信息的结构信息。通常用户所接触到的文件,比如图形文件、数据文件、文档文件以及声音文件都属于这种文件,这种类型的文件是按照其内部结构又可分为纯文本文件(ASCII)、二进制文件(binary)、数据格式的文件(data)、各种压缩文件。
纯文本文件(ASCII):这是Unix系统中最多的一种文件类型,之所以称为纯文本文件,是因为内容可以直接读到的数据,例如数字、字母等等。设 置文件几乎都属于这种文件类型。举例来说,使用命令“cat ~/.bashrc”就可以看到该文件的内容(cat是将文件内容读出来)。
二进制文件(binary):系统其实仅认识且可以执行二进制文件(binary file)。Linux中的可执行文件(脚本,文本方式的批处理文件不算)就是这种格式的。举例来说,命令cat就是一个二进制文件。
数据格式的文件(data):有些程序在运行过程中,会读取某些特定格式的文件,那些特定格式的文件可以称为数据文件(data file)。举例来说,Linux在用户登入时,都会将登录数据记录在 /var/log/wtmp文件内,该文件是一个数据文件,它能通过last命令读出来。但使用cat时,会读出乱码。因为它是属于一种特殊格式的文件。
-
目录文件 d
用于存放文件名以及其相关信息的文件,是内核组织文件系统的基本节点。目录文件可以包含下一级文件目录或者普通文件,在Linux中,目录文件是一种文件。
能用 # cd 命令进入的。
-
字符设备文件 c
字符设备文件:即串行端口的接口设备,例如键盘、鼠标等等。
-
块设备文件 b
块设备文件 : 就是存储数据以供系统存取的接口设备,简单而言就是硬盘。例如一号硬盘的代码是 /dev/hda1等文件。
-
管道文件 p
是一种很特殊的文件,主要用于不同进程的信息传递。当两个进程需要进行数据或者信息传递时,可以使用通道文件,一个进程将需要传递的数据或者信息写入管道的一端,另一进程从管道的另一端取得所需要的数据或者信息,通常管道是建立在调整缓存中
-
套接字文件 s
这类文件通常用在网络数据连接。可以启动一个程序来监听客户端的要求,客户端就可以通过套接字来进行数据通信。
-
链接文件类型 I
是一种特殊文件,指向一个真实存在的文件链接,类似于Windows下的快捷方式,链接文件的不同,又可分为硬链接文件和符号链接文件。
(1)、查看文件类型的三种方法
①**ls -l/ls -ld
或者ll [ls -l —查看文件
ls -ld —查看路径`` ll ----跟ls -l 一样
]**
ll anaconda-ks.cfg //看第一个字符 -rw-------. 1 root root 2460 6月 1 23:37 anaconda-ks.cfg [root@localhost log]# ls -ld /etc drwxr-xr-x. 81 root root 4096 Jan 29 03:25 /etc
②**file 命令**
[root@localhost data]# file a.txt a.txt: ASCII text
③**stat 命令**
[root@localhost data]# stat a.txt //查看文件的详细属性(其中包括文件时间属性) File: `a.txt' Size: 3 Blocks: 8 IO Block: 4096 regular file Device: 803h/2051d Inode: 544365 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2018-01-28 20:56:01.965885036 +0800 Modify: 2018-01-28 20:55:27.181876154 +0800 Change: 2018-01-28 20:55:27.181876154 +0800
3、Linux中文件扩展名
windows里通过扩展名来区分文件类型的。linux里文件扩展名和文件类型没有关系。但为了容易区分和兼容用户使用windows的习惯,我们还是会用扩展名来表示文件类型。举例如下:
源码.tar、.tar.gz、.tgz、.zip、.tar.bz表示压缩文件,创建命令一般为tar,gzip,zip等。
.sh表示shell脚本文件,通过shell语言开发的程序。
.pl表示perl语言文件,通过perl语言开发的程序。
.py表示python语言文件,通过python语言开发的程序。
.html、.htm、.php、.jsp、.do表示网页语言的文件。
.conf表示系统服务的配置文件。
.rpm表示rpm安装包文件。
4、文件的属性
inode 索引节点编号:544365 文件类型 :文件类型是’-’,表示这是一个普通文件 文件权限:rw-r–r-- 表示文件可读、可写、可执行,文件所归属的用户组可读可执行,其他用户可读可执行 硬链接个数 表示a.txt这个文件没有其他的硬链接,因为连接数是1,就是他本身 文件属主 表示这个文件所属的用户,这里的意思是a.txt文件被root用户拥有,是第一个root 文件属组 表示这个文件所属的用户组,这里表示a.txt文件属于root用户组,是第二个root 文件大小 文件大小是3个字节 文件修改时间 这里的时间是该文件最后被更新(包括文件创建、内容更新、文件名更新等)的时间可用如下命令查看文件的修改、访问、创建时间
5、 更改文件属性
(1)chgrp:更改文件属组
语法:
chgrp [-R] 属组名 文件名
参数选项
-
-R:递归更改文件属组,就是在更改某个目录文件的属组时,如果加上-R的参数,那么该目录下的所有文件的属组都会更改。
(2)chown:更改文件属主,也可以同时更改文件属组
语法:
chown [–R] 属主名 文件名 chown [-R] 属主名:属组名 文件名
进入 /root 目录(~)将install.log的拥有者改为bin这个账号:
[root@www ~] cd ~ [root@www ~]# chown bin install.log [root@www ~]# ls -l -rw-r--r-- 1 bin users 68495 Jun 25 08:53 install.log
将install.log的拥有者与群组改回为root:
[root@www ~]# chown root:root install.log [root@www ~]# ls -l -rw-r--r-- 1 root root 68495 Jun 25 08:53 install.log
(3)chmod:更改文件9个属性
Linux文件属性有两种设置方法,一种是数字,一种是符号。
Linux 文件的基本权限就有九个,分别是 owner/group/others(拥有者/组/其他) 三种身份各有自己的 read/write/execute 权限。
先复习一下刚刚上面提到的数据:文件的权限字符为: -rwxrwxrwx , 这九个权限是三个三个一组的!其中,我们可以使用数字来代表各个权限,各权限的分数对照表如下:
-
r:4
-
w:2
-
x:1
每种身份(owner/group/others)各自的三个权限(r/w/x)分数是需要累加的,例如当权限为: -rwxrwx--- 分数则是:
-
owner = rwx = 4+2+1 = 7
-
group = rwx = 4+2+1 = 7
-
others= --- = 0+0+0 = 0
所以等一下我们设定权限的变更时,该文件的权限数字就是 770。变更权限的指令 chmod 的语法是这样的:
chmod [-R] xyz 文件或目录
选项与参数:
-
xyz : 就是刚刚提到的数字类型的权限属性,为 rwx 属性数值的相加。
-
-R : 进行递归(recursive)的持续变更,以及连同次目录下的所有文件都会变更
举例来说,如果要将 .bashrc 这个文件所有的权限都设定启用,那么命令如下:
[root@www ~]# ls -al .bashrc -rw-r--r-- 1 root root 395 Jul 4 11:45 .bashrc [root@www ~]# chmod 777 .bashrc [root@www ~]# ls -al .bashrc -rwxrwxrwx 1 root root 395 Jul 4 11:45 .bashrc
那如果要将权限变成 -rwxr-xr-- 呢?那么权限的分数就成为 4+2+1[4+0+0]=754。
(4)符号类型改变文件权限
还有一个改变权限的方法,从之前的介绍中我们可以发现,基本上就九个权限分别是:
-
user:用户
-
group:组
-
others:其他
那么我们就可以使用 u, g, o 来代表三种身份的权限。
此外, a 则代表 all,即全部的身份。读写的权限可以写成 r, w, x,也就是可以使用下表的方式来看:
chmod | u | +(加入) -(除去) =(设定) | r | 文件或目录 |
---|---|---|---|---|
g | w | |||
o | x | |||
a |
如果我们需要将文件权限设置为 -rwxr-xr-- ,可以使用 chmod u=rwx,g=rx,o=r 文件名 来设定:
# touch test1 // 创建 test1 文件 # ls -al test1 // 查看 test1 默认权限 -rw-r--r-- 1 root root 0 Nov 15 10:32 test1 # chmod u=rwx,g=rx,o=r test1 // 修改 test1 权限 # ls -al test1 -rwxr-xr-- 1 root root 0 Nov 15 10:32 test1
而如果是要将权限去掉而不改变其他已存在的权限呢?例如要拿掉全部人的可执行权限,则:
# chmod a-x test1 # ls -al test1 -rw-r--r-- 1 root root 0 Nov 15 10:32 test1
(二) 标准I/O -介绍
-
IO:input/output,针对文件的输入输出。
1.概念:
-
在C库中定义的一组专门用于输入输出的函数。
2.特点:
(1)标准IO通过缓冲机制减少系统调用的次数,从而提高效率。 (2)标准IO围绕“流”进行操作,流使用“ FILE * ”描述,“ FILE * ”->一个结构体指针。 (3)标准IO默认打开三个流:stdin(标准输入),stdout(标准输出),stderr(标准出错)。 系统调用:内核向上提供的一层函数接口。
3.ctags追踪代码
例:查看FILE的代码。
命令:在终端输入vi -t FILE
回车后进入以下界面后,输入1,然后回车:
回车后进入以下界面:
光标自动位于48行开头,可以看出FILE是结构体_IO_FILE重定义后的结构体名;
然后将光标置于_IO_FILE处,按下"Ctrl+]",终端底部将会出现以下界面:
输入1后回车,就可以看到有关结构体_IO_FILE的详细代码:
Ctrl+t可以回退到上一界面;
(三) 标准I/O -流
1. FILE
标准IO用一个结构体类型来存放打开的文件的相关信息 标准I/O的所有操作都是围绕FILE来进行
2. 流(stream)
FILE又被称为流(stream) 文本流/二进制流
(四) 标准I/O - 文本流和二进制流
1. Windows
二进制流: 换行符 ‘\n’ 文本流: 换行符 ‘\r’ ‘\n
2. Linux
换行符 ‘\n’
(五) 标准I/O - 流的缓冲类型
1.全缓冲
全缓存与文件相关,刷新缓存区有三个条件:
①程序正常退出时刷新;
②缓存区满刷新;
③强制刷新:fflush函数;
2. 行缓冲
行缓存与终端相关,刷新行缓存有四个条件:
①‘\n’可以刷新;
②程序正常退出时刷新;
③缓存区满刷新;
④强制刷新:fflush函数;
3. 无缓冲
数据直接写入文件, 流不进行缓冲
不缓存即没有缓存区,一般是*标准出错stderr*,因为程序出错时,我们希望它及时显示在终端上,而不是保存在缓存区;
4. 示例
分析以下代码:
#include <stdio.h> int main(int argc, const char *argv[]) { printf("hello"); while(1); return 0; } // 程序运行后,光标一直停止在行首闪烁,不会输出字符串hello
我们尝试三种方式来刷新缓存区:
(1)使用\n刷新缓存区:
#include <stdio.h> int main(int argc, const char *argv[]) { printf("hello\n"); while(1); return 0; }
程序运行后,会先输出字符串hello,然后光标在字符串的下一行行首不断闪烁;
(2)缓存区满刷新:
#include <stdio.h> int main(int argc, const char *argv[]) { for(int i = 0;i < 300;i++) { printf("%04d",i); } while(1); return 0; }
我们可以通过缓存区满刷新的特点来计算缓存区的大小,程序执行后,每输出一个数字,该数字都占4位,不够4位以0补全,从0输出到255,一共输出了256个数字,缓存区大小即256*4,1024个字符大小,但是这种方法比较麻烦;
(3) 通过标准IO计算缓存区大小:
使用标准IO计算缓存区大小时,应该先使用缓存区,然后再计算;
#include <stdio.h> int main(int argc, const char *argv[]) { printf("hello\n");//必须先使用缓存区,然后进行缓存区大小计算 printf("%d\n",stdout->_IO_buf_end - stdout->_IO_buf_base); return 0; }
-
stdout相当于FILE *,也就是FILE的结构体指针,buf指缓冲区;
-
程序执行后,先输出字符串hello,然后在下一行输出缓存区大小1024;
(4)强制刷新fflush:
#include <stdio.h> int main(int argc, const char *argv[]) { printf("hello"); fflush(NULL); while(1); return 0; }
程序执行后,先输出hello,然后光标在hello的下一位持续闪烁;
(六) 标准I/O - stdin,stdout,stderr
标准I/O预定义3个流, 程序运行时自动打开
标准输入流 0 STDIN_FILENO stdin 标准输出流 1 STDOUT_FILENO stdout ·标准错误流 2 STDERR_FILENO stderr
(七)标准I/O - fopen函数
1. 标准I/O - 打开流
下列函数可用于打开一个标准I/O流:
FILE *fopen (const char *path, const char *mode);
成功时返回流指针; 出错时返回NULL
2. 标准I/O - fopen - mode参数
mode参数:
“r” 或 “rb” 以只读方式打开文件,文件必须存在 “r+” 或 ”r+b” 以读写方式打开文件, 文件必须存在。 “w” 或 “wb” 以只写方式打开文件, 若文件存在则文件长度清为0。 若文件 不存在则创建。 “w+” 或 “w+b” 以读写方式打开文件, 其他同”w”。 “a” 或 “ab” 以只写方式打开文件, 若文件不存在则创建; 向文件写入的数 据被追加到文件末尾。 “a+” 或 “a+b” 以读写方式打开文件。 其他同”a” * 当给定”b”参数时, 表示以二进制方式打开文件, 但Linux下忽略该参数
3. 标准I/O -fopen - 示例
#include <stdio.h> int main(int argc, char *argv[]) { FILE *fp; if ((fp = fopen("test.txt", "r+")) == NULL) { printf("fopen error\n"); return -1; } return 0; }
4. 标准I/O - freopen - 示例
#include <stdio.h> int main(int argc, char *argv[]) { if (freopen("test.txt", "w", stdout) == NULL) { perror("freopen"); return -1; } printf("hello world\n"); return 0; }
4. 标准I/O - fopen - 新建文件权限
-
fopen() 创建的文件访问权限是0666(rw-rw-rw-)
-
Linux系统中umask设定会影响文件的访问权限, 其规则 为(0666 & ~umask)
-
用户可以通过umask函数修改相关设定
-
如果希望umask不影响文件访问权限, 该如何设定?
(八) 标准I/O - 处理错误信息
1. 处理错误信息相关的函数
extern int errno; void perror(const char *s); char *strerror(int errno);
-
errno 存放错误号
-
perror先输出字符串s, 再输出错误号对应的错误信息
-
strerror根据错误号返回对应的错误信息
2. 标准I/O - 错误信息处理 - 示例1
#include <stdio.h> int main(int argc, char *argv[]) { FILE *fp; if ((fp = fopen(“test.txt”, “r+”)) == NULL) { perror(“fopen”); return -1; } … … fopen: No such file or director
3. 标准I/O - 错误信息处理 - 实例2
#include <stdio.h> #include <string.h> #include <errno.h> int main(int argc, char *argv[]) { FILE *fp; if ((fp = fopen(“test.txt”, “r+”)) == NULL) { printf(“fopen: %s\n”, strerror(errno)); return -1; } … … fopen: No such file or directory
(九) 标准I/O - 关闭流
int fclose(FILE *stream);
fclose()调用成功返回0, 失败返回EOF, 并设置errno
流关闭时自动刷新缓冲中的数据并释放缓冲区
当一个程序正常终止时, 所有打开的流都会被关闭。
流一旦关闭后就不能执行任何操作
(十) 标准I/O - 文件的读写
1. 标准I/O - 读写流
流支持不同的读写方式:
读写一个字符:
fgetc()/fputc()
一次读/写一个字符读写一行:
fgets()和fputs()
一次读/写一行读写若干个对象:
fread()/fwrite()
每次读/写若干个对象, 而每个对象具有相同的长度
2. 标准I/O - 按字符输入
(1) 标准I/O - 按字符输入
下列函数用来输入一个字符:
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
-
成功时返回读取的字符; 若到文件末尾或出错时返回EOF
-
getchar()等同于fgetc(stdin)
(2) 标准I/O - fgetc - 示例
int ch; ch = fgetc(stdin); printf(“%c\n”, ch); FILE *fp; int ch, count = 0; if ((fp = fopen(argv[1], “r”)) == NULL) { perror(“fopen”); return -1; } while ((ch = fgetc(fp)) != EOF) { count++; } printf(“total %d bytes\n”, count);
3. 标准I/O - 按字符输出
(1) 标准I/O - 按字符输出
下列函数用来输出一个字符:
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
-
成功时返回写入的字符; 出错时返回EOF
-
putchar(c)等同于fputc(c, stdout)
(2) 标准I/O – fputc – 示例
fputc(‘a’, stdout); putchar(‘\n’); FILE *fp; int ch; if ((fp = fopen(argv[1], “w”)) == NULL) { perror(“fopen”); return -1; } for(ch = ‘a’; ch <=‘z’; ch++) { fputc(ch, fp); }
4 . 案例 - 复制文件
#include <stdio.h> int main(int argc, char *argv[]) { FILE *fps, *fpd; int ch; if (argc < 3) { printf("Usage : %s <src_file> <dst_file>\n", argv[0]); return -1; } if ((fps = fopen(argv[1], "r")) == NULL) { perror("fopen"); return -1; } if ((fpd = fopen(argv[2], "w")) == NULL) { perror("fopen"); return -1; } while ((ch = fgetc(fps)) != EOF) { fputc(ch, fpd); } fclose(fps); fclose(fpd); return 0; }
5. 标准I/O - 按行输入
(1) 标准I/O - 按行输入
下列函数用来输入一行:
#include <stdio.h>
char *gets(char *s);
char *fgets(char *s, int size, FILE *stream);
-
成功时返回s, 到文件末尾或出错时返回NULL
-
gets不推荐使用, 容易造成缓冲区溢出
-
遇到’\n’或已输入size-1个字符时返回, 总是包含’\0’
(2) 标准I/O - fgets - 示例
#define N 6 char buf[N]; fgets(buf, N, stdin); printf(“%s”, buf);
-
假设键盘输入分别是: abcd<回车> abcdef<回车>
-
buf中的内容是?
6. 标准I/O - 按行输出
(1) 标准I/O - 按行输出
下列函数用来输出字符串:
#include <stdio.h>
int puts(const char *s);
int fputs(const char *s, FILE *stream);
-
成功时返回输出的字符个数; 出错时返回EOF
-
puts将缓冲区s中的字符串输出到stdout, 并追加’\n’
-
fputs将缓冲区s中的字符串输出到stream
(2) 标准I/O - fputs - 示例
puts(“hello world”); FILE *fp; char buf[] = “hello world”; if ((fp = fopen(argv[1], “a”)) == NULL) { perror(“fopen”); return -1; } fputs(buf, fp);
-
注意: 输出的字符串中可以包含’\n’, 也可以不包含
7. 案例 - 统计文本文件行数
#include <stdio.h> #include <string.h> #define N 64 int main(int argc, char *argv[]) { FILE *fp; int line = 0; char buf[N]; if (argc < 2) { printf("Usage: %s <file>\n", argv[0]); return -1; } if ((fp = fopen(argv[1], "r")) == NULL) { printf("fopen error\n"); return -1; } while (fgets(buf, N, fp) != NULL) { if (buf[strlen(buf)-1] == '\n') line++; } printf("the line of %s is %d\n", argv[1], line); return 0; }
8. 标准I/O - 按对象读写
(1) 标准I/O - 按对象读写
下列函数用来从流中读写若干个对象:
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t n, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);
-
成功返回读写的对象个数; 出错时返回EOF
-
既可以读写文本文件, 也可以读写数据文件
-
效率高
(2) 标准I/O – fread/fwrite – 示例
int s[10]; if (fread(s, sizeof(int), 10, fp) < 0) { perror(“fread”); return -1; } struct student { int no; char name[8]; float score; } s[] = {{ 1, “zhang”, 97}, {2, “wang”, 95}}; fwrite(s, sizeof(struct student), 2, fp);
9. 标准I/O - 刷新流
(1) 标准I/O - 刷新流
#include <stdio.h>
int fflush(FILE *fp);
-
成功时返回0; 出错时返回EOF
-
将流缓冲区中的数据写入实际的文件
-
Linux下只能刷新输出缓冲区
10 . 标准I/O - 定位流 – ftell/fseek/rewind
(1) 标准I/O - 定位流 – ftell/fseek/rewind
#include <stdio.h>
int fflush(FILE *fp);
-
成功时返回0; 出错时返回EOF
-
将流缓冲区中的数据写入实际的文件
-
Linux下只能刷新输出缓冲区
-
offset参数: 偏移量, 可正可负
-
rewind()将流定位到文件开始位置
-
读写流时, 当前读写位置自动后移
(2) 示例一(在文件末尾追加字符’t’ )
FILE *fp = fopen(“test.txt”, “r+”); fseek(fp, 0, SEEK_END); fputc(‘t’, fp);
(3) 示例二 (获取文件长度))
#include <stdio.h> int main(int argc, char *argv[]) { FILE *fp; if ((fp = fopen("test.txt", "r+")) == NULL) { perror("fopen"); return -1; } fseek(fp, 0, SEEK_END); printf("length is %ld\n", ftell(fp)); return 0; }
11. 标准IO - 判断流是否出错和结束
(1) 标准IO - 判断流是否出错和结束
#include <stdio.h>
int ferror(FILE *stream);
int feof(FILE *stream);
-
ferror()返回1表示流出错; 否则返回0
-
feof()返回1表示文件已到末尾; 否则返回0
12. 标准I/O - 格式化输出
(1) 标准I/O - 格式化输出
#include <stdio.h>
int printf(const char *fmt, …);
int fprintf(FILE *stream, const char *fmt, …);
int sprintf(char *s, const char *fmt, …);
-
成功时返回输出的字符个数; 出错时返回EOF
-
使用起来很方便, 强烈推荐!
(2) 标准I/O - 格式化输出 - 示例1
以指定格式 “年-月-日” 分别写入文件和缓冲区
#include <stdio.h> #include <unistd.h> #include <time.h> #include <string.h> int main() { FILE *fp; int line = 0; char buf[64]; time_t t; struct tm *tp; if ((fp = fopen("test.txt", "a+")) == NULL) { perror("fopen"); return -1; } while (fgets(buf, 64, fp) != NULL) { if (buf[strlen(buf)-1] == '\n') line++; } while ( 1 ) { time(&t); tp = localtime(&t); fprintf(fp, "%02d, %d-%02d-%02d %02d:%02d:%02d\n", ++line, tp->tm_year+1900, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec); fflush(fp); sleep(1); } return 0; }
(3)标准I/O - 格式化输出 - 示例2
格式化输出示例
#include <stdio.h> int main() { int year, month, date; FILE *fp; char buf[64]; year = 2014; month = 10; date = 26; fp = fopen("test.txt", "a+"); fprintf(fp, "%d-%d-%d\n", year, month, date); sprintf(buf, "%d-%d-%d\n", year, month, date); return 0; }
二、文件I/O
(一) 文件I/O - 介绍
什么是文件I/O
posix(可移植操作系统接口)定义的一组函数
不提供缓冲机制, 每次读写操作都引起系统调用
核心概念是文件描述符
访问各种类型文件
Linux下, 标准IO基于文件IO实现
(二) 文件I/O - open
1. 文件I/O - open
open函数用来创建或打开一个文件:
#include <fcntl.h>
int open(const char *path, int oflag, …);
成功时返回文件描述符; 出错时返回EOF
打开文件时使用两个参数
创建文件时第三个参数指定新文件的权限
只能打开设备文件
2. 文件I/O - open - 示例1
以只写方式打开文件1.txt。 如果文件不存在则创建, 如果文件存在则清空:
int fd; if ((fd = open(“1.txt”, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) { perror(“open”); return -1; }… …
3. 文件I/O – open – 示例2
以读写方式打开文件1.txt。 如果文件不存在则创建, 如果文件存在则报错:
int fd; if ((fd = open(“1.txt”, O_RDWR|O_CREAT|O_EXCL, 0666)) < 0) { if (errno == EEXIST) { perror(“exist error”); } else { perror(“other error”); } }
(三) 文件I/O – close
1. 文件I/O - close
close函数用来关闭一个打开的文件:
#include <unistd.h>
int close(int fd);
成功时返回0; 出错时返回EOF
程序结束时自动关闭所有打开的文件
文件关闭后, 文件描述符不再代表文件
2. 文件I/O - 示例
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> int main() { int fd; if ((fd = open("1.txt", O_RDWR|O_CREAT|O_EXCL, 0666)) < 0) { if (errno == EEXIST) { perror("exist error"); } else { perror("other error"); } } return 0; }
(四) 文件I/O - read
1. 文件I/O - read
read函数用来从文件中读取数据:
#include <unistd.h>
ssize_t read(int fd, vo id *buf, size_t count);
成功时返回实际读取的字节数; 出错时返回EOF
读到文件末尾时返回0
buf是接收数据的缓冲区
count不应超过buf大小
2. 文件I/O -read - 示例
从指定的文件(文本文件)中读取内容并统计大小
int main(int argc, char *argv[]) { { int fd, n, total = 0; char buf[64]; if (argc < 2) { printf(“Usage : %s <file>\n”, argv[0]); return -1; }if((fd = open(argv[1], O_RDONLY)) < 0) { perror(“open”); return -1; } while ((n = read(fd, buf, 64)) > 0) { total += n; }… …
(五) 文件I/O - write
1. 文件I/O -write
write函数用来向文件写入数据:
#include <unistd.h>
ssize_t write(int fd, void *buf, size_t count);
成功时返回实际写入的字节数; 出错时返回EOF
buf是发送数据的缓冲区
count不应超过buf大小
2. 文件I/O – write – 示例
将键盘输入的内容写入文件, 直到输入quit
int fd; char buf[20]; if ((fd = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) { perror(“open”); return -1; } while (fgets(buf, 20, stdin) != NULL) if (strcmp(buf, “quit\n”) == 0) break; write(fd, buf, strlen(buf)); }
(六) 文件I/O - lseek
1. 文件I/O - lseek
lseek函数用来定位文件:
#include <unistd.h> off_t lseek(int fd, off_t offset, intt whence);
成功时返回当前的文件读写位置; 出错时返回EOF
参数offset和参数whence同fseek完全一样
2. 示例1(复制文件)
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <errno.h> #define N 64 int main(int argc, char *argv[]) { int fds, fdt, n; char buf[N]; if (argc < 3) { printf("Usage : %s <src_file> <dst_file>\n", argv[0]); return -1; } if ((fds = open(argv[1], O_RDONLY)) == -1) { fprintf(stderr, "open %s : %s\n", argv[1], strerror(errno)); return -1; } if ((fdt = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0666)) == -1) { fprintf(stderr, "open %s : %s\n", argv[2], strerror(errno)); return -1; } while ((n = read(fds, buf, N)) > 0) { write(fdt, buf, n); } close(fds); close(fdt); return 0; }
3. 示例2(键盘输入内容到文件)
#include <stdio.h> #include <fcntl.h> #include <string.h> int main(int argc, char *argv[]) { int fd; char buf[20]; if ((fd = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) { perror("open"); return -1; } while (fgets(buf, 20, stdin) != NULL) { if (strcmp(buf, "quit\n") == 0) break; write(fd, buf, strlen(buf)); } close(fd); return 0; }
4. 示例3(统计文件大小)
#include <stdio.h> #include <fcntl.h> int main(int argc, char *argv[]) { int fd, n, total = 0; char buf[64]; if (argc < 2) { printf("Usage : %s <file>\n", argv[0]); return -1; } if ((fd = open(argv[1], O_RDONLY)) < 0) { perror("open"); return -1; } while ((n = read(fd, buf, 64)) > 0) { total += n; } printf("the length of %s is %d\n", argv[1], total); close(fd); return 0; }
(七)访问目录
1. 访问目录 – opendir
opendir函数用来打开一个目录文件:
#include <dirent.h>
DIR *opendir(const char *n ame);
DIR是用来描述一个打开的目录文件的结构体类型
成功时返回目录流指针; 出错时返回NULL
2. 访问目录 – readdir
readdir函数用来读取目录流中的内容:
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
struct dirent是用来描述目录流中一个目录项的结构体类型
包含成员char d_name[256] 参考帮助文档
成功时返回目录流dirp中下一个目录项;
出错或到末尾时时返回NULL
3. 访问目录 – closedir
closedir函数用来关闭一个目录文件:
#include <dirent.h>
int closedir(DIR *dirp);
成功时返回0; 出错时返回EOF
4. 访问目录 – 示例代码
打印指定的目录下所有文件名称
int main(int argc, char *argv[]) { { DIR *dirp; struct dirent *dp; if (argc < 2) { printf(“Usage : %s <directory>\n”, argv[0]); return -1; }if((dirp = opendir(argv[1])) == NULL) { perror(“opendir”); return -1; } while ((dp = readdir(dirp)) != NULL) { printf(“%s\n”, dp->d_name); }c losedir(dirp);
(八) 修改文件访问权限 – chmod/fchmod
chmod/fchmod函数用来修改文件的访问权限:
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
成功时返回0; 出错时返回EOF
root和文件所有者能修改文件的访问权限
示例: chmod(“test.txt”, 0666);
(九) 文件属性
1. 获取文件属性 – stat/lstat/fstat
stat/lstat/fstat函数用来获取文件属性:
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
成功时返回0; 出错时返回EOF
如果path是符号链接stat获取的是目标文件的属性; 而lstat获取的是链接文件的属性
2.文件属性 – struct stat
struct stat是存放文件属性的结构体类型:
-
mode_t st_mode; 类型和访问权限
-
uid_t st_uid; 所有者id
-
uid_t st_gid; 用户组id
-
off_t st_size; 文件大小
-
time_t st_mtime; 最后修改时间
(十) 文件的类型和访问权限
1. 文件类型 – st_mode
通过系统提供的宏来判断文件类型:
st_mode & 0170000
S_ISREG(st_mode) 0100000
S_ISDIR(st_mode) 0040000
S_ISCHR(st_mode) 0020000
S_ISBLK(st_mode) 0060000
S_ISFIFO(st_mode) 0010000
S_ISLNK(st_mode) 0120000
S_ISSOCK(st_mode) 0140000
2. 文件访问权限 – st_mode
通过系统提供的宏来获取文件访问权限:
S_IRUSR | 00400 | bit:8 |
---|---|---|
S_IWUSR | 00200 | 7 |
S_IXUSR | 00100 | 6 |
S_IRGRP | 00040 | 5 |
S_IWGRP | 00020 | 4 |
S_IXGRP | 00010 | 3 |
S_IROTH | 00004 | 2 |
S_IWOTH | 00002 | 1 |
S_IXOTH | 00001 | 0 |
3. 示例1(显示目录内容)
#include <stdio.h> #include <dirent.h> int main(int argc, char *argv[]) { DIR *dirp; struct dirent *dp; if (argc < 2) { printf("Usage : %s <directory>\n", argv[0]); return -1; } if ((dirp = opendir(argv[1])) == NULL) { perror("opendir"); return -1; } while ((dp = readdir(dirp)) != NULL) { printf("%s\n", dp->d_name); } closedir(dirp); return 0; }
4. 示例2(显示文件属性)
#include <stdio.h> #include <unistd.h> #include <time.h> #include <sys/types.h> #include <sys/stat.h> int main(int argc, char *argv[]) { struct stat buf; int n; struct tm *tp; if (argc < 2) { printf("Usage : %s <file>\n", argv[0]); return -1; } if (lstat(argv[1], &buf) < 0) { perror("lstat"); return -1; } switch (buf.st_mode & S_IFMT) { case S_IFREG: printf("-"); break; case S_IFDIR: printf("d"); break; } for (n=8; n>=0; n--) { if (buf.st_mode & (1<<n)) { switch (n % 3) { case 2: printf("r"); break; case 1: printf("w"); break; case 0: printf("x"); break; } } else { printf("-"); } } printf(" %lu", buf.st_size); tp = localtime(&buf.st_mtime); printf(" %d-%02d-%02d", tp->tm_year+1900, tp->tm_mon+1, tp->tm_mday); printf(" %s\n", argv[1]); return 0; }
三、静态库与动态库
(一) 库的概念
库是一个二进制文件, 包含的代码可被程序调用
-
标准C库、 数学库、 线程库……
-
库有源码, 可下载后编译; 也可以直接安装二进制包
-
/lib /usr/lib
(二) 库的知识
-
库是事先编译好的, 可以复用的代码。
-
在OS上运行的程序基本上都要使用库。 使用库可以提高开发效率。
-
Windows和Linux下库文件的格式不兼容
-
Linux下包含静态库和共享库
(三) 静态库特点
1. 静态库的特点
_ 编译(链接)时把静态库中相关代码复制到可执行文件中_
程序中已包含代码, 运行时不再需要静态库
程序运行时无需加载库, 运行速度更快
占用更多磁盘和内存空间
静态库升级后, 程序需要重新编译链接
2. 静态库创建(1)
-
确定库中函数的功能、 接口_
-
编写库源码hello.c_
#include <stdio.h> void hello(void) { printf(“hello world\n”); return; }
-
编译生成目标文件
$ gcc -c hello.c -Wall
3. 静态库创建(2)
-
创建静态库 hello
$ ar crs libhello.a hello.o
-
查看库中符号信息
$nm libhello.a
hello.o:
0000000 T hello
U puts
4. 链接静态库
-
编写应用程序test.c
#include <stdio.h> void hello(void); int main() { hello(); return 0; }
-
编译test.c 并链接静态库libhello.a
$ gcc -o test test.c -L. -lhello $ ./test hello world
(四) 共享库
1. 共享库特点
编译(链接)时仅记录用到哪个共享库中的哪个符号, 不复制共享库中相关代码
-
程序不包含库中代码, 尺寸小
-
多个程序可共享同一个库
-
程序运行时需要加载库
-
库升级方便, 无需重新编译程序
-
使用更加广泛
2. 共享库创建(1)
-
确定库中函数的功能、 接口
-
编写库源码hello.c bye.c
#include <stdio.h> void hello(void) { printf(“hello world\n”); return; }
-
编译生成目标文件
$ gcc -c -fPIC hello.c bye.c -Wall
3. 共享库创建(2)
-
创建共享库 common
$ gcc -shared -o libcommon.so.1 hello.o bye.o
-
为共享库文件创建链接文件
-
$ ln -s libcommon.so.1 libcommon.so
-
符号链接文件命名规则
-
lib<库名>.so
4. 链接共享库
-
编写应用程序test.c
#include <stdio.h> #include “common.h” int main() { hello(); bye(); return 0; }
-
编译test.c 并链接共享库libcommon.so
$ gcc -o test test.c -L. -lcommon
5. 加载共享库
-
执行程序
$ ./test
./test: error while loading shared libraries: libcommon.so
cannot open shared object file : No such file or directory
-
添加共享库的加载路径
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:
$ ./test
hello world
`bye! `
6. 如何找到共享库
-
为了让系统能找到要加载的共享库, 有三种方法 :
-
把库拷贝到/usr/lib和/lib目录下
-
在LD_LIBRARY_PATH环境变量中添加库所在路径
-
添加/etc/ld.so.conf.d/*.conf文件, 执行ldconfig刷新