Linux系统编程(一) ------ 文件操作函数

文件操作

打开文件

1.使用open()函数打开和创建文件

  • 手册文件 man 2 open

函数头文件及函数原型
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

函数参数:

  pathname:待打开文件的绝对路径和文件名。

  flags:打开的旗标类型,或称模式,
  O_RDONLY      只读模式打开文件
  O_WRONLY      只写模式打开文件
  O_RDWR        读写模式打开文件
  O_CREAT       若欲打开的文件不存在则自动建立该文件
  O_TRUNC       若文件存在并且以可写的方式打开时, 此旗标会令文件长度清为0, 
                而原来存于该文件的资料也会消失。
  O_EXCL        如果O_CREAT 也被设置, 此指令会去检查文件是否存在。 
                文件若不存在则建立该文件,否则将导致打开文件错误. 
                此外, 若O_CREAT 与O_EXCL 同时设置,并且欲打开的文件为符号连接, 
                则会打开文件失败。

  参数mode仅在flags中含有O_CREAT时有效,设定新建文文件的打开权限,有下列数种组合,
  S_IRWXU             00700 权限,代表该文件所有者具有可读、可写及可执行的权限。
  S_IRUSR 或S_IREAD,  00400 权限,代表该文件所有者具有可读取的权限。
  S_IWUSR 或S_IWRITE, 00200 权限,代表该文件所有者具有可写入的权限。
  S_IXUSR 或S_IEXEC,  00100 权限,代表该文件所有者具有可执行的权限。
  S_IRWXG             00070 权限,代表该文件用户组具有可读、可写及可执行的权限。
  S_IRGRP             00040 权限,代表该文件用户组具有可读的权限。
  S_IWGRP             00020 权限,代表该文件用户组具有可写入的权限。
  S_IXGRP             00010 权限,代表该文件用户组具有可执行的权限。
  S_IRWXO             00007 权限,代表其他用户具有可读、可写及可执行的权限。
  S_IROTH             00004 权限,代表其他用户具有可读的权限。
  S_IWOTH             00002 权限,代表其他用户具有可写入的权限。
  S_IXOTH             00001 权限,xit代表其他用户具有可执行的权限。

函数返回值: 打开文件成功,返回一个文件描述符 >2;打开失败,返回-1。

提示:使用 access()作用户认证方面的判断要特别小心, 例如在access()后再作open()空文件可能会造成系统安全上的问题。

2.使用create()函数创建并打开文件

函数原型

     int creat(const char *pathname, mode_t mode);
     相当于使用调用方式,
     open(const char *pathname, (O_CREAT|O_WRONLY|O_TRUNC));

函数参数:

  pathname   待打开文件的绝对路径和文件名。

  mode       新创建文件的权限,见上面open()

函数返回值:若成功会返回新的文件描述符,若有错误发生则会返回-1。

提示:creat()无法建立特别的装置文件,如果需要请使用mknod()。

读写文件

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

  • 手册文件 man 2 read

函数头文件及函数原型
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

函数参数:

 fd      文件指针,提供数据的文件的文件描述符,读取的数据的来源。

 buf     读到的数据所存放的内存空间的起始地址,同时文件的当前读写位置向后移。

 count   想要读取的数据的字节数,也是提供的存储空间字节数。

函数说明及返回值: read()会把参数fd 所指的文件传送count 个字节到buf 指针所指的内存中(暨在[0,count]区间变化)。

1.若参数count 为0,则read()不会有作用并返回0。
2.成功时,返回值为实际读取到的字节数。
3.如果返回0,表示已到达文件尾,暨碰到了EOF或是无可读取的数据。
4.此外文件读写位置会随读取到的字节移动。
5.有错误发生时则返回-1,而文件读写位置则无法预测。
提示:

read()函数负责从文件句柄中读取指定数量的字节,并将这些字节放在标量型变量中。read()函数和标准I/O函数fread()相同的方式处理I/O缓冲的。为了提高效率,read()函数并不是一次读取一个字节,而是读取一块数据并保存到临时存储区中。然后,C的fread函数与Perl的read函数会从临时缓冲区将数据一次一个字节地传送给程序。print()函数(而不是write()函数负责输出read()函数返回的实际字节。print()函数类似于C中的fwrite()函数。

附加:如果顺利 read()会返回实际读到的字节数,最好能将返回值与参数count 作比较,若返回的字节数比要求读取的字节数少,则
1. 读取普通文件时,读到文件末尾还不够 nbytes 字节。例如:如果文件只有 30 字节,
而我们想读取 100字节,那么实际读到的只有 30 字节,read 函数返回 30 。
此时再使用 read 函数作用于这个文件会导致 read 返回 0 。
2. 从终端设备(terminal device)读取时,一般情况下每次只能读取一行。
3. 从网络读取时,网络缓存可能导致读取的字节数小于 nbytes 字节。
4. 读取 pipe 或者 FIFO 时,pipe 或 FIFO 里的字节数可能小于 nbytes 。
5. 从面向记录的设备读取时,某些面向记录的设备(如磁带)每次最多只能返回一个记录。
6. 在读取了部分数据时被信号中断。读操作始于 cfo 。在成功返回之前,cfo 增加,
增量为实际读取到的字节数。

2.使用write()函数向指定文件中写入数据
  • 手册文件 man 2 write

函数头文件及函数原型
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

函数参数:

fd      待写入数据的文件的描述符

buf     写入数据的起始地址

count   待写入的数据的字节数

函数说明及返回值: write()会把参数buf 所指的内存写入count 个字节到参数fd 所指的文件内。当然,文件读写位置也会随之移动。
如果顺利会返回实际写入数据的字节数,表示写了部分或者全部的数据。
当有错误发生时,返回-1,我们要根据错误的类型来处理。如果错误为EINTR表示在写时出现了中断错误。如果为EPIPE表示网络连接出现了问题。

提示:对于普通文件,写操作始于 cfo 。如果打开文件时使用了 O_APPEND,则每次写操作都将数据写入文件末尾。成功写入后,cfo 增加,增量为实际写入的字节数。

定位文件

预概念: 所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。读写操作通常开始于 cfo,并且使 cfo 增大,增量为读写的字节数。文件被打开时,cfo 会被初始化为 0,除非使用了 O_APPEND 。

使用lseek()函数定位指定已打开文件的读写指针

  • 手册文件 man lseek

函数头文件及函数原型
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

函数参数:

fd 待重新定位读写指针位置的文件的描述符

offset 读写指针的偏移量(可正可负可为0)

whence 读写指针的偏移位置
    SEEK_SET   相对文件首部偏移,文件偏移量将被设置为 offset。
    SEEK_CUR   相对文件当前读写位置偏移,文件偏移量将被设置为 cfo 加上 offset,
               offset 可以为正也可以为负。
    SEEK_END   相对文件尾部偏移,文件偏移量将被设置为文件长度加上 offset,
               offset 可以为正也可以为负。

函数说明及返回值: 每一个已打开的文件都有一个读写位置,当打开文件时通常其读写位置是指向文件开头,若是以附加的方式打开文件(如O_APPEND),则读写位置会指向文件尾。当read()或write()时,读写位置会随之增加,lseek()便是用来控制该文件的读写位置。参数fildes 为已打开的文件描述词,参数offset 为根据参数whence来移动读写位置的位移数。当调用成功时则返回目前的读写位置,也就是距离文件多少个字节数。若有错误则返回-1。

例:

 将读写位置移到文件开头时: lseek(int fildes, 0, SEEK_SET);
 将读写位置移到文件尾时:   lseek(int fildes, 0, SEEK_END);
 想要取得目前文件位置时:   lseek(int fildes, 0, SEEK_CUR);
提示:

1.Linux 系统不允许lseek()对tty 装置作用,此项动作会令lseek()返回ESPIPE。
2.如果参数 fd(文件描述符)指定的是 pipe(管道)、FIFO 或者 socket,lseek 返回 -1 并且置 errno 为 ESPIPE。 对于普通文件(regular file),cfo 是一个非负整数。但对于特殊设备,cfo 有可能是负数。因此,我们不能简单地测试 lseek 的返回值是否小于 0 来判断 lseek 成功与否,而应该测试 lseek 的返回值是否等于 -1 来判断 lseek 成功与否。
3.lseek 仅将 cfo 保存于内核中,不会导致任何 I/O 操作。这个 cfo 将被用于之后的读写操作。
4.如果 offset 比文件的当前长度更大,下一个写操作就会把文件“撑大(extend)”。这就是所谓的在文件里创造"空洞(hole)”。没有被实际写入文件的所有字节由重复的 0 表示。空洞是否占用硬盘空间是由文件系统(file system)决定的。

关闭文件

使用close函数关闭指定文件

  • 手册文件 man close

函数头文件及函数原型

 #include <unistd.h>
 int close(int fd);

函数参数:

fd    为open()或creat()打开的文件描述符。

函数说明及返回值: 当使用完已打开的文件后若已不再需要则可使用 close()关闭该文件, 而close()会让数据写回磁盘, 并释放该文件所占用的资源. 参数fd 为先前由open()或creat()所返回的文件描述词.**返回值:若文件顺利关闭则返回0, 发生错误时返回-1.

提示:虽然在进程结束时,系统会自动关闭已打开的文件,但仍建议自行关闭文件,并确实检查返回值。

综合案例

// ./my-cp <src_file> <dst_file>

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

#define BUFFER_SIZE 100

int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        printf("usage : %s <src_file> <dst_file>\n",
            argv[0]);
        return 1;
    }
    
    int src_fd = 0;
    int dst_fd = 0;
    int n = 0;
    char buf[BUFFER_SIZE] = {'\0'};
    char *src_file = argv[1];
    char *dst_file = argv[2];
    
    // 1.open
    // 1.1 以只读方式打开源文件
    if((src_fd = open(src_file, O_RDONLY)) == -1)
    {
        perror("open src error");
        return 1;
    }
    // 1.2 以只写方式打开目的文件
    if((dst_fd = open(dst_file, 
            O_WRONLY | O_CREAT | O_TRUNC,
            S_IRUSR | S_IWUSR)) == -1)
    {
        perror("open dst error");
        return 1;
    }
    
    // 2. 循环从源文件中读取数据写入到目的文件中
    // 直到读到源文件的尾部为止
    // 2.1 read data from src_file
    // 2.2 write data to dst_file
    while((n = read(src_fd, buf, BUFFER_SIZE)) > 0)
    {
        write(dst_fd, buf, n);
    }
    
    // 3.close
    close(src_fd);
    close(dst_fd);

    return 0;
}

// 练习:
// 实现一个相对完整版的cp程序,要求能够判断出目标文件是否存在。
//  如果存在,给出提示是否覆盖。
// 思路:
// 1.打开源文件
// 2.判断目的文件是否存在
// 3.如果目的文件存在,提示是否覆盖
// 4.如果选择覆盖,则以只写的方式打开文件,并截短文件内容(O_TRUNC)
// 5.如果选择不覆盖,则提醒输入新的保存文件名,并已只写方式打开
// 6.循环读取源文件内容,写入到目的文件中
// 7.关闭已打开的文件

// 思考题1:能否关闭标准输入文件、标准输出文件、标准出错文件?

参考资料

刘老师上课资料及网上前辈资料
计算机操作系统教程:介绍现代操作系统原理及应用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容