linux -- 文件IO

一,linux man命令

1、什么是 man?

man = manual(手册),是 Linux 中查看命令、函数、配置文件等文档的工具。

2、基本用法

复制代码
man <命令/函数>

3、man 的章节(最重要!)

|---------------|------------------------------------|
| 内容 | 示例 |
| 用户命令(可执行程序) | man ls, man cat |
| 系统调用(内核提供的函数) | man 2 open, man 2 read |
| 库函数(C标准库等) | man 3 printf, man 3 malloc |
| 特殊文件(设备文件) | man 4 tty |
| 文件格式和约定 | man 5 passwd, man 5 fstab |
| 游戏 | |
| 杂项(惯例、协议等) | man 7 man-pages |
| 系统管理员命令 | man 8 ifconfig |
| 内核例程(非标准) | |

4、常用操作

4.1 指定章节查看

复制代码
man <章节号> <名称>

示例:

bash 复制代码
man 1 open    # 查看open命令(章节1)
man 2 open    # 查看open系统调用(章节2,我们写文件IO用的)
man 3 printf  # 查看printf库函数(章节3)
```### 2. 搜索所有章节

```bash
man -a <名称>    # 逐个显示所有匹配的手册页
man -k <关键字>   # 搜索包含关键字的手册页(= apropos)

4.2. 只看简短描述

bash 复制代码
whatis <命令>     # 显示手册页的单行描述

5. 在 man 中的导航(阅读时的按键)

|----------------|--------------|
| 按键 | 功能 |
| Space/PageDown | 向下翻页 |
| b/PageUp | 向上翻页 |
| q | 退出 |
| /pattern | 向下搜索 pattern |
| n | 下一个搜索结果 |
| N | 上一个搜索结果 |
| g/Home | 跳到开头 |
| G/End | 跳到结尾 |
| h | 显示帮助 |

6. 实战:用 man 学文件IO

6.1 查看 open 系统调用

bash 复制代码
man 2 open

你会看到:

  • 头文件:#include <fcntl.h>

  • 函数原型

  • 参数说明(flags 的各种取值)

  • 返回值

  • 错误码

6.2. 查看 read 系统调用

bash 复制代码
man 2 read

6.3. 查看目录操作

bash 复制代码
man 3 opendir   # 查看 opendir 库函数
man 3 readdir   # 查看 readdir

6.4. man man - 查看 man 自己的手册

bash 复制代码
man man   

7. 快速示例

bash 复制代码
man 2 write    # 正确,看系统调用
man write       # 可能会看到命令,不是我们要的

二、 open函数打开文件

open函数(补充)

cpp 复制代码
#include <stdio.h> // 标准输入输出,printf等
#include <errno.h> // 错误号,errno变量
#include <string.h> // 字符串函数,strerror等
#include <unistd.h> // UNIX标准函数,read/write/close/sleep

/*
 * ./open 1.txt           //运行方式:./open 1.txt
 * argc    = 2            //argc = 2:有2个参数(程序名+文件名)
 * argv[0] = "./open"     //argv[0]:程序名 "./open"
 * argv[1] = "1.txt"      //argv[1]:文件名 "1.txt"
 */

int main(int argc, char **argv)
{
    int fd;
    
    if (argc != 2) //检查用户是否提供了文件名- 如果没有给文件名(argc不等于2),打印使用说明
    {
        printf("Usage: %s <file>\n", argv[0]);
        return -1;
    }

    fd = open(argv[1], O_RDWR);//argv[1]:要打开的文件名,O_RDWR:以读写模式打开,返回值赋值给 fd(文件描述符)
    if (fd < 0)//打开失败
    {
        printf("can not open file %s\n", argv[1]);
        print[]f("errno = %d\n", errno);
        printf("err: %s\n", strerror(errno));
        perror("open");
    }
    else//打开成功
    {
        printf("fd = %d\n", fd);
    }

    while (1)
    {
        sleep(10);
    }

    close(fd);//关闭文件
    return 0;
}

在实战过程中遇到不了解的函数,可以直接使用man命令查询

命令逐行解释:

  1. 运行程序测试
cpp 复制代码
./open open.c   
/*
输出: fd = 3
运行程序,成功打开文件
返回的文件描述符是 3
*/
  1. 后台运行程序
cpp 复制代码
./open ./open.c &
/*
输出: [1] 4669

符号         说明
&            后台运行(程序在后台跑,不占终端)
[1]          作业号
4669         进程ID (PID)
*/
  1. 查看进程列表
cpp 复制代码
ps

/*
输出:
PID TTY      TIME CMD
2994 pts/1  00:00:00 bash
4669 pts/1  00:00:00 open    ← 我们的程序
4670 pts/1  00:00:00 ps
*/
  1. 进入正确的proc目录
cpp 复制代码
cd /proc/4724/
/*
/proc/4669/ = 4669号进程的信息目录
Linux的 /proc 文件系统是个虚拟文件系统,里面存着所有进程的信息!---
*/
  1. 查看进程目录内容
cpp 复制代码
ls

输出一大堆文件/目录,重点看:

|--------|-------------|
| 项目 | 作用 |
| fd | 文件描述符目录 |
| exe | 指向可执行文件 |
| cwd | 当前工作目录 |
| maps | 内存映射 |
| status | 进程状态 |

6.查看该进程打开的文件描述符

cpp 复制代码
ls fd

输出: 0 1 2 3

7. 详细查看文件描述符

cpp 复制代码
cd  /proc/4724/fd
ls -l

或者
cd /proc/4724/
ls fd -l
fd 指向 说明
0 /dev/pts/1 标准输入 → 终端
1 /dev/pts/1 标准输出 → 终端
2 /dev/pts/1 标准错误 → 终端
3 /home/book/01_open/open.c 🔴 我们用 open() 打开的文件!

三、使用open函数创建文件

创建文件:

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

/*
 * ./create 1.txt
 * argc    = 2
 * argv[0] = "./open"
 * argv[1] = "1.txt"
 */

int main(int argc, char **argv)
{
    int fd;
    
    if (argc != 2)
    {
        printf("Usage: %s <file>\n", argv[0]);
        return -1;
    }

    fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0777);
    /*
     O_RDWR   读写模式打开
     O_CREAT  如果文件不存在,就创建它!
     O_TRUNC  如果文件已存在,就清空它!
     0777 =  所有者、组、其他用户都有读写执行权限
     1 表示执行权限,2 表示可读权限 4 表示可写权限 可仔细累加
    */
    if (fd < 0)
    {
        printf("can not open file %s\n", argv[1]);
        printf("errno = %d\n", errno);
        printf("err: %s\n", strerror(errno));
        perror("open");
    }
    else
    {
        printf("fd = %d\n", fd);
    }

    while (1)
    {
        sleep(10);
    }

    close(fd);
    return 0;
}

执行结果如下:

第一个权限 0700 表示如下:

cpp 复制代码
S_IRWXU  ← 拆成三部分看
│││││││
││││││└─ U = User (所有者)
│││││└── X = eXecute (执行)
││││└─── W = Write (写)
│││└──── R = Read (读)
││└───── RWX = 读+写+执行
└─────── S = (权限常量前缀)

要点:umask

1、什么是 umask?

umask = U ser MASK(用户权限掩码)

  • 它是一个权限过滤器

  • 用来"屏蔽"掉某些权限

  • 创建新文件/目录时,它决定默认权限

2、umask 的工作原理

公式:

bash 复制代码
实际权限 = 请求权限 - umask

举例说明:

场景 请求权限 umask 实际得到的权限
创建文件 0666 (rw-rw-rw-) 0002 0664 (rw-rw-r--)
创建目录 0777 (rwxrwxrwx) 0002 0775 (rwxrwxr-x)

3、常用 umask 值

umask值 作用 文件默认权限 目录默认权限
0000 不屏蔽任何权限 0666 (rw-rw-rw-) 0777 (rwxrwxrwx)
0002 屏蔽其他用户写权限 0664 (rw-rw-r--) 0775 (rwxrwxr-x)
0022 屏蔽组和其他用户写权限 0644 (rw-r--r--) 0755 (rwxr-xr-x)
0027 严格屏蔽 0640 (rw-r-----) 0750 (rwxr-x---)

4、查看 umask```bash

umask # 输出:0002(八进制) umask -S # 输出:u=rwx,g=rwx,o=rx(符号形式,更直观)

bash 复制代码
五、修改 umask 临时修改(只对当前终端有效):
umask 0022 # 改成0022
umask 0002 # 改回0002

永久修改(对所有终端有效):

bash 复制代码
# 编辑 ~/.bashrc 或 ~/.profile
echo 'umask 0022' >> ~/.bashrc
source ~/.bashrc

5、umask 位详解

bash 复制代码
umask: 0002
       ││││
       │││└─ 其他用户权限掩码(2 = -w-)
       ││└── 组用户权限掩码(0 = ---)
       │└─── 所有者权限掩码(0 = ---)
       └──── 特殊位(通常不用管)
umask位 八进制值 屏蔽的权限
0 0 不屏蔽
1 1 屏蔽执行权限(--x)
2 2 屏蔽写权限(-w-)
3 3 屏蔽写和执行(-wx)
4 4 屏蔽读权限(r--)
5 5 屏蔽读和执行(r-x)
6 6 屏蔽读和写(rw-)
7 7 屏蔽所有权限(rwx)

6、回到之前的例子

bash 复制代码
fd = open("1.txt", O_RDWR | O_CREAT | O_TRUNC, 0777);

//                                                      请求权限
```**实际过程:**
请求权限:0777 (rwxrwxrwx) umask: 0002 (-------w-)
实际权限:0775 (rwxrwxr-x)

八、为什么需要 umask?

安全!

如果没有 umask:

创建的文件默认权限 `0666` = 任何人都能写!

创建的目录默认权限 `0777` = 任何人都能删文件!

有了 umask(默认0002):

文件:`0664` = 只有所有者和组能写

目录:`0775` = 只有所有者和组能删文件

---

九、快速记忆

| umask | 用途 |
|-------|------|
| `0002` | 普通用户(默认) |
| `0022` | 服务器/root用户(更安全) |

四、 使用write写文件

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

/*
 * ./write 1.txt  str1 str2
 * argc    = 4
 * argv[0] = "./write"
 * argv[1] = "1.txt"
 */

int main(int argc, char **argv)
{
    int fd;
    int i;
    int len;
    
    if (argc < 3)//参数小于3,argv[0] = 程序名,argv[1] = 文件名,argv[2] = 第1个要写的字符串
    {
        printf("Usage: %s <file> <string1> <string2> ...\n", argv[0]);
        return -1;
    }

    fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd < 0)
    {
        printf("can not open file %s\n", argv[1]);
        printf("errno = %d\n", errno);
        printf("err: %s\n", strerror(errno));
        perror("open");
    }
    else
    {
        printf("fd = %d\n", fd);
    }

    for (i = 2; i < argc; i++)//i=2,从第三个参数开始写入
    {
        len = write(fd, argv[i], strlen(argv[i]));
        if (len != strlen(argv[i]))
        {
            perror("write");
            break;
        }
        write(fd, "\r\n", 2);
    }

    close(fd);
    return 0;
}

五、 使用read函数读文件

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

/*
 * 程序功能:读取文件内容并打印到标准输出
 * 使用方法:./read 1.txt
 * 参数说明:
 *   argc = 2(程序名 + 文件名)
 *   argv[0] = "./read"(程序名)
 *   argv[1] = "1.txt"(要读取的文件名)
 */

int main(int argc, char **argv)
{
    int fd;                   // 文件描述符,用来标识打开的文件
    int i;                    // 循环变量(虽然定义了但本程序中没用到)
    int len;                  // 实际读取的字节数
    unsigned char buf[100];   // 缓冲区,用来存放从文件读取的数据,大小100字节

    /* 检查参数个数:必须提供一个文件名 */
    if (argc != 2)
    {
        printf("Usage: %s &lt;file&gt;\n", argv[0]);  // 打印使用说明
        return -1;                                     // 参数不对,异常退出
    }

    /* 打开文件:只读模式 */
    fd = open(argv[1], O_RDONLY);
    /* 
     * argv[1]: 要打开的文件名
     * O_RDONLY: 只读模式打开(不创建、不清空)
     * 返回值 fd:
     *   - 成功: 返回非负整数(文件描述符)
     *   - 失败: 返回 -1
     */

    /* 检查文件是否打开成功 */
    if (fd &lt; 0)
    {
        /* 打开失败,打印三种方式的错误信息 */
        printf("can not open file %s\n", argv[1]);  // 自定义错误提示
        printf("errno = %d\n", errno);               // 打印错误号
        printf("err: %s\n", strerror(errno));        // 把错误号转成文字描述
        perror("open");                               // 自动打印 "open: 错误描述"
    }
    else
    {
        /* 打开成功,打印文件描述符 */
        printf("fd = %d\n", fd);  // 通常是3(0,1,2被标准输入输出占用)
    }

    /* ==========================================
     * 核心部分:循环读取文件并打印
     * ==========================================
     */
    while (1)  // 无限循环,直到读到文件末尾
    {
        /* 从文件读取数据到缓冲区 */
        len = read(fd, buf, sizeof(buf)-1);
        /*
         * fd: 文件描述符
         * buf: 存放数据的缓冲区
         * sizeof(buf)-1: 最多读取 99 字节(留1字节给字符串结束符 '\0')
         * len: 实际读取到的字节数
         *   - len &gt; 0: 成功读取了 len 字节
         *   - len = 0: 到达文件末尾(EOF)
         *   - len &lt; 0: 读取出错
         */

        /* 情况1:读取出错 */
        if (len &lt; 0)
        {
            perror("read");  // 打印错误信息
            close(fd);        // 关闭文件
            return -1;        // 异常退出
        }
        
        /* 情况2:到达文件末尾(EOF) */
        else if (len == 0)
        {
            break;  // 跳出循环,结束读取
        }
        
        /* 情况3:成功读取到数据 */
        else
        {
            /*
             * 此时缓冲区中有数据:
             *   buf[0], buf[1], ..., buf[len-1] 是读取到的数据
             * 但是!read() 读取的是原始字节,不是C字符串!
             * C字符串需要以 '\0' 结尾,所以我们要手动加上!
             */
            buf[len] = '\0';  // 在读取的数据末尾加上字符串结束符
            printf("%s", buf); // 以字符串形式打印出来
        }
    }

    /* 读取完毕,关闭文件 */
    close(fd);
    return 0;  // 正常退出
}
特性 write.c read.c
open标志 `O_RDWR O_CREAT
核心循环 遍历参数写入 循环读取直到EOF
缓冲区处理 不需要 必须加 \0
判断结束 参数遍历完 len == 0
相关推荐
Trouvaille ~2 小时前
【项目篇】从零手写高并发服务器(六):EventLoop事件循环——Reactor的心脏
linux·运维·服务器·c++·高并发·epoll·reactor模式
海边的梦2 小时前
救命!此电脑网络位置异常?AD域排错3步封神,DNS/NetLogon/GPO根因一键定位
服务器·开发语言·php
林鸿群2 小时前
Ubuntu 26.04 本地安装 GitLab CE 完整教程(非 Docker 方式)
linux·ubuntu·gitlab·私有部署·代码托管·ubuntu 26.04·omnibus
Azure DevOps2 小时前
Azure DevOps:应用远程MCP服务器,提升工作效率
服务器·microsoft·flask·azure·devops
丁劲犇2 小时前
在Trae Solo模式下用Qt HttpServer和Concurrent升级MCP服务器绘制6G互联网覆盖区域
服务器·开发语言·qt·ai·6g·mcp·trae
勇闯逆流河2 小时前
【Linux】Linux进程概念(进程优先级,进程切换详解)
linux·运维·服务器
老师好,我是刘同学2 小时前
30个核心Linux命令速查手册
linux
fsj2009yx2 小时前
如何把无公网的求生之路2服务器借助VPS转发注册到steam master列表中
linux·wireguard·求生之路2
慵懒的猫mi2 小时前
deepin UOS AI 助手接入飞书(Feishu)配置指南
linux·人工智能·ai·gpt-3·飞书·文心一言·deepin