Linux文件下I/O基础详解

本文转自网络文章,转载此文章仅为个人收藏,分享知识,已获取原文作者授权。
原文作者:木易_

文件基础

一组相关数据的有序集合

文件类型

常规文件: r ----- 二进制文件;ASCII码文件

目录文件: d

字符设备文件: c

块设备文件: b

管道文件: p

套接字文件: s

符号链接文件: l

不同的操作系统,所支持的文件类型不一样;

标准IO

1. 概念

C库中定义好的一些用于输入和输出的函数;主流操作系统上都实现了C库;

标准IO通过缓冲机制减少系统调用,实现了更高的效率;

1.1 缓冲机制:

没有缓冲机制的情况下,应用程序每次读写文件都需要通过系统调用来完成;(应用程序指定读取多少数据,系统调用就读取多少数据)

应用程序内部开辟一个缓冲区buf(4k),(一次性读取4k的数据返回应用程序);缓冲区有数据,直接从缓冲区读取,避免重复的系统调用。(写入数据的时候也是先往缓冲区中写数据,缓冲区满了才会去写实际的文件)

1.2 系统调用:

代码直接操作硬件:单片机;

有操作系统的情况下,应用程序访问硬件,必须通过操作操作系统提供的系统调用接口来操作硬件;

不同的操作系统,系统调用接口不兼容。

2. 流(FILE结构体)
2.1 定义

标准IO中用一个结构体类型来存放打开的文件的相关信息;(系统头文件中定义好的)

标准IO中所有的操作都是围绕FILE来进行。(一个FILE就代表一个打开的文件)

2.2 流(stream)

FILE又被称为流;

文本流/二进制流

Windows系统中:(对换行符的处理不同)

二进制流:换行符 — ‘\n’ 文本流:换行符 — ‘\r’‘\n’//回车和换行

Linux系统中不作区分,都看作二进制流;

3. 流的缓冲类型
3.1 全缓冲

当流的缓冲区无数据(读)或无空间(写)时才执行实际I/O操作;(默认缓冲类型)

3.2 行缓冲

当在输入和输出时遇到换行符‘\n’时,进行I/O操作;(进行实际的文件操作,读或写)

当流和一个终端关联时,默认为行缓冲。(典型:标准输入和标准输出流。如:打印调试信息时,如果不加换行符,打印的信息仅仅会写在标准输出流的缓冲区中,并没有写在实际的终端上)

3.3 无缓冲

数据直接写入文件,流不进行缓冲。(对流进行操作的时候都是访问实际的文件;如:打印错误信息时,希望错误信息直接打印在终端上。标准错误流默认为无缓冲)

    在linux下运行一个程序时,系统会自动打开三个流,(标准输入stdin,标准输出stdout,标准错误流stderr)可以在程序中直接使用。
4. 流的打开与关闭
4.1 流的打开
4.1.1 fopen
FILE *fopen(const char *path,const char *mode);
//第一个参数:指定打开文件的路径(通过字符串或者命令行参数把==文件名传递进来==);
//第二个参数:流的打开方式
//成功时返回流指针;出错时返回NULL。(fopen的返回值通常要保存下来进行错误处理)
4.1.2 流的打开方式

mode参数:

‘r’或‘rb’ — 文件以只读方式打开,文件必须存在。

r表示以文本流的方式打开一个文件;rb表示以二进制的方式打开文件。//linux不区分文本流和二进制流

‘r +’或‘r + b’ — 文件以读写方式打开,文件必须存在。

‘w’或‘wb’ — 文件以只写方式打开,文件必须存在则文件长度清0(内容清空);若文件不存在则创建。

‘w + ’或‘w + b’ — 文件以读写方式打开,文件必须存在则文件长度清0(内容清空);若文件不存在则创建。

//文件原来内容已经被清空,只能读取新写入的内容。

‘a’或‘ab’ — 文件以只写方式打开,若文件不存在则创建;向文件写入的数据会被追加到文件末尾。

‘a + ’或‘a + b’ — 文件以读写方式打开,若文件不存在则创建;向文件写入的数据会被追加到文件末尾。

//可以读取到文件原来的内容。

4.1.3 示例
#include <stdio.h>
int main(int argc,char *argv[])
{
    FILE *fp;//定于一个流指针fp
    if((fp = fopen("text.txt","r +")) == NULL)//调用fopen(文件名,打开方式),如果只有文件名无路径,默认为当前路径;
    //判断fp的值是否存在,若不存在打印错误信息;
    {
        printf("fopen error\n");//标准输出流是行缓冲,加'\n'可以立即显示在终端上;
        return -1;
    }
    ...
    return 0;
}
4.1.4 新建文件权限

fopen()创建的文件默认访问权限是0666(rw-rw-rw-) //0表示8进制数,后面分别表示用户、同组用户和其他用户的权限。

在linux中,umask的设定会影响新建文件的访问权限,其规则为(0666 &~ umask)

umask值:root用户默认是0022,普通用户默认是 0002;

// root用户先位反再进行与操作之后文件权限为0644;

用户可以通过umask函数来设置权限掩码,只会影响到当前程序

umask 002//权限改为002
umask//查看umask值

掩码设置为0时,对文件的权限无影响

4.2 处理错误信息
4.2.1代码实现
extern int errno;  // errno用来存放错误号的整型变量;

void perror(const char *s); //  perror先输出字符串s,再打印错误号对应的错误信息;

char *strerror(int errno); //  根据错误号返回对应的错误信息;(返回值是字符串首地址,存放的是错误信息)
4.2.2 示例

1.perror打印错误信息

#include <stdio.h>
int main(int argc, char *argv[])
{
    FILE *fp;
    if((fp = fopen("text.txt","r +")) == NULL){
        perror("fopen");
        return -1;
    }
    ...
}

2.strerror(erron)打印错误信息

#include <stdio.h>
#include <string.h>//声明strerror函数
#include <errno.h>//声明errno变量
int main(int argc, char *argv[])
{
    FILE *fp;
    if((fp = fopen("text.txt","r +")) == NULL){
        printf("fopen:%s\n",strerror(errno));
        return -1;
    }
    ...
}
4.3 流的关闭
4.3.1 fclose
int fclose(FILE *stream);//参数是之前打开的流指针
//调用成功返回0,失败返回EOF,并设置errno;
4.3.2 注意事项

流关闭时自动强制刷新缓冲区的数据并释放缓冲区;

当一个程序正常终止时,所有打开的流都会被关闭;(要养成手动关闭流的习惯)

流一旦关闭后就不能执行任何操作。

if(fp != NULL)
fclose(fp);//流关闭之前需要判断是否为NULL
4.4思考

如何测试程序中能够打开的文件或流的最大个数

#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{ 

    int count = 0;
    FILE *fp;
    while(1)
    {   
        if((fp = fopen("text.txt","a")) == NULL){
            perror("fopen");
            break;
        }
        count++;
    }   
    printf("count = %d\n",count);
    if(fp != NULL)
    fclose(fp);
    return 0;
} 
***
fopen:Too many open files
count = 1021
段错误 (核心已转储)//段错误原因---fp有为空的情况,可用fclose(stdout)关闭;或者先判断是否为空指针

1021 + stdin + stdout + stderr = 1024

5.流的读写方式
5.1按照字符读写
fgetc()/fputc()  一次读/写一个字  //文本文件和二进制文件都可以但效率低
5.1.1按字符输入
#include <stdio.h>
int fgetc(FILE *stream);//参数为流指针,用来指定从哪个流中读取字符
int getc(FILE *stream);//等同fgetc
int getchar(void);//没有参数,只能从标准输入流中读取一个字符并返回
getchar() == fgetc(stdin)
//成功时返回读取的字符;若到文件末尾或出错时返回EOF(-1)
5.1.2示例:统计一个文件的大小
FILE *fp;
int ch,count = 0;
if(fp = fopen("text.txt","r")) == NULL){//依次从文件中读取一个字符;
    perror("fopen");
    return -1;
}
while((ch = fgetc(fp)) != EOF){//如果读取的数据不是-1,就计数;
    count++;
}
printf("total %d bytes\n",count);
5.1.3按字符输出
#include <stdio.h>
int fputc(int c,FILE *stream);//(要输出的字符,指定写入的流)
int putc(int c,FILE *stream);//等同fputc
int putchar(int c);//默认向标准输出流写入一个字符;
//成功时返回读取的字符;若到文件末尾或出错时返回EOF(-1)
5.1.4示例:向一个流输入26个字母
FILE *fp;
int ch; 
if((fp = fopen("text.txt","w")) == NULL){
    perror("fopen");
    return -1; 
}   
for(ch = 'a';ch <='z';ch++){
    fputc(ch,fp);//把ch中的字符写入到fp指定的流中
} 
5.1.5示例:文件的复制
//通过命令行参数传递源文件和目标文件名
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 src file");
    	return -1; 
	}
if ((fpd = fopen(argv[2],"w")) == NULL)//打开目标文件并判断是否有效
	{
    	perror("fopen dst file");
    	return -1; 
	}   
while((ch = fgetc(fps)) != EOF))//从源文件读取内容并写入目标文件
    {
        fputc(ch, fpd);
    }
    fclose(fps);
    fclose(fpd);
5.2按行进行读写
fgets()/fputs()  一次读/写一行
//只能读写文本文件
5.2.1按行读取
#include <stdio.h>
char *gets(char *s);//不建议使用,未指定缓冲区大小,会使文件缓冲区溢出;
char *fgets(char *s, int size, FILE *stream);
//从流指针中读取一行字符串,保存至s所指向的缓冲区中(size指定缓冲区的大小)
//成功时返回缓冲区首地址,到文件末尾或出错时返回NULL;
//遇到换行符或者size-1个有效字符时返回,都会在后面追加'\0'表示字符串结束;
123456
#define N 6
char buf[N];//定义字符数组保存读到的字符串
fgets(buf,N,stdin);//stdin从标准输入读取
printf("%s",buf);//abcd换行 --- abcd'\n''\0'//abcdef --- abcde'\0'
//下次再调用fgets,缓冲区剩余内容会重新读到buf
5.2.2按行写入
#include <stdio.h>
int puts(const char *s);//把存放在s缓存区的字符串写入到标准输出流
int fputs(const char *s,FILE *stream);//向指定的流中写入缓冲区s中的字符串
//成功时返回字符串长度,出错时返回EOF
//puts将缓冲区s中的字符串输出到stdout,并追加'\n'

注意

仅仅是将缓冲区中的字符串写入流,无论缓冲区中有无换行符都无影响;

FILE *fp;
char buf[]="hello world";
if((fp = fopen(argv[1],"a")) == NULL){//将字符串一追加的形式写入命令行参数
    perror("fopen");
    return -1;
}
fputs(buf,fp);
5.2.3示例:统计文本文件行数
#define N 64
 FILE *fp;
    int count = 0;
    char buf[N]={0};
    if(argc != 2){
        printf("%s 文本文件名\n",argv[0]);
        return -1;
    }
    if((fp = fopen(argv[1],"r")) == NULL){
        perror("fopen");
        return -1;
    }
    while((fgets(buf,N,fp))!=NULL){//一直读取到文件末尾返回NULL
        if(buf[strlen(buf)-1] == '\n')
        {
            count++;
        }
    }
    printf("%d\n",count);

换行符也算一个字符,并且字符串以’\0’结束,所以倒数第二位是’\n’,详见按行读取5.2.1

5.3 按照指定对象去读写
fread()/fwrite()  每次读/写若干对象,而每个对象具有相同的长度
//指定对象可以指定字符对象,整型对象,结构体对象等,以这个对象为基本单位去读/写流
//文本文件和二进制文件都可以  
#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);
//将存放在缓冲区ptr中的n个size大小的对象写入fp流指针指定的对象中
//成功时返回实际读写的对象个数,出错时返回EOF(-1);读到文档末尾返回0
5.3.1 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);
5.3.2 示例:文件的复制
//通过命令行参数传递源文件和目标文件名
#define N 64  
FILE *fps, *fpd;
char buf[N];
int n;
if (argc < 3)//判断命令行参数个数
	{
   		printf("Usage : %S <src_file> <dst_file>\n",argv[0]);//告诉用户正确格式
        return -1;
	}
if ((fps = fopen(argv[1],"r")) == NULL)//打开源文件并判断是否有效
	{
    	perror("fopen src file");
    	return -1; 
	}
if ((fpd = fopen(argv[2],"w")) == NULL)//打开目标文件并判断是否有效
	{
    	perror("fopen dst file");
    	return -1; 
	}   
while((n = fread(buf,1,N,fps)) > 0)//通过fread返回值判断是否读到文件末尾
    {
    	fwrite(buf, 1, n, fpd);//将缓冲区中保存的数据写入fpd
    }
    fclose(fps);
    fclose(fpd);
6. 流的刷新与定位
6.1 流的刷新
6.1.1 流自动刷新缓冲区

1.当流的缓冲区满或者有换行符时,系统会自动刷新流的缓冲区,将内容写入实际的文件中。

对于全缓冲,当缓冲区满时才会自动刷新;行缓冲满足一个条件即可自动刷新。

2.当流关闭的时候,如果流中存有数据,系统会将内容写入实际的文件并刷新流。

3.通过fflush函数刷新流

6.1.2 fflush函数
#include <stdio.h>
int fflush(FILE *fp);
//成功时返回0;出错时返回EOF(-1);
//使用此函数时,会强制将流中缓冲区的内容写入实际的文件中;
//Linux下只能刷新输出缓冲区。输入缓冲区的内容只能通过fgetc或者其他函数读走
6.1.3 fflush示例
#include <stdio.h>
int main()
{
    FILE *fp;
    if((fp = fopen("text.txt","w")) == NULL)
    {
        perror("fopen");
        return -1;
    }
    fputc('a',fp);
    fflush(fp);//如果不刷新缓冲区,由于12行程序会一直运行,全缓冲不会刷新,'a'就会一直在缓冲区中,而不会存入文件中;
    while(1);
    return 0;
}
6.2 流的定位
6.2.1 函数定义

当打开一个流的时候,其内部有一个当前读写位置;读写位置会自动后移;

#include <stdio.h>
long ftell(FILE *stream);
//返回值为长整型,返回指定流的当前读写位置;流打开时其读写位置为0
long fseek(FILE *stream,long offset, int whence);
//用来设定一个流的当前读写位置
//(流指针,偏移量(可正可负),基准点)  ---whence参数SEEK_SET/SEEK_CUR/SEEK_END
//正数表示基准点之后,负数表示基准点之前;
//不允许偏移到初始位置以前;允许超出当前文件的长度;
void rewind(FILE *stream);
//直接将流定位到起始位置

成功时返回流的读写位置,出错时返回EOF;

6.2.2 示例:文件末尾追加
FILE *fp = fopen("text.txt","r+");
fseek(fp,0,SEEK_END);
fputc('t',fp);
6.2.3 示例:获取文件长度
FILE *fp;
if((fp = fopen("text.txt","r+")) == NULL){
    perror("fopen");
    return -1;
}
fseek(fp,0,SEEK_END);//将文件读写位置定位到文件末尾
printf("length is %d\n",ftell(fp));//获取当前读写位置的值
6.3 判断流是否出错和结束
#include <stdio.h>
int ferror(FILE *stream);
//判断流是否出错:出错时返回1;没有出错时返回0,表示逻辑假。
int feof(FILE *stream);
//测试流的读写位置是否已经到了末尾
//已经到末尾返回1,表示逻辑真;否则返回0。
7. 流的格式化输出
7.1 sprintf/fprintf
#include <stdio.h>
int fprintf(FILE *stream, const char *fmt, ...);
//流指针设置为stdout此函数同printf
//将格式化后的字符串输出到指定的流中
    参数:(流指针, 格式符...)
    
int sprintf(char *s,const char *fmt, ...);
//以指定的格式把一个字符串输出到缓冲区中
	参数:(缓冲区首地址, 格式符...)//一般用字符数组存放字符串
7.2 示例:以指定年月日分别写入文件和缓冲区
int year, month, data;
FILE *fp;
char buf[64];

year = 2014; month = 10; date = 26;
fp = fopen("text.txt", "a+");
fprintf(fp,"%d-%d-%d\n", year, month, date);
sprintf(buf,"%d-%d-%d\n", year, month, date);
7.3 示例
time()用来获取系统时间(秒数)
//1970年到现在的秒数
    
localtime()将系统时间转换成本地时间

FILE *fp;
int line = 0;
char buf[64];
time_t t;
struct tm *tp;
if((fp = fopen("text.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);
}

文件IO

1. 概念
1.1 文件IO

遵循POSIX规范,也是一组函数,无缓冲,每次读写都会执行系统调用。

通过文件描述符fd表示一个打开的文件,可以访问各种类型的文件。

Linux下,标准IO基于文件IO实现。

1.2 文件描述符

文件描述符是一个非负整数。Linux为程序中每个打开的文件分配一个文件描述符;

文件描述符由系统从0开始分配,依次递增;

每个程序中打开的文件所对应的文件描述符都是独立的,互不影响。

2. 打开文件
2.1 open
#include <fcntl.h>
int open(const char *path, int oflag, ...);
//打开已经存在的文件时使用两个参数
//创建文件时,第三个参数指定新文件的访问权限

参数:
    要打开的文件的路径
    指定打开方式
    
返回值:
    成功时返回文件描述符;出错时返回EOF

设备文件只能通过open打开;需要通过特定的函数来创建。

2.2 打开方式
2.2.1 函数原型
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);

pathname:被打开的文件名(可包括路径名)

mode:三位8进制数表示 CREAT时必须加权限

O_RDONLY:只读方式打开文件;
O_WRONLY:只写方式打开;
O_RDWR:以读写的方式打开;
//以上三种读写方式互斥
O_CREAT:如果文件不存在,就创建新的文件;
//创建文件时mode表示新建文件的权限
O_EXCL:测试用CREAT创建文件时文件是否存在;
//如果文件存在,则可以返回错误信息
O_TRUNC:如果文件已经存在,打开文件时删除原有数据;
O_APPEND:以添加的方式打开文件,写的内容会追加到文件末尾;
2.2.2 示例

只写方式打开文件1.txt,不存在创建,存在清空;

int fd;
if((fd = open("1.txt", O_WRONLY|O_CREAT|O_TRUNC,0666)) < 0)
{
    perror("open");
    return -1;
}

读写方式打开1.txt,不存在创建,存在报错

int fd;
if((fd = open("1.txt", O_RDWR|O_CREAT|O_EXCL,0666)) < 0)
{
    if(errno == EEXIST)//因为文件存在报错
    {
		perror("exit error");
    }
    else
    {
        perror("other open");
    }
}
3. 文件关闭
3.1 close
#include <unistd.h>
int close(int fd);

参数:要关闭文件的文件描述符
返回值:成功时:0   出错时:-1
//程序结束时自动关闭打开的所有文件
//文件关闭后,文件描述符不再代表文件
4. 文件IO编程接口
4.1 读取文件
4.1.1 read函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

参数:
    指定读取的文件描述符
    缓冲区首地址//count一般指定为缓冲区大小
    指定读取的字节数//不能超过缓冲区大小
    
返回值:
    成功时返回实际读取的字节数;出错时返回-1

读到文件末尾时返回0

4.1.2 示例

从指定文件中读取内容并统计大小

int fd, n, count = 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)
{
    count += n;
}
printf("文件大小为:%dByte\n",count);
4.2写入文件
4.2.1 write函数
#include <unistd.h>
ssize_t write(int fd, void *buf,size_t count);

参数:
    指定写入的文件描述符
    发送缓冲区首地址//count一般指定为缓冲区大小
    指定读取的字节数//不能超过缓冲区大小
    
返回值:
    成功时返回实际写入的字节数;出错时返回-1
4.2.2 示例

将键盘输入的内容写入文件,直到输入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)//fgets会读取换行符
        break;
    write(fd, buf, strlen(buf));//buf中字符串长度不确定,所以用strlen
}
4.3定位文件
4.3.1 lseek函数
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

参数:
    文件描述符
    偏移量//同fseek
    基准点//同fseek
    
返回值:
    成功返回当前文件的读写位置(基准点+偏移量);出错时返回-1
4.4 思考

利用文件IO实现文件的复制 — 文件名通过命令行参数确定

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
//定义缓冲区
#define N 64

int main()
{
	int fds, fdt, n;//定义两个文件描述符,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[2], strerror(errno));
		return -1;
	}
    //只写方式打开目标文件
	if((fds = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC, 0666)) == -1)
	{//往标准错误流中打印错误信息
		fprintf(stderr, "open %s : %s\n", argv[2], strerror(errno));
		return -1;
	}
	//将从源文件中读取到的n个数据存入buf
	while((n = read(fds, buf, N)) > 0)
	{
		write(fdt, buf, n);//把缓冲区的内容写入目标文件,读多少写多少,用n不用N
	}
	close(fds);
	close(fdt);
	return 0;
}
5. 目录操作和文件属性
5.1 读取目录
5.1.1 opendir函数

打开一个目录文件

#include <dirent.h>
DIR *opendir(const char *name);
//DIR是一个结构体类型,用来描述一个打开的目录文件

参数:打开目录文件的路径
    
返回值:成功返回目录流指针;出错时返回NULL;
5.1.2 readdir函数

读取目录流中的内容

#include <dirent.h>
struct dirent *readdir(DIR *dirp);
结构体中包含成员char d_name[256] 存放目录项中文件的名称

   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打开的目录流指针
    
返回值:一个结构体指针(用来描述目录流中的目录项)  //需要用循环来读取直到结束
    出错或到末尾时返回NULL;
5.1.3 closedir函数

关闭目录文件

#include <dirent.h>
int closedir(DIR *dirp);

参数:打开的目录流指针
   
返回值:成功时返回0;失败时返回-1 
5.1.4 示例

打印指定目录下的所有文件名称

DIR *dirp;
struct dirent *dp;
if(argc < 2){
    printf("Usage: %s <src_file> <dst_file>\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);
123456789101112
5.2 修改文件访问权限
5.2.1 chmod/fchmod
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
//通过路径指定要修改的文件
int fchmod(int fd, mode_t mode);
//通过文件按描述符指定要修改的文件---所以要先用open打开文件
返回值:
    成功时返回0;出错时返回-1//超级用户和文件所有者能修改文件权限

示例:

chmod("text.txt", 0666);
5.3 获取文件属性
5.3.1 stat/lstat/fstat 函数
#include <sys/stat.h>

   int stat(const char *pathname, struct stat *statbuf);
//符号链接文件指向的文件的文件属性
   int lstat(const char *pathname, struct stat *statbuf);
   int fstat(int fd, struct stat *statbuf);
//符号文件本身的文件属性

参数:
    路径
    结构体指针(指向的地址保存获取的文件属性)
    
   int fstat(int fd, struct stat *statbuf);
 //通过文件描述符获取文件属性

参数:    
    文件描述符
    结构体指针(指向的地址保存获取的文件属性)    
    
返回值:
    成功时返回0;出错时返回-1
5.3.2 结构体中存储的信息

struct stat是存放文件属性的结构体类型

//常用文件属性如下;完整结构体可以man 2 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;//文件最后修改时间
}

st_mode的文件类型获取—通过系统提供的宏来判断

S_IFREG(st_mode);  //普通文件
S_IFDIR(st_mode);  //目录文件
...

st_mode的文件访问权限获取—通过系统提供的宏来判断

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-miXTHbjD-1693032282811)(https://blog.csdn.net/weixin_55953651/article/details/%E6%96%87%E4%BB%B6IO/QQ%E6%88%AA%E5%9B%BE20220429052914.png)]

USR代表用户;GRP代表同组用户;OTH代表其他用户;

R、W、X分别代表可读、可写、可执行;1表示有相应权限,0表示无权限;

5.4 示例

获取并显示文件属性;$ ./a.out text.c ;-rw-r–r-- 317 2014-11-08 text.c

#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;//将time获得的秒利用localtime转换为本地时间
    //判断终端是否输入参数
    if(argc < 2)
    {
        printf("Usage : %s <files>\n", argv[0]);
        return -1;
    }
    //获取指定文件的属性并保存到buf中
    if(lstat(argv[1], &buf) < 0)
    {
        perror("lstat");
        return -1;
    }
    //判断当前文件类型
    switch(buf.st_mode & S_IFMT)
    {
        case S_IFREG:
            printf("-");//判断是否为普通文件
        case S_IFDIR:
            printf("d");//判断是否位目录文件
        //可以加case继续判断文件类型
    }
    //判断文件权限(从第8位开始判断每一位是1是0)
    for(n = 8; n>=0; n--)
    {
        if(buf.st_mode & (1<<n))//&(1左移n位,从第n位开始,依次检测是否为1)
        //如果第n位为1,&完以后结果为真,执行下列程序
        {
            //876.543.210 第8.5.2位表示读权限位,共同点是%3==2;同理可case1;case0;
            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;
}

1. 库的概念

一个二进制文件,包含的代码可以被程序调用。

根据库的不同功能可以分为标准c库、数学库、线程库等。

库有源码,可下载源码后编译,也可以直接安装二进制包。

库的目录/lib或/user/lib

库是事先编译好的,可以复用的代码。

在OS上运行的程序基本上都要使用库,可以提高开发效率。

Windows和Linux下库文件你的格式不兼容。

2. 库的分类

Linux下包含静态库和动态库

3.静态库

编译(链接)时把静态库中相关代码复制到可执行文件中

程序中已经包含静态库中的部分代码,运行时不再需要静态库;

程序运行时无需加载库,运行速度更快;

占用更多磁盘和内存空间;

静态库升级后,程序需要重新编译链接;

3.1 静态库的创建流程

确定库中函数的功能、接口

3.2 编写库源码

hello.c文件

#include <stdio.h>
void hello(void){
    printf("hello word\n");
    return;
}
3.3 编译生成目标文件
$gcc -c hello.c -Wall//先把 .c 生成对应的 .o
3.4 创建静态库
$ar crs libhello.a hello.o//ar crs 库文件名称 库中包含的目标文件(可以多个)
//库文件命名规则lib+name+.a   name为库名   已经为2进制形式
3.5 查看库中符号信息(可省略)

查看库中包含的函数名称

$nm libhello.a
    
    hello.o:
	0000000 T hello
        	U puts
3.6 链接静态库

text.c文件

#include <stdio.h>
void hello(void);//声明函数原型	
int main(){
    hello();//调用库函数
    return 0;

3.7 编译

编译text.c并链接静态库libhello.a

$ gcc -o text text.c -L. -lhello
//L.添加库的搜索路径,'.'代表当前路径(hello这个库放在当前路径下,并没有放到系统默认的lib路径中,系统编译时只会去默认路径搜索)
//l 指定要链接的库名,***而不是库文件名***  不加-l 编译器默认链接c库
//-o 指定程序名称
4. 动态库

编译(链接)时仅记录用到哪个共享库中的哪个符号,不复制共享库中相关代码;

程序不包含库中代码,尺寸小;

多个程序可共享一个库;

程序运行时需要加载库;

库升级方便,无需重新编译程序;

4.1 动态库的创建

确定库中函数的功能、接口

4.2 编写库源码

hello.c bye.c文件

#include <stdio.h>
void hello(void){
    printf("hello word\n");
    return;
}
12345
#include <stdio.h>
void hello(void){
    printf("Bye\n");
    return;
}
4.3 编译生成目标文件
$gcc -c -fPIC hello.c bye.c -Wall//先把 .c 生成对应的 .o
//-fPIC 生成位置无关代码---生成的.o文件可以在任意位置执行
4.4 创建共享库
$gcc -share -o libcommon.so.1 hello.o bye.o//gcc -shar -o 共享库文件名 库中包含的目标文件列表(可以多个)
//lib+name+.so.X  数字X表示版本号   name为库名   已经为2进制形式
4.5 为共享库创建符号链接文件
$ln -s libcommon.so.1 libcommon.so//ln -s 目标文件 创建的符号链接文件名称
//符号链接文件命名规则lib+name+.so   符号链接指向->实际的库文件
4.6 调用共享库

text.c文件

#include <stdio.h>
#include "common.h"
int main(){
    hello();
    bye();//调用库函数
    return 0;
}

common.h文件

void hello(void);
void bye(void);
4.7 编译

编译text.c并链接静态库libcommon.so

$ gcc -o text text.c -L. -lcommon
//L.添加库的搜索路径,'.'代表当前路径(hello这个库放在当前路径下,并没有放到系统默认的lib路径中,系统编译时只会去默认路径搜索)
//l 指定要链接的库名,***而不是库文件名***  不加-l 编译器默认链接c库

先去链接动态库,再去链接静态库,如果都没有则报错;

-static   //直接连接静态库

出错:共享库需要加载,系统没有在/lib或/user/lib中找到共享库,共享库在当前路径;

4.8 共享库加载

修改当前share的环境变量;//可以添加库的指定搜索路径

$export LD_LIBRARY_path=$LD_LIBRARY_path:.
//$引用原来的值:追加的新的路径   .代表当前路径

缺陷:只对当前的share有效;

修改系统的配置文件,把路径加到系统默认搜索路径

*.conf文件存放库名称和库的路径信息
//   添加/etc/ld.so.conf.d/*.conf文件,执行ldconfig刷新
12
$sudo vi /etc/ld.so.conf.d/my.conf
//在打开的文件中输入以下绝对路径并保存退出
/home/linux/io/share   //绝对路径
$sudo ldconfig

原文地址:https://blog.csdn.net/weixin_55953651/article/details/124595715

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值