来源:微信公众号「编程学习基地」

文件操作

在进行 Linux 文件操作之前,我们先简单了解一下 Linux 文件系统

Linux文件类型

Linux中文件类型只有以下这几种:

符号 文件类型
- 普通文件
d 目录文件,d是directory的简写
l 软连接文件,亦称符号链接文件,s是soft或者symbolic的简写
b 块文件,是设备文件的一种(还有另一种),b是block的简写
c 字符文件,也是设备文件的一种(这就是第二种),c是character的文件
s 套接字文件,这种文件类型用于进程间的网络通信
p 管道文件,这种文件类型用于进程间的通信

怎么判断文件类型?

  1. $ ls -l
  2. total 8
  3. drwxrwxr-x 2 ubuntu ubuntu 4096 Oct 25 15:30 dir
  4. prw-rw-r-- 1 ubuntu ubuntu 0 Oct 25 15:30 FIFOTEST
  5. -rw-rw-r-- 1 ubuntu ubuntu 2 Oct 25 15:25 main.c
  6. lrwxrwxrwx 1 ubuntu ubuntu 6 Oct 25 15:28 main.c-temp -> main.c
  7. srwxrwxr-x 1 ubuntu ubuntu 0 Oct 25 15:24 test.sock

ls -l 第一个字母代表文件类型

Linux文件权限

文件权限是文件的访问控制权限,那些用户和组群可以访问文件以及可以执行什么操作

查看文件权限

文件类型后面紧跟着的就是文件权限

简单介绍下文件权限,如下图所示:

因为Linux是一个多用户登录的操作系统,所以文件权限跟用户相关。

修改文件权限

1.以二进制的形式修改文件权限

什么是二进制形式?以main.c的权限为例

  1. -rw-rw-r-- 1 ubuntu ubuntu 2 Oct 25 15:25 main.c

文件的权限为rw-rw-r--,对应的二进制为664,如何计算呢,看下表

可读 可写 可执行
字符表示 r w x
数字表示 4 2 1

所有者的权限为rw-,对应着4+2+0,也就是最终的权限6,以此类推,用户组的权限为6,其他用户的权限为4.

修改文件权限需要用到chmod命令,如下所示

  1. $ ls -l
  2. -rw-rw-r-- 1 ubuntu ubuntu 2 Oct 25 15:25 main.c
  3. $ chmod 666 main.c
  4. $ ls -l
  5. -rw-rw-rw- 1 ubuntu ubuntu 2 Oct 25 15:25 main.c

二进制的计算不要算错了

2.以加减赋值的方式修改文件权限

还是用到chmod命令,直接上手

  1. $ ls -l
  2. -rw-rw-rw- 1 ubuntu ubuntu 2 Oct 25 15:25 main.c
  3. $ chmod o-w main.c
  4. $ ls -l
  5. -rw-rw-r-- 1 ubuntu ubuntu 2 Oct 25 15:25 main.c
文件所有者 user 文件所属组用户 group 其他用户 other
u g o

+- 分别表示增加和去掉相应权限

简单的了解了Linux下的文件操作之后就开始进入代码编程阶段

Linux error

获取系统调用时的错误描述

Linux下的文件操作属于系统调用,Linux中系统调用的错误都存储于 errno 中,例如文件不存在,errno置 2,即宏定义 ENOENT ,对应的错误描述为 No such file or directory

打印系统调用时的错误描述需要用到 strerror,定义如下

  1. #include <string.h>
  2. char *strerror(int errnum);

查看系统中所有的 errno 所代表的含义,可以采用如下的代码:

  1. /* Function: obtain the errno string
  2. * char *strerror(int errno)
  3. */
  4. #include <stdio.h>
  5. #include <string.h> //for strerror()
  6. //#include <errno.h>
  7. int main()
  8. {
  9. int tmp = 0;
  10. for(tmp = 0; tmp <=256; tmp++)
  11. {
  12. printf("errno: %2d\t%s\n",tmp,strerror(tmp));
  13. }
  14. return 0;
  15. }

可以自己手动运行下,看下输出效果

打印错误信息

之前谈到Linux系统调用的错误都存储于 errno 中,errno定义如下

  1. #include <errno.h>
  2. int errno;

除了 strerror 可以输出错误描述外,perror 也可以,而且更加方便

打印系统错误消息 perror ,函数原型及头文件定义如下

  1. #include <stdio.h>
  2. void perror(const char *s);

使用示例:

  1. /**
  2. * @brief 文件不存在打开失败时打印错误描述
  3. */
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <stdio.h>
  7. #include <string.h> //strerror
  8. #include <errno.h> //errno
  9. int main() {
  10. int fd = open("test.txt", O_RDWR);
  11. if(fd == -1) {
  12. perror("open");
  13. //printf("open:%s\n",strerror(errno));
  14. }
  15. close(fd);
  16. return 0;
  17. }

当文件 test.txt 不存在时打印如下

  1. ./main
  2. open: No such file or directory

系统IO函数

UNIX环境下的C 对二进制流文件的读写有两种体系:

  1. fopen,fread,fwrite ;
  2. open, read, write;

fopen 系列是标准的C库函数;open系列是 POSIX 定义的,是UNIX系统里的system call。

文件操作不外乎 open,close,read,write,lseek,从打开文件开始介绍

open/close

open 定义如下

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. int open(const char *pathname, int flags);
  5. int open(const char *pathname, int flags, mode_t mode);

- pathname:要打开的文件路径

- flags:对文件的操作权限设置还有其他的设置

​ O_RDONLY, O_WRONLY, O_RDWR 这三个设置是互斥的

- mode:八进制数,表示创建出的新的文件的操作权限,例如:0775

close 定义如下

  1. #include <unistd.h>
  2. int close(int fd);
打开文件

通过open打开一个存在的文件

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <stdio.h>
  5. #include <unistd.h>
  6. int main() {
  7. int fd = open("test.txt", O_RDONLY);// 打开一个文件
  8. if(fd == -1) {
  9. perror("open");
  10. }
  11. // 读写操作
  12. close(fd); // 关闭
  13. return 0;
  14. }

如果文件存在,打开文件;文件不存在,打开失败,错误描述为No such file or directory

创建文件

通过open创建一个新的文件

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. int main() {
  7. // 创建一个新的文件
  8. int fd = open("test.txt", O_RDWR | O_CREAT, 0777);
  9. if(fd == -1) {
  10. perror("open");
  11. }
  12. // 关闭
  13. close(fd);
  14. return 0;
  15. }

这里有个东西需要注意一下,先看输出结果

  1. $ ls -l test.txt
  2. -rwxrwxr-x 1 dengzr dengzr 0 Oct 27 19:50 test.txt

创建文件的同时赋予文件权限,在上面的Linux文件权限中已经介绍过了文件权限

创建文件时赋值的权限为777,但是创建的文件权限为775,这是我们需要注意的地方。

在linux系统中,我们创建一个新的文件或者目录的时候,这些新的文件或目录都会有默认的访问权限。默认的访问权限通过命令umask查看。

  1. $ umask
  2. 0002

用户创建文件的最终权限为mode & ~umask

例如Demo中创建的文件权限mode = 0777,所以最终权限为 0775

  1. 777 -> 111111111
  2. ~002 -> 111111101 &
  3. 775 -> 111111101

修改默认访问权限

更改umask值,可以通过命令 umask mode 的方式来更改umask值,比如我要把umask值改为000,则使用命令 umask 000 即可。

  1. $ umask
  2. 0002
  3. $ umask 000
  4. $ umask
  5. 0000

删除test.txt重新运行程序创建

  1. $ ./create
  2. $ ls -l test.txt
  3. -rwxrwxrwx 1 dengzr dengzr 0 Oct 27 20:06 test.txt

ps:这种方式并不能永久改变umask值,只是改变了当前会话的umask值,打开一个新的shell输入umask命令,可以看到umask值仍是默认的002。要想永久改变umask值,则可以修改文件/etc/bashrc,在文件中添加一行 umask 000

read/write

文件I/O最基本的两个函数就是read和write,在《unix/linux编程实践教程》也叫做unbuffered I/O。

这里的ubuffered,是指的是针对于read和write本身来说,他们是没有缓存机制(应用层无缓冲)。

read定义如下

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

参数:

- fd:文件描述符

- buf:保存读取数据的缓冲区

- count:读取数据的大小

返回值:

- 成功:

>0: 返回实际的读取到的字节数

=0:文件已经读取完了

- 失败:-1 ,并且设置errno

简单应用一下,示例Demo

  1. #include <fcntl.h>
  2. #include <unistd.h>
  3. #include <stdio.h>
  4. #include <errno.h> //errno
  5. int main() {
  6. int fd = open("test.txt", O_RDWR);
  7. if(fd == -1) {
  8. perror("open");
  9. }
  10. char buff[1024];
  11. memset(buff,'\0',1024);
  12. int readLen = read(fd,buff,1024);
  13. printf("readLen:%d,data:%s",readLen,buff);
  14. close(fd);
  15. return 0;
  16. }

Demo读取 test.txt 里面的数据并显示

  1. $ ./main
  2. readLen:5,data:text

write定义如下

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

参数:

- fd:文件描述符

- buf:待写入数据块

- count:写入数据的大小

返回值:

- 成功:实际写入的字节数

- 失败:返回-1,并设置errno

同样简单应用一下,Demo如下

  1. #include <fcntl.h>
  2. #include <unistd.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. #include <errno.h> //errno
  6. int main() {
  7. int fd = open("test.txt", O_WRONLY | O_CREAT ,0775);
  8. if(fd == -1) {
  9. perror("open");
  10. }
  11. char buf[256] = "text";
  12. int Len = write(fd,buf,strlen(buf));
  13. printf("readLen:%d,data:%s\n",Len,buf);
  14. // close(fd);
  15. return 0;
  16. }

在Demo中注释掉close,数据写入成功

  1. $ ./main
  2. readLen:4,data:text
  3. $ cat test.txt
  4. text

对比fwrite等标准的C库函数,write是没有缓冲的,不需要fflush或者close将缓冲数据写入到磁盘中但是程序open后一定要close,这是一个良好的编程习惯。

ps:其实write是有缓冲的,在用户看不到的系统层,我们可以理解为没有缓冲

lseek

作用:对文件文件指针进行文件偏移操作

lseek定义如下

  1. #include <sys/types.h>
  2. #include <unistd.h>
  3. off_t lseek(int fd, off_t offset, int whence);

参数:

​ - fd:文件描述符

​ - offset:偏移量

​ - whence:

​ SEEK_SET :设置文件指针的偏移量

​ SEEK_CUR :设置偏移量:当前位置 + 第二个参数offset的值

​ SEEK_END:设置偏移量:文件大小 + 第二个参数offset的值

返回值:返回文件指针的位置

lseek和标准C库函数fseek没有什么区别,几个作用简单了解一下

1.移动文件指针到文件头

  1. lseek(fd, 0, SEEK_SET);

2.获取当前文件指针的位置

  1. lseek(fd, 0, SEEK_CUR);

3.获取文件长度

  1. lseek(fd, 0, SEEK_END);

示例Demo如下

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. int main() {
  7. int fd = open("test.txt", O_RDWR);
  8. if(fd == -1) {
  9. perror("open");
  10. return -1;
  11. }
  12. // 获取文件的长度
  13. int len = lseek(fd, 0, SEEK_END);
  14. printf("file len:%d\n",len);
  15. // 关闭文件
  16. close(fd);
  17. return 0;
  18. }
  1. ./main
  2. file len:4

linux下的标准输入/输出/错误

在文件IO操作里面一直讲到文件描述符,那我就不得不提一下linux中的标准输入/输出/错误

在C语言的学习过程中我们经常看到的stdin,stdout和stderr,这3个是被称为终端(Terminal)的标准输入(standard input),标准输出( standard out)和标准错误输出(standard error),这对应的是标准C库中的fopen/fwrite/fread。

但是在在Linux下,操作系统一级提供的文件API都是以文件描述符来表示文件,对应的的标准输入,标准输出和标准错误输出是0,1,2,宏定义为STDIN_FILENO、STDOUT_FILENO 、STDERR_FILENO ,这对应系统API接口库中的open/read/write。

标准输入(standard input)

在c语言中表现为调用scanf函数接受用户输入内容,即从终端设备输入内容。也可以用fscanf指明stdin接收内容或者用read接受,对应的标准输入的文件标识符为0或者STDIN_FILENO。

  1. #include<stdio.h>
  2. #include<string.h>
  3. int main()
  4. {
  5. char buf[1024];
  6. //C语言下标准输入
  7. scanf("%s",buf);
  8. //UNIX下标准输入 stdin
  9. fscanf(stdin,"%s",buf);
  10. //操作系统级 STDIN_FILENO
  11. read(0,buf,strlen(buf));
  12. return 0;
  13. }

ps:注意read不可以和stdin搭配使用

标准输出(standard out)

在c语言中表现为调用printf函数将内容输出到终端上。使用fprintf指明stdout也可以把内容输出到终端上或者wirte输出到终端,对应的标准输出的文件标识符为1或者STDOUT_FILENO 。

  1. #include<stdio.h>
  2. #include<string.h>
  3. #include <unistd.h>
  4. int main()
  5. {
  6. char buf[1024];
  7. //C语言下标准输入 输出
  8. scanf("%s",buf);
  9. printf("buf:%s\n",buf);
  10. //UNIX下标准输入 stdin
  11. fscanf(stdin,"%s",buf);
  12. fprintf(stdout,"buf:%s\n",buf);
  13. //操作系统级 STDIN_FILENO
  14. read(STDIN_FILENO,buf,strlen(buf));
  15. write(STDOUT_FILENO,buf,strlen(buf));
  16. return 0;
  17. }

标准错误输出(standard error)

标准错误和标准输出一样都是输出到终端上, 标准C库对应的标准错误为stderr,系统API接口库对应的标准错误输出的文件标识符为2或者STDERR_FILENO。

  1. #include<stdio.h>
  2. #include<string.h>
  3. int main()
  4. {
  5. char buf[1024]="error";
  6. fprintf(stderr,"%s\n",buf);
  7. write(2,buf,strlen(buf));
  8. return 0;
  9. }

既然有标准输出,为什么要有标准错误呢?既生瑜何生亮?

一个简单的Demo让你了解一下,诸葛的牛逼

  1. #include <stdio.h>
  2. int main()
  3. {
  4. fprintf(stdout, "stdout");
  5. fprintf(stderr, "stderr");
  6. return 0;
  7. }

猜一下是先输出stdout还是stderr,按照正常思维是先输出stdout,再输出stderr。

但是stderr属于诸葛流,喜欢抢占先机,所以先输出stderr,再输出stdout。

~咳咳,扯远了,实际上stdout是块设备,stderr不是。对于块设备,只有当下面几种情况下才会被输入:遇到回车;缓冲区满;flush被调用。而stderr因为没有缓冲所以直接输出。

谈一下stdin和STDIN_FILENO区别

以前我一直没搞明白,以为stdin等于0,其实stdin类型为 FILE*;STDIN_FILENO类型为 int,不能相提并论,其次stdin属于标准I/O,高级的输入输出函数,对应的fread、fwrite、fclose等;STDIN_FILENO属于系统API接口库,对应的read,write,close。

上面都是我零碎的知识点总结一下备忘。

Linux文件IO操作的更多相关文章

  1. linux文件IO操作篇 (一) 非缓冲文件

    文件IO操作分为 2 种 非缓冲文件IO 和 缓冲文件IO 它们的接口区别是 非缓冲 open() close() read() write() 缓冲 fopen() fclose() fread() ...

  2. Linux文件IO操作函数概述

    文件概述 Linux中,一切皆文件.文件为操作系统服务和设备提供了一个简单而一致的接口.这意味着程序完全可以像使用文件那样使用磁盘文件.串行口.打印机和其他设备. 也就是说,大多数情况下,你只需要使用 ...

  3. linux文件IO操作篇 (二) 缓冲文件

    2. 缓冲文件操作 //规模较大 实时性低的文件 //当数据长度快要超过缓冲区的范围时,或者时间周期达到时,数据才被送往指定位置 //需要使用FILE * 作为文件标识符 //stdin 标准输入 / ...

  4. 转:Linux 文件IO理解

    源地址http://blog.csdn.net/lonelyrains/article/details/6604851 linux文件IO操作有两套大类的操作方式:不带缓存的文件IO操作,带缓存的文件 ...

  5. Linux学习记录--文件IO操作相关系统编程

    文件IO操作相关系统编程 这里主要说两套IO操作接口,各自是: POSIX标准 read|write接口.函数定义在#include<unistd.h> ISO C标准 fread|fwr ...

  6. 树莓派学习笔记——使用文件IO操作GPIO SysFs方式

    0 前言     本文描写叙述假设通过文件IO sysfs方式控制树莓派 GPIO端口.通过sysfs方式控制GPIO,先訪问/sys/class/gpio文件夹,向export文件写入GPIO编号, ...

  7. imx6用文件io操作gpio

    具体请参考: http://blog.csdn.net/u014213012/article/details/53140781 这里要注意的是: 要让linux支持文件io方式操作gpio,首先驱动必 ...

  8. 2.Linux文件IO编程

    2.1Linux文件IO概述 2.1.0POSIX规范 POSIX:(Portable Operating System Interface)可移植操作系统接口规范. 由IEEE制定,是为了提高UNI ...

  9. 9.2 Go 文件IO操作

    9.2 Go 文件IO操作 1.1.1. bufio包 带有缓冲区的IO读写操作,用于读写文件,以及读取键盘输入 func main() { //NewReader返回一个结构体对象指针 reader ...

随机推荐

  1. Anaconda配置国内镜像源

    1. 为conda配置(清华)镜像源 使用conda进行安装时,访问的是国外的网络,所以下载和安装包时会特别慢.我们需要更换到国内镜像源地址,这里我更换到国内的清华大学地址.(永久添加镜像) Wind ...

  2. 【PHP数据结构】栈的相关逻辑操作

    对于逻辑结构来说,我们也是从最简单的开始.堆栈.队列,这两个词对于大部分人都不会陌生,但是,堆和栈其实是两个东西.在面试的时候千万不要被面试官绕晕了.堆是一种树结构,或者说是完全二叉树的结构.而今天, ...

  3. TP5框架下实现数据库的备份功能-tp5er/tp5-databackup

    1.安装扩展 方法一: composer require tp5er/tp5-databackup dev-master 方法二 composer.json: "require": ...

  4. prometheus、node_exporter设置开机自启动

    方法一.写入rc.local 在/etc/rc.local文件中编辑需要执行的脚本或者命令,我个人习惯用这个,因人而异,有的项目可能需要热加载配置文件,用服务会更好 #普罗米修斯启动,需要后面接con ...

  5. Shell系列(29)- 单分支if语句格式

    单分支if条件语句 if [ 条件判断 ] ;then 程序 fi 或者 if [ 条件判断 ] then 程序 fi 注意点 if语句使用fi结尾,和一般语言使用大括号结尾不同 [ 条件判断 ]就是 ...

  6. Linux档案权限篇(一)

    查看档案的属性"ls-al". 即列出所有的档案的详细权限与属性(包括隐藏文件) 权限 第一个字符代表档案的类型: d:代表是目录 -:代表是文件 l:代表是连接文件(相当于win ...

  7. three.js 材质翻转

    刚学.这个鸟玩意儿卡了半天,记录一下. var skyBox = new THREE.Mesh(skyGeometry, skyMaterial); //创建一个完整的天空盒,填入几何模型和材质的参数 ...

  8. Yaml书写方法详解

    一.关于yaml语法详解 yaml通常以空格做锁进,一般是2个或者4个,如果写更多,只要格式对其 就不会报错 二.yaml基本语法规则 大小写敏感 使用锁进表示层级关系 缩紧时候不允许用tab键,只能 ...

  9. 基于深度学习的建筑能耗预测01——Anaconda3-4.4.0+Tensorflow1.7+Python3.6+Pycharm安装

    基于深度学习的建筑能耗预测-2021WS-02W 一,安装python及其环境的设置 (写python代码前,在电脑上安装相关必备的软件的过程称为环境搭建) · 完全可以先安装anaconda(会自带 ...

  10. 解决vue项目中遇到父组件的按钮或操作控制重新挂载子组件但是子组件却无效果的情况

    在vue项目中终会遇到需要父组件的按钮或操作控制重新挂载子组件的需求,我在新项目中就遇到这种需求.真实场景是父组件的早,中,晚三个按钮(代表三个时间段)来控制子组件的table表格列的动态加载. 子组 ...