附录A 文件与文件系统

什么是文件

知识点:UNIX中文件是字节流,一串0或多个字节,不区分二进制/文本的内部格式。
作用:设备、管道、普通文件全部统一抽象成字节流,读写接口完全一致,程序不用关心数据来源是磁盘、网卡、终端。

  • 文本文件:字节流以LF(\n,十进制10)作为行结束;Windows是CRLF(\r\n)。
  • 二进制文件:任意字节,文本文件属于二进制子集。
  • 空文件:字节数为0,依然是合法文件,常用于标记状态(时间戳标记、告警占位)。
  • 虚拟文件 /proc:不是磁盘上真实文件,内核向外暴露进程、硬件信息的虚拟字节流。

示例读取scsi硬件信息:

1
cat /proc/scsi/scsi

警告:文本程序遇到超长行、不可打印控制字符,行为会不可预期。

文件如何命名

知识点1:文件名允许字符限制

  • 禁止字符:NUL(0x00)、斜杠/。/是路径分隔符;NUL是C语言字符串结束标记。
  • 现实最佳实践:只用ASCII字母、数字、-、_、.。
  • 不推荐:空格、shell元字符* ? [] ! $、文件名以-开头(会被识别成命令选项)。

知识点2:大小写敏感 UNIX文件名区分大小写:readme、Readme、README是3个完全不同文件。

修改LC_ALL=C,启用传统ASCII排序。

知识点3:文件名长度限制

  • NAME_MAX:单文件名最大字符数,POSIX最小14,现代系统普遍255。
  • PATH_MAX:完整路径总字符上限。

查询系统限制示例:

1
2
getconf NAME_MAX .     # 当前文件系统单文件名最大长度
getconf PATH_MAX .     # 当前文件系统完整路径最大长度

重要实操:shell脚本中,存放文件名的变量必须双引号包裹,应对文件名带空格、特殊字符。

1
2
3
4
# 正确
rm "$filename"
# 错误,空格会分词
rm $filename

UNIX层级式文件系统

文件系统架构

知识点1:树状结构 根目录/,所有目录嵌套形成一棵树。 两个特殊目录:

  • . 当前目录本身
  • .. 父目录;根目录的..指向自己

知识点2:路径规则

  1. 连续多个斜杠 //tmp///x等价于/tmp/x,POSIX标准会合并。
  2. 路径末尾带/,语义代表这是目录。

知识点3:mount 挂载

作用:把一个文件系统“嫁接”到现有目录树上;被挂载目录原有内容会被隐藏,卸载后恢复。

1
2
3
mount /mnt/cdrom     # 挂载光驱
umount /mnt/cdrom    # 卸载
mount                # 查看全部已挂载文件系统

配置文件:/etc/fstab,开机自动挂载配置。

排查无法umount:使用lsof查看哪些进程占用目录。

文件系统实现概况(inode核心)

知识点1:inode(索引节点)

作用:保存文件元数据:大小、权限、属主、时间戳、磁盘块位置;inode不保存文件名! 文件名保存在目录文件里面:目录存储「文件名 ↔ inode编号」映射。

现实意义:

  1. 同一个inode可以被多个目录条目引用 → 硬链接
  2. 文件删除:只是删除目录条目;inode连接计数归0才真正释放磁盘块。

知识点2:硬链接 vs 符号链接(软链接)

类型 关键点 限制
硬链接 多个文件名指向同一个inode 不能跨文件系统;不能链接目录(除 . ..)
符号链接 保存文本形式路径,指向另一个文件名 可以跨文件系统,可以指向目录;可以循环断裂

示例,软链接循环报错:

1
2
3
4
ln -s one two
ln -s two one
cat one
# cat: one: Too many levels of symbolic links

软链接移动坑:写相对路径的软链接,移动父目录还能正常;写绝对路径移动后大概率断裂。

设备作为UNIX文件

知识点:一切皆文件,设备放在/dev

作用:磁盘、声卡、串口全部抽象成文件,open/read/write统一操作。

常用特殊虚拟设备:

  1. /dev/null:黑洞,写入全部丢弃,读取立刻返回EOF
  2. /dev/tty:当前终端
  3. /dev/urandom:获取系统随机字节,密码脚本常用

UNIX文件到底可以多大

  1. 文件大小受inode存储的长度字段、磁盘块大小共同约束;老32位系统单文件上限约2GB。
  2. 文件系统会预留一部分磁盘空间只允许root使用,防止普通用户把磁盘彻底打满系统崩溃。

查看文件系统磁盘空间:

1
2
3
df      # 磁盘使用
df -i   # 查看inode使用量
du -s * # 查看目录占用大小

没有名称的文件

知识点:重定向的文件,进程内部不记住原始文件名

  • /dev/stdin /dev/stdout /dev/stderr,部分系统提供。
  • 命令惯例:单独一个-代表标准输入/标准输出。

UNIX文件属性

文件所有权与权限

知识点1:所有权 每个文件有 uid(用户ID)、gid(组ID);数字ID才是内核识别的,用户名只是/etc/passwd做的映射。

知识点2:基础权限 r w x

对普通文件:

  • r:读内容
  • w:写修改内容
  • x:可以执行(程序/脚本必须有x权限才能运行)

对目录含义完全不一样(高频考点)

  • r:可以列出目录里面的文件名
  • w:可以在目录创建、删除文件(哪怕你对这个文件没有写权限!)
  • x:可以进入cd到此目录、可以访问目录下文件(必须知道文件名)

目录实操演示:

1
2
3
mkdir test
chmod a-r test     # 取消读权限:ls test失败,但 ls test/the-file 若知道文件名仍可访问
chmod a-x test     # 取消执行权限:完全无法cd、无法访问内部任何文件

r (目录:Read):读取这张表格 → 列出目录内文件名
通俗比喻:文件夹像一个带盒子:
r = 允许你掀开盒盖,浏览盒子里面全部纸条(文件名列表)
盒子盖子不让你掀开(无 r),但你已经提前记住纸条名字,你依旧伸手可以摸到那张纸条(前提要有 x,允许伸手进去)。

w(目录:Write):修改这张表格 → 创建、删除、重命名条目
通俗比喻:文件夹像一个带盒子:
w = 允许你在盒子里新增、扔掉、改写纸条(增删/重命名文件名条目)
哪怕纸条上的内容你完全看不懂(文件本身没有读写权限),也依旧可以把纸条直接扔掉(删除文件)。

x(目录:Execute,搜索位):进入目录、解析路径,访问内部条目
通俗比喻:文件夹像一个带盒子:
x = 允许你把手伸进盒子内部,定位摸到指定的某一张纸条(按文件名查找文件inode)
盒子不让你伸手进去(无 x),就算你牢牢记住所有纸条的名字,也碰不到盒子里任何一张纸条。

umask:新建文件/目录权限掩码

作用:不是设置权限,是 “要拿掉哪些权限位”。

计算规则:

  • 新建文件原始最大权限:0666(rw‑rw‑rw‑,文件默认没有执行 x)
  • 新建目录原始最大权限:0777(rwx‑rwx‑rwx,目录需要 x 进入)

最终权限 = 原始权限 减去 umask 掩码的权限位(按位掩码清除)

1
2
3
umask          # 查看当前掩码
umask 027      # 设置掩码,新建文件拿掉组写、其他人读写执行
touch testfile

027 拆解

八进制 027,拆分成 3 组,分别对应 用户(u) 组(g) 其他(o)

  • 第一位0:用户 u → 不拿掉任何权限
  • 第二位2:组 g → 拿掉w(写)权限
  • 第三位7:其他 o → 拿掉r w x全部三种权限

脚本最佳实践:脚本开头明确设置umask,放在一切创建文件操作之前。

① 新建普通文件:touch testfile

文件原始默认最大权限:0666 → -rw‑rw‑rw‑ 减去 umask 027:

  • 用户:6‑0 =6 → rw‑
  • 组:6‑2 =4 → r‑‑
  • 其他:6‑7 =0 → ‑‑‑

testfile 最终权限:‑rw‑r‑‑‑‑‑(0640)

1
2
3
4
umask 027
touch testfile
ls -l testfile
# -rw-r-----  1 xxx  xxx  0 xxx testfile

注意:普通文件天生不会自动带上执行 x,哪怕原始是 666,不是 777。touch 出来的文件永远没有 x 权限。

② 如果新建目录 mkdir testdir

目录原始最大权限是 0777,减去 umask 027

  • 用户:7‑0 =7 → rwx
  • 组:7‑2 =5 → r‑x
  • 其他:7‑7 =0 → —

目录最终权限:drwxr‑x‑‑‑(0750)

1
2
3
mkdir testdir
ls -ld testdir
# drwxr-x---  1 xxx  xxx  xxx testdir

特殊权限位:set‑uid、set‑gid、sticky粘滞位

  1. set‑uid:执行进程拥有文件所有者权限。

重要警告:shell脚本不要设置set‑uid,有大量安全漏洞。

  1. set‑gid:新建文件继承目录组ID;常用于协作目录。
  2. sticky粘滞位:典型/tmp目录;目录开启后,用户只能删除自己创建的文件。

文件时间戳

inode维护3种时间戳

  1. atime access:文件被读取访问时间
  2. mtime modify:文件内容被修改时间
  3. ctime change:inode元数据变更(权限、属主、硬链接增减)
1
2
3
4
ls -l      # 展示 mtime 修改时间
ls -u      # 展示 atime 访问时间
ls -c      # 展示 ctime inode变更时间
touch file # 修改atime、mtime,不改动ctime

备份工具tar会修改atime,会破坏原始访问时间。

文件连接(硬链接/软链接补充)

  1. 硬链接:同一个inode;修改任意文件名,全部链接同步看到变更;删除一个链接不会删除数据,链接计数归零才销毁。
  2. 软链接:保存路径字符串;源文件删除,软链接变成断裂链接。

UNIX扩展文件名惯例

UNIX系统文件扩展名没有内核强制意义,仅仅是人约定俗成。 .sh shell脚本、.c C源码、.tar打包文件只是惯例;没有.exe概念,是否可执行看权限x位。

临时备份文件惯例:~、#后缀/前缀,编辑器自动生成备份。

小结

整理本书附录核心要点清单

  1. UNIX把一切抽象字节流,普通文件、设备、proc虚拟文件统一处理。
  2. inode保存文件元数据;目录保存文件名‑inode映射;硬链接、软链接机制。
  3. 文件、目录的r/w/x权限含义目录和普通文件语义不一样;umask控制新建文件默认权限。
  4. mount挂载构建文件系统树;/tmp使用sticky粘滞位保护公共目录。
  5. 文件名要避开空格、特殊元字符,脚本变量引用文件名务必双引号"$var"。
  6. 文件3种时间戳 atime/mtime/ctime,各自触发条件不同。