大家好,我是蟹老板~
到现在,我做互联网开发十多年了,服务器也折腾过不少。从物理机、虚拟机到容器,从 CentOS 6 一路折腾到 Rocky Linux、Ubuntu Server 和各种云主机,我越来越觉得,Linux 文件系统看似基础,实际上藏着很多能让人睡不好觉的细节。
很多人会用 cd、ls、rm,却不明白 inode 是什么;知道 chmod 777 能解决权限问题,却不知道它顺手制造了多大的安全问题;看见磁盘空间没了,只会删日志,结果日志删掉了,空间还是不回来。
这篇文章,我想把 Linux 的文件、目录、路径、inode、链接、权限和时间戳和大家唠唠。
文中配图已上传,有需要的朋友,可以去公众号发送文字:文章配图,即可获取图片~
一、一切皆文件 —— Linux 文件哲学
1.1 什么是“一切皆文件”
Linux 世界里有一句被说烂了的话。
一切皆文件。
刚开始听到这句话时,我也觉得有点装。
键盘是键盘,硬盘是硬盘,网络连接是网络连接,怎么能全叫文件呢?后来写服务端程序写多了,才发现这套设计真的有点东西。
Linux 并不是说所有对象在物理结构上都是普通文本文件,而是尽量通过统一的文件接口来访问各种资源。
程序处理普通文件时,会使用这样的系统调用。
open()read()write()close()
读取终端输入,思路差不多。
访问串口设备,思路也差不多。
读写管道、套接字以及某些内核接口,仍然可以使用类似的文件描述符模型。
应用程序 │ │ open / read / write / close ▼文件描述符 │ ├── 普通文件 ├── 目录 ├── 设备文件 ├── 管道 ├── 套接字 └── 内核虚拟文件
这种设计最大的好处不是“哲学听起来高级”,而是接口统一。
程序不需要为每一种设备重新发明一套读写逻辑。Shell 也可以把不同对象连接起来。
cat access.log | grep "500" | wc -l
这里的 cat 从文件读取内容,grep 从管道读取内容,wc 再从另一个管道读取结果。它们并不关心上游到底是磁盘文件、终端输入还是其他程序输出。
这就是 Unix 和 Linux 工具链为什么那么灵活。
一个工具只做好一件事,再通过文件描述符和管道把它们拼起来。看着朴素,组合起来却很凶。
当然,“一切皆文件”也不能理解得太死。设备往往还需要 ioctl 等额外接口,网络套接字也有自己的连接语义。Linux 做到的是尽量统一,而不是把世界粗暴地压成同一种东西。
1.2 Linux 七种文件类型
运行下面的命令时,输出第一列最前面的字符表示文件类型。
ls -l
示例结果可能是这样。
-rw-r--r-- 1 dev dev 1234 Jul 20 10:30 app.logdrwxr-xr-x 2 dev dev 4096 Jul 20 10:20 configlrwxrwxrwx 1 dev dev 12 Jul 20 10:18 current -> release-2026
最前面的字符不是权限位,它表示文件类型。
┌────────┬────────────┬────────────────────────────┐│ 字符 │ 文件类型 │ 常见对象 │├────────┼────────────┼────────────────────────────┤│ - │ 普通文件 │ 文本、图片、程序、压缩包 ││ d │ 目录文件 │ /etc、/home、项目目录 ││ l │ 符号链接 │ 版本链接、快捷入口 ││ c │ 字符设备 │ 终端、串口、随机数设备 ││ b │ 块设备 │ 硬盘、分区、光盘设备 ││ p │ 命名管道 │ 进程间单向数据传输 ││ s │ 套接字 │ 本地进程通信、服务端接口 │└────────┴────────────┴────────────────────────────┘
普通文件的类型字符是 -。
Linux 不会仅凭扩展名判断文件类型。一个没有 .txt 后缀的文件照样可能是文本文件,一个叫 photo.jpg 的文件也可能只是被恶作剧改了名字。
可以用 file 命令分析文件内容。
file READMEfile serverfile image.png
目录的类型字符是 d。
目录也是文件,只不过它存储的不是普通正文,而是文件名与 inode 编号之间的映射关系。这个概念后面讲 inode 时会再次出现。
符号链接的类型字符是 l。
它更接近 Windows 快捷方式,但又不完全一样。符号链接本身是一个独立文件,里面保存目标路径。
字符设备的类型字符是 c。
字符设备通常按字节流处理数据。终端设备就是常见例子。
ls -l /dev/tty
块设备的类型字符是 b。
硬盘和磁盘分区通常属于块设备。系统可以按数据块进行随机访问。
ls -l /dev/sdals -l /dev/nvme0n1
命名管道的类型字符是 p。
它常用于进程间通信。
mkfifo message.pipe
一个终端读取管道。
cat message.pipe
另一个终端写入数据。
echo "hello linux" > message.pipe
套接字文件的类型字符是 s。
MySQL、Docker、Nginx、systemd 等程序都可能通过 Unix Domain Socket 在本机进行通信。
ls -l /var/run/docker.sock
套接字文件看着像文件,却不是用 cat 随便读取的普通文件。它代表一个通信端点。
二、文件系统的骨架 —— 树形目录结构
Linux所有目录和文件,都源自唯一的根目录,也就是 /。
2.1 从根开始的树
Linux 没有盘符体系。
整个文件系统从唯一的根目录 / 开始,所有磁盘、分区、移动设备和网络文件系统都挂到这棵目录树的某个位置。
/├── boot├── dev├── etc├── home│ ├── alice│ └── bob├── mnt│ └── data├── proc├── sys├── usr└── var
假设服务器有一块系统盘和一块数据盘。
系统盘分区 /dev/nvme0n1p2 │ └── 挂载到 /数据盘分区 /dev/sdb1 │ └── 挂载到 /data
挂载完成后,用户访问 /data,实际上就是在访问 /dev/sdb1 上的文件系统。
mount /dev/sdb1 /data
这就是挂载点。
挂载点必须是目录。挂载以后,该目录原本的内容会暂时被覆盖在下面,不会真正消失,但在卸载文件系统前看不到。
这个坑我踩过。
有一年迁移测试服务器,我把新磁盘直接挂到了一个已经有文件的目录上。挂完一看,原文件全没了,差点当场写离职申请。后来执行 umount,文件又出现了。
不是文件丢了,是被挂载内容挡住了。
查看块设备和挂载关系,lsblk 很方便。
lsblk
典型输出如下。
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTSnvme0n1 259:0 0 200G 0 disk├─nvme0n1p1 259:1 0 1G 0 part /boot└─nvme0n1p2 259:2 0 199G 0 part /sdb 8:16 0 2T 0 disk└─sdb1 8:17 0 2T 0 part /data
增加文件系统类型和 UUID 后更实用。
lsblk -fNAME FSTYPE UUID MOUNTPOINTSnvme0n1├─nvme0n1p1 xfs 71bb... /boot└─nvme0n1p2 xfs f2e1... /sdb└─sdb1 ext4 a932... /data
完整目录树和挂载关系,可以这样理解。
/├── boot ← 单独的启动分区├── dev ← devtmpfs├── etc├── home├── proc ← proc 虚拟文件系统├── run ← tmpfs├── sys ← sysfs 虚拟文件系统├── data ← /dev/sdb1├── usr└── var
目录长得像普通目录,背后却可能来自完全不同的磁盘,甚至来自网络。
df -hT
它能查看各挂载点使用的文件系统类型和空间。
2.2 FHS 标准 —— 目录的“规矩”
不同发行版如果随便安排目录,软件开发和运维会非常痛苦。
A 系统把配置放 /config,B 系统放 /settings,C 系统又放 /system/conf。写安装脚本的人估计要边写边骂。
FHS,也就是 Filesystem Hierarchy Standard,试图规定 Linux 和类 Unix 系统中常见目录应该承担什么职责。
它大致约束两层内容。
根目录下面应该放哪些核心目录。
/usr 和 /var 内部又应该如何组织。
FHS 并不意味着所有发行版完全一致。实际系统中仍然会出现发行版差异、软件自定义目录以及容器镜像精简结构。但有了这套共识,至少不会乱得离谱。
这些年 Linux 目录结构还有一个明显变化,那就是 /usr 合并。
传统结构中,根目录下有一套基础命令目录。
/bin/sbin/lib/lib64
/usr 下又有一套。
/usr/bin/usr/sbin/usr/lib/usr/lib64
早期这样设计与磁盘容量、启动过程、分区安排有关。系统启动早期需要的工具放在根文件系统,其他程序可以放在单独挂载的 /usr 中。
现代 Linux 的启动机制已经变了,initramfs 也能承担早期启动任务。继续维护两套目录会增加打包、依赖和路径管理的复杂度。
很多现代发行版已经采用合并结构。
/bin -> usr/bin/sbin -> usr/sbin/lib -> usr/lib/lib64 -> usr/lib64
可以执行下面的命令看看。
ls -ld /bin /sbin /lib /lib64
在现代 Ubuntu、Debian、Fedora、Rocky Linux 等系统中,你很可能看到符号链接。
合并前后的结构差异大致如下。
传统结构/├── bin├── sbin├── lib└── usr ├── bin ├── sbin └── lib现代结构/├── bin ────────> /usr/bin├── sbin ────────> /usr/sbin├── lib ────────> /usr/lib└── usr ├── bin ├── sbin └── lib
这样做以后,程序文件集中到 /usr,根目录结构更简单,软件包也不用纠结一个命令到底应该放 /bin 还是 /usr/bin。
有些老教程会郑重其事地说,/bin 只放普通用户命令,/sbin 只放管理员命令。这个说法在历史上有依据,但到了现代系统里,边界已经没有那么绝对了。
别死背目录名。
先看你的系统实际结构。
2.3 核心目录逐一解读
/bin
/bin 传统上保存系统启动和基本操作所需的命令,例如 ls、cp、mv、cat。
现代发行版中,它通常是 /usr/bin 的符号链接。
readlink -f /bin
/sbin
/sbin 传统上保存系统管理命令,例如 fsck、mkfs、ip、shutdown。
现代系统中,它通常指向 /usr/sbin。
普通用户也可以运行其中部分命令,只是很多操作需要 root 权限。
/etc
/etc 是系统级配置文件的集中地。
/etc/passwd/etc/group/etc/hosts/etc/fstab/etc/ssh/sshd_config/etc/nginx/nginx.conf
这里通常放文本配置,不应该放大型二进制程序,也不应该把运行日志扔进去。
我见过有人把备份包放到 /etc/nginx/backup-final-final-真的最终版.tar.gz。能用是能用,就是半夜排错的人容易血压上来。
/dev
/dev 保存设备文件。
/dev/null/dev/zero/dev/random/dev/tty/dev/sda/dev/nvme0n1
/dev/null 是著名的“黑洞”。
command > /dev/null 2>&1
写进去的数据会被丢弃。
/dev/zero 可以持续产生零字节。
/dev/random 和 /dev/urandom 提供随机数据。
这些文件通常由内核和设备管理机制动态创建,不是管理员手工一个个建出来的。
/home
普通用户的家目录一般位于 /home。
/home/alice/home/bob
用户登录后,默认工作目录通常是自己的家目录。
echo "$HOME"
不过 /home 只是常见约定,不是宇宙真理。某些服务器、LDAP 环境或容器里,用户家目录可能放在其他地方。
/root
/root 是 root 用户的家目录。
它不是根目录 /。
这两个名字很像,新手经常混。
/ 根目录/root root 用户的家目录
root 的家目录单独放在根文件系统中,即使 /home 没有挂载,管理员通常仍能登录和维护系统。
/var
/var 用来保存经常变化的数据。
/var/log/var/cache/var/lib/var/spool/var/tmp
日志一般在 /var/log。
软件运行状态和持久数据经常放在 /var/lib。
包管理缓存可能位于 /var/cache。
邮件、打印任务等排队数据可能出现在 /var/spool。
生产服务器磁盘爆满,十次有六七次都能在 /var 找到线索。日志失控、容器层堆积、数据库数据增长,全是老演员。
/usr
/usr 是现代 Linux 用户空间程序和只读共享数据的核心位置。
/usr/bin/usr/sbin/usr/lib/usr/share/usr/local
不要把 /usr 简单理解成“某个用户的目录”。普通用户自己的文件应该放家目录。
/usr/local 通常留给本机管理员手动安装的软件。
/usr/local/bin/usr/local/lib/usr/local/etc
系统包管理器安装的软件一般进入 /usr,手工编译安装的软件常进入 /usr/local。这样升级系统包时不容易互相踩脚。
/tmp
/tmp 保存临时文件。
很多系统会定期清理它,重启后也可能清空。
任何用户通常都能在 /tmp 创建文件,但不能随便删除别人的文件,这依赖 Sticky Bit。
ls -ld /tmp
常见权限如下。
drwxrwxrwt
末尾的 t 就是 Sticky Bit。
/proc
/proc 是内核提供的虚拟文件系统。
它不真正占用普通磁盘空间,而是把进程和内核运行状态以文件形式暴露出来。
/proc/cpuinfo/proc/meminfo/proc/loadavg/proc/1234/
数字目录通常对应进程 PID。
cat /proc/cpuinfocat /proc/meminfo
很多系统监控工具,本质上就是在读取 /proc。
/sys
/sys 同样是虚拟文件系统,主要展示设备、驱动、总线和内核对象关系。
/sys/class/sys/block/sys/devices
运维人员平时直接操作 /sys 的频率可能不算高,但设备管理工具、udev 和性能调优经常依赖它。
/boot
/boot 保存内核、initramfs、引导程序配置等启动文件。
/boot/vmlinuz-*/boot/initramfs-*/boot/grub2/
这个目录别拿来随便清。
尤其是安装多个内核版本后,/boot 空间可能被占满。正确做法是通过包管理器卸载不需要的旧内核,而不是看谁文件名长就删谁。
/run
/run 保存本次开机期间的运行时数据。
/run/nginx.pid/run/lock/run/user
它通常使用 tmpfs,重启后内容消失。
PID 文件、套接字、锁文件很适合放在这里,因为它们只对当前运行周期有效。
核心目录可以压缩成一张速查图。
/├── bin 基础命令,现代系统多指向 /usr/bin├── sbin 管理命令,现代系统多指向 /usr/sbin├── etc 系统配置├── dev 设备文件├── home 普通用户家目录├── root root 用户家目录├── var 日志、缓存、状态和变化数据├── usr 应用程序、库和共享资源├── tmp 临时文件├── proc 进程与内核信息├── sys 设备与内核对象├── boot 内核与启动文件└── run 本次启动的运行时数据
三、在树中定位 —— 路径的艺术
3.1 绝对路径与相对路径
绝对路径从根目录 / 开始。
/etc/nginx/nginx.conf/home/dev/project/app.py/var/log/messages
只要文件系统结构不变,无论当前位于哪个目录,绝对路径都指向同一个位置。
相对路径从当前工作目录开始。
假设当前位于 /home/dev/project。
pwd
输出如下。
/home/dev/project
此时访问 config/app.yaml,完整位置就是:
/home/dev/project/config/app.yaml
两种路径可以这样对比。
当前目录/home/dev/project绝对路径/home/dev/project/config/app.yaml相对路径config/app.yaml
绝对路径更稳定,适合脚本、配置文件和系统服务。
相对路径更短,适合交互式操作以及项目内部引用。
我写部署脚本时,重要路径通常会转成绝对路径。原因很现实,脚本经常不是从你以为的目录启动。
你手工运行时没问题,放到 cron 里就报错,这种事太常见了。
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"CONFIG_FILE="$SCRIPT_DIR/config/app.yaml"
这段代码先获取脚本所在目录,再拼出配置文件位置。脚本从哪里启动都不怕。
3.2 特殊目录符号
. 表示当前目录。
./start.sh
这里不能直接写 start.sh,通常是因为当前目录默认不在 PATH 中。加上 ./ 明确告诉 Shell,从当前目录运行。
.. 表示上级目录。
cd ..
多个 .. 可以连续使用。
cd ../../
~ 表示当前用户的家目录。
cd ~cd ~/projects
也可以引用其他用户的家目录。
cd ~alice
前提是系统能够解析该用户。
- 在 cd 命令中表示上一个工作目录。
cd /var/logcd /etc/nginxcd -
执行后会回到 /var/log。
我平时在两个目录之间来回切换,cd - 用得非常多。比重新敲完整路径舒服。
路径导航可以画成这样。
/home/dev/project/src │ ├── . 当前目录 ├── .. /home/dev/project ├── ~ /home/dev └── cd - 上一个工作目录
文件名带空格时,需要引号或转义。
cat "my file.txt"
也可以这样写。
cat my\ file.txt
文件名以 - 开头时更麻烦,因为命令可能把它当成选项。
假设文件叫 -danger.txt。
rm -danger.txt
这条命令可能报错,因为 rm 会尝试解析选项。
正确方式有两种。
rm -- -danger.txt
或者明确使用相对路径。
rm ./-danger.txt
-- 表示后面的内容不再解析为命令选项。
这个小技巧很实用,尤其是处理别人故意构造的奇怪文件名时。
四、文件的身份证 —— inode 与链接
4.1 inode 才是文件的真正身份
大多数人下意识认为,文件名就是文件。
在 Linux 文件系统里,文件名更像一个入口。文件真正的元数据由 inode 保存。
目录中保存的是文件名与 inode 编号的映射。
目录项┌───────────────┬──────────────┐│ 文件名 │ inode 编号 │├───────────────┼──────────────┤│ report.txt │ 105278 ││ app.log │ 105311 │└───────────────┴──────────────┘
inode 通常记录这些信息。
inode 一般不保存文件名。
文件名保存在目录项中。
文件内容保存在数据块中。
文件名 report.txt │ ▼目录项inode 105278 │ ▼inode 元数据权限、大小、时间、数据块指针 │ ▼数据块真正的文件内容
查看 inode 编号可以使用 ls -i。
ls -i report.txt105278 report.txt
查看详细状态可以使用 stat。
stat report.txt
输出中会包含 inode 编号、权限、链接数和时间信息。
File: report.txtSize: 2048Inode: 105278Links: 1Access: (0644/-rw-r--r--)Uid: (1000/dev)Gid: (1000/dev)
不同文件系统对 inode 的实现并不完全相同,但文件名、inode 与数据块这种理解方式非常适合日常排错。
4.2 硬链接
硬链接就是多个目录项指向同一个 inode。
创建硬链接可以使用 ln。
echo "hello" > source.txtln source.txt backup.txt
查看 inode。
ls -li source.txt backup.txt105278 -rw-r--r-- 2 dev dev 6 Jul 20 11:00 backup.txt105278 -rw-r--r-- 2 dev dev 6 Jul 20 11:00 source.txt
两个文件名的 inode 编号相同。
权限后面的数字从 1 变成了 2,这就是链接计数。
source.txt ──┐ ├── inode 105278 ── 数据块backup.txt ──┘
修改任意一个文件,另一个看到的内容也会变化。
echo "linux" >> backup.txtcat source.txt
因为它们本来就是同一个文件,只是名字不同。
硬链接有几个典型限制。
它不能跨文件系统,因为 inode 编号只在当前文件系统内部有意义。
它通常不能由普通用户链接目录。文件系统内部的 . 和 .. 本质上涉及目录链接,但系统不会允许用户随便制造目录环路。
它不会因为原文件名被删除而失效,因为不存在真正意义上的“原文件”。所有硬链接地位平等。
rm source.txtcat backup.txt
backup.txt 仍然可以正常读取。
这里就涉及链接计数。
创建一个硬链接,链接计数加一。
删除一个文件名,链接计数减一。
只有链接计数归零,并且没有进程继续打开该文件时,inode 和数据块才会真正释放。
4.3 符号链接
符号链接是一个独立文件,内部保存目标路径。
ln -s source.txt shortcut.txt
查看结果。
ls -li source.txt shortcut.txt105278 -rw-r--r-- 1 dev dev 6 Jul 20 11:00 source.txt105399 lrwxrwxrwx 1 dev dev 10 Jul 20 11:01 shortcut.txt -> source.txt
两个 inode 不同。
shortcut.txt │ │ 内容为 source.txt ▼source.txt │ ▼inode 105278 │ ▼数据块
符号链接可以跨文件系统。
ln -s /data/archive/report.txt ~/report-link
符号链接也可以指向目录。
ln -s /opt/nginx-1.28.0 /opt/nginx
原目标删除后,符号链接会变成失效链接。
rm source.txtcat shortcut.txt
此时可能得到下面的错误。
No such file or directory
符号链接保存的路径可以是绝对路径,也可以是相对路径。
相对链接的解析基准不是你当前所在目录,而是符号链接本身所在目录。
这个地方很绕。
ln -s ../releases/v2 app/current
../releases/v2 会相对于 app/current 所在的 app 目录解析。
4.4 硬链接与软链接的区别
┌──────────────────┬────────────────────┬──────────────────────┐│ 对比项目 │ 硬链接 │ 符号链接 │├──────────────────┼────────────────────┼──────────────────────┤│ inode │ 与目标相同 │ 拥有独立 inode ││ 保存内容 │ 直接指向同一文件 │ 保存目标路径 ││ 跨文件系统 │ 不支持 │ 支持 ││ 链接目录 │ 通常不支持 │ 支持 ││ 删除原文件名 │ 不受影响 │ 链接失效 ││ 修改内容 │ 所有链接共同变化 │ 实际修改目标 ││ 文件大小 │ 与目标一致 │ 通常是路径字符串长度 ││ 常见用途 │ 同文件多入口 │ 快捷入口、版本切换 │└──────────────────┴────────────────────┴──────────────────────┘
硬链接不是复制。
软链接也不是复制。
它们解决的是路径引用问题,不是数据备份问题。
4.5 文件删除的底层真相
执行 rm 时,Linux 通常做的不是“把文件内容立即擦除”。
它会调用类似 unlink 的操作,删除目录项,并减少 inode 的链接计数。
执行 rm report.log │ ▼删除目录项 report.log │ ▼inode 链接计数减一 │ ├── 仍大于 0 │ 文件继续存在 │ └── 等于 0 │ ▼检查是否仍被进程打开 │ ┌──────┴──────┐ │ │ 没有 仍然打开 │ │释放 inode 和数据 等进程关闭后再释放
这就是为什么文件被删后,有时磁盘空间不会释放。
生产环境里最常见的情况是日志文件被删除,但服务进程仍然保持文件描述符。
例如,一个 Java 服务持续写入 /var/log/app.log。
管理员发现日志太大,直接执行:
rm /var/log/app.log
目录中看不到文件了。
可 Java 进程仍然握着原来的文件描述符,继续向已经被删除的 inode 写数据。文件名没了,数据还在增长。
查看这类文件可以使用:
lsof | grep deleted
更直接一点:
lsof +L1
输出可能类似这样。
java 2318 app 12w REG 253,0 21474836480 0 105311 /var/log/app.log (deleted)
这里说明 Java 进程 PID 为 2318,仍然打开一个已删除文件,占用大约 20GB。
解决方式通常不是继续 rm,因为名字已经没了。
可以让服务重新打开日志文件。
systemctl reload app
不支持 reload 时,可能需要重启。
systemctl restart app
某些紧急场景下,可以通过进程文件描述符清空内容。
: > /proc/2318/fd/12
这招风险不小,必须确认 PID 和文件描述符,别手抖。
更规范的做法是让 logrotate 配合服务重载,或者使用 copytruncate,不过 copytruncate 在高并发写日志时可能存在短暂数据丢失窗口。
文件删除并不代表数据立即从磁盘上物理擦掉,这也是数据恢复工具有时能够恢复误删文件的原因。不过磁盘块一旦被重新使用,恢复概率就会快速下降。
误删后最重要的动作不是疯狂试命令。
是停止写入那个文件系统。
五、文件的通行证 —— 权限管理
5.1 读、写、执行三种权限
执行 ls -l 会看到这样的权限字符串。
-rwxr-xr--
拆开以后是这样。
- rwx r-x r--│ │ │ ││ │ │ └── 其他用户 o│ │ └───────── 所属组 g│ └──────────────── 所有者 u└────────────────────── 文件类型
三种基础权限分别对应数字。
r 读权限 4w 写权限 2x 执行权限 1
权限数字通过相加得到。
rwx = 4 + 2 + 1 = 7rw- = 4 + 2 = 6r-x = 4 + 1 = 5r-- = 4 = 4
755 可以展开为:
所有者 7 rwx所属组 5 r-x其他人 5 r-x
对应权限字符串:
rwxr-xr-x
普通文件上的 r 允许读取内容。
w 允许修改内容。
x 允许把文件作为程序或脚本执行。
脚本有执行权限还不够,通常还需要正确的解释器声明。
#!/usr/bin/env bash
赋予执行权限。
chmod +x deploy.sh
运行脚本。
./deploy.sh
5.2 目录权限为什么不一样
目录权限是 Linux 新手最容易误判的地方。
目录的 r 允许读取目录项,也就是看到里面有哪些文件名。
目录的 x 允许穿过目录,访问其中的文件或子目录。
目录的 w 允许修改目录项,也就是创建、删除和重命名文件,但通常需要配合 x。
假设目录权限只有读,没有执行。
chmod 400 demo
你可能还能看到文件名。
ls demo
但无法正常获取文件元数据。
ls -l demo
输出可能出现大量问号。
-????????? ? ? ? ? ? file.txt
原因很有意思。
ls 读取目录本身时,拿到了文件名。
但它想进一步对 file.txt 执行 stat,需要穿过目录。目录没有 x 权限,所以失败。
目录权限可以这样理解。
目录 r能看目录里的名字目录 x能沿路径进入并访问对象目录 w + x能创建、删除、重命名目录项
还有一个反直觉点。
能不能删除文件,主要看父目录权限,不完全看文件本身的写权限。
假设某个文件是只读的。
-r--r--r-- file.txt
如果你对父目录拥有 w 和 x,通常仍然可以删除这个文件,因为删除操作修改的是父目录中的目录项。
反过来,即使文件本身可写,如果父目录不允许写,你可以修改文件内容,却不能删除或改名。
修改文件内容看文件本身的 w删除文件名看父目录的 w 和 x
这点很重要。
我早期排权限问题时,经常盯着文件权限看半天,最后才发现是上级目录缺了执行权限。那感觉像拿着钥匙站在房门前,结果小区大门不让进。
可以用 namei -l 逐级检查路径权限。
namei -l /srv/web/config/app.yaml
它会把路径中的每一级目录权限都列出来。
5.3 修改权限与所有者
数字法适合一次性设置完整权限。
chmod 755 script.shchmod 640 app.confchmod 700 ~/.ssh
递归修改目录时要谨慎。
chmod -R 755 project
这条命令会把普通文件也加上执行权限,经常不符合预期。
更合理的做法是分别处理目录和文件。
find project -type d -exec chmod 755 {} \;find project -type f -exec chmod 644 {} \;
符号法适合局部修改。
chmod u+x script.shchmod g+w shared.txtchmod o-r secret.txtchmod a-x file.txt
修改所有者使用 chown。
chown alice report.txt
同时修改用户和组。
chown alice:developers report.txt
递归修改。
chown -R app:app /srv/myapp
修改所属组也可以单独使用 chgrp。
chgrp developers report.txt
命令速查如下。
chmod 755 file 设置完整权限chmod u+x file 给所有者增加执行权限chmod g-w file 去掉组写权限chown user file 修改所有者chown user:group file 修改所有者和组chgrp group file 修改所属组
不要把 chmod 777 当万能药。
它确实能让很多 Permission denied 暂时消失,也会让所有用户拥有读、写、执行权限。服务器上这么干,相当于门锁坏了以后,决定把门拆了。
问题是解决了,房子也开放参观了。
5.4 默认权限与 umask
新建文件为什么通常是 644,新建目录为什么通常是 755?
这和默认基础权限以及 umask 有关。
普通文件的基础权限通常从 666 开始。
rw-rw-rw-
文件默认不带执行权限。Linux 不希望你创建一个普通文本文件,它就自动变成可执行程序。
目录的基础权限通常从 777 开始。
rwxrwxrwx
目录需要执行权限才能正常进入和访问。
umask 会把指定权限位掩掉。
假设 umask 是 022。
目录结果通常是:
777022---755
文件结果通常是:
666022---644
不过这里说“相减”只是便于理解。
更准确的说法是按位清除权限,而不是普通十进制减法。遇到权限位重叠和特殊情况时,死背减法容易算错。
基础权限rwx rwx rwxumask 022--- -w- -w-最终权限rwx r-x r-x
查看当前 umask。
umask
设置当前 Shell 的 umask。
umask 027
常见值有这些。
022目录 755文件 644适合大多数普通系统环境002目录 775文件 664适合共享用户组协作077目录 700文件 600适合私密文件和高安全环境
umask 只影响新创建的文件,不会自动修改已有文件。
Shell 中临时修改只对当前 Shell 和它启动的子进程有效。想长期生效,可以根据系统环境放到 /etc/profile、用户 Shell 配置或 systemd 服务配置中。
systemd 服务可以单独配置。
[Service]UMask=0027
团队项目目录里,经常会把 SGID、用户组和 umask 组合起来。
chgrp -R developers /srv/projectchmod 2775 /srv/projectumask 002
这样新文件更容易继承协作组。
5.5 特殊权限与 ACL
SUID 出现在可执行文件的所有者执行位上。
-rwsr-xr-x
它让程序运行时获得文件所有者的有效身份。
典型例子是 passwd。
ls -l /usr/bin/passwd
普通用户需要修改自己的密码,但密码数据库由 root 管理。passwd 通过 SUID 在受控逻辑下完成操作。
SUID 风险很高。
任何带 SUID 的程序如果存在漏洞,都可能成为提权入口。
查找 SUID 文件可以使用:
find / -perm -4000 -type f 2>/dev/null
SGID 用在文件上时,程序运行后获得文件所属组身份。
SGID 用在目录上时,新建文件通常继承目录的所属组,而不是创建者的默认组。
chmod 2775 /srv/shared
权限开头的 2 表示 SGID。
Sticky Bit 常用于共享目录。
chmod 1777 /tmp/demo
所有用户都能创建文件,但通常只有文件所有者、目录所有者或 root 能删除文件。
ACL 可以给特定用户或组单独授权。
查看 ACL。
getfacl report.txt
给用户 alice 增加读写权限。
setfacl -m u:alice:rw report.txt
给目录设置默认 ACL,让新文件继承权限。
setfacl -m d:u:alice:rwx shared
ACL 中还有一个 mask 概念,它会限制命名用户、命名组和所属组的最大有效权限。
有时你明明给用户设置了 rwx,实际却只有 r-x,别急着怀疑人生,先看 ACL mask。
getfacl shared
六、动手实践 —— 文件与目录常用命令
6.1 浏览与导航
列出目录内容使用 ls。
lsls -lls -als -hls -i
组合使用更常见。
ls -lah
-l 使用长格式。
-a 显示隐藏文件。
-h 把文件大小显示成人类容易阅读的单位。
-i 显示 inode 编号。
按修改时间排序。
ls -lt
文件大的排前面。
ls -lhS
切换目录使用 cd。
cd /etccd ..cd ~cd -
查看当前目录使用 pwd。
pwd
树状展示目录可以使用 tree。
treetree -L 2tree -a
有些精简系统没安装 tree,可以通过包管理器安装。
sudo apt install tree
或者:
sudo dnf install tree
6.2 创建与删除
创建目录使用 mkdir。
mkdir project
创建多级目录使用 -p。
mkdir -p project/{src,config,logs,data,tmp}
这一条命令会创建多组目录。
project├── config├── data├── logs├── src└── tmp
创建空文件或更新时间戳使用 touch。
touch app.log
删除文件使用 rm。
rm app.log
递归删除目录。
rm -r project
强制删除且不提示。
rm -rf project
rm -rf 本身没有错,错的是没看路径就执行。
我现在执行危险删除前,通常会先做一次确认。
printf '%s\n' "$TARGET_DIR"ls -ld "$TARGET_DIR"
脚本里还会检查变量是否为空。
if [ -z "$TARGET_DIR" ] || [ "$TARGET_DIR" = "/" ]; then echo "拒绝删除危险路径" exit 1firm -rf -- "$TARGET_DIR"
删除空目录可以使用 rmdir。
rmdir empty-dir
目录非空时,它会拒绝执行。某些时候这种“胆小”反而比 rm -rf 更让人安心。
6.3 复制、移动与重命名
复制文件使用 cp。
cp source.txt target.txt
复制目录使用递归选项。
cp -r project project-backup
保留时间戳、权限等属性可以使用 -p。
cp -p app.conf app.conf.bak
生产迁移更常用归档模式。
cp -a source/ target/
-a 会尽量保留符号链接、权限、所有者和时间戳。
处理大量文件时,我更喜欢 rsync。
rsync -aHAX source/ target/
H 保留硬链接。
A 保留 ACL。
X 保留扩展属性。
移动和重命名都使用 mv。
mv old.txt new.txt
移动到其他目录。
mv app.log archive/
同一文件系统内移动文件通常只需要修改目录项,速度很快。
跨文件系统移动时,实际过程接近复制后删除,速度取决于数据量。
6.4 查看文件内容
cat 适合查看较短文件。
cat app.conf
它会一次性输出全部内容。拿它看几百 MB 的日志,终端会像瀑布一样往下冲,人基本看不见啥。
分页查看使用 less。
less app.log
常用操作如下。
空格 下一页b 上一页/关键字 向下搜索n 下一个匹配N 上一个匹配G 跳到末尾g 跳到开头q 退出
more 也能分页,但交互能力通常不如 less。
查看开头使用 head。
head app.loghead -n 20 app.log
默认显示前 10 行。
查看结尾使用 tail。
tail app.logtail -n 100 app.log
实时追踪日志。
tail -f app.log
日志轮转后想继续跟踪同名文件,可以考虑:
tail -F app.log
-F 会在文件被替换后尝试重新打开。
不同场景可以这么选。
短配置文件 cat大文件分页阅读 less看开头格式 head看最新日志 tail实时追踪日志 tail -f 或 tail -F
6.5 查找文件与目录
find 会实时遍历目录树。
按名称查找。
find /var/log -name "*.log"
忽略大小写。
find /var/log -iname "*.LOG"
按类型查找。
find /srv -type ffind /srv -type dfind /srv -type l
按大小查找。
find /var -type f -size +1Gfind /tmp -type f -size -10k
按修改时间查找。
find /var/log -type f -mtime -1
这表示查找修改时间在过去一个 24 小时区间内的文件,但 -mtime 按完整的 24 小时周期取整。需要更细时,可以使用分钟。
find /var/log -type f -mmin -60
按访问时间查找。
find /data -type f -amin -30
按权限查找。
find /srv -type f -perm 0777
查找任何人都可写的文件。
find /srv -type f -perm -0002
按所有者查找。
find /home -user alice
结合 -exec 执行命令。
find /tmp -type f -mtime +7 -exec rm -f {} \;
{} 表示当前查找到的对象。
\; 表示每个文件单独执行一次命令。
批量执行可以使用 +。
find /var/log -name "*.log" -exec gzip {} +
涉及删除时,先把 rm 换成 echo 看结果。
find /tmp -type f -mtime +7 -exec echo rm -f {} \;
确认无误再真正执行。这个习惯救过我不止一次。
locate 基于预先建立的文件名数据库,速度很快。
locate nginx.conf
但数据库可能不是实时更新。
sudo updatedb
find 与 locate 可以这样理解。
find实时扫描结果新速度受目录规模影响支持权限、大小、时间等复杂条件locate查询数据库速度很快结果可能滞后主要按路径和名称查找
查找命令位置可以使用 which。
which python
但 which 主要查找 PATH 中的可执行文件。
更了解 Shell 解析结果时,我更喜欢 type。
type cdtype lstype python
它能告诉你目标是别名、Shell 内建命令、函数还是外部程序。
cd is a shell builtinls is aliased to `ls --color=auto'python is /usr/bin/python
whereis 会尝试查找程序二进制、源码和手册位置。
whereis nginx
在文件内容中搜索使用 grep。
grep "ERROR" app.log
显示行号。
grep -n "ERROR" app.log
忽略大小写。
grep -i "error" app.log
递归搜索。
grep -rn "database_url" config/
排除某些目录。
grep -rn "TODO" . --exclude-dir=.git
搜索固定字符串,避免正则字符产生影响。
grep -F "a.b.c" file.txt
6.6 链接与查看
创建硬链接。
ln source.txt hard-link.txt
创建符号链接。
ln -s source.txt soft-link.txt
强制更新符号链接时,常见写法如下。
ln -sfn /opt/app-2.0 /opt/app
查看文件类型。
file app
查看完整状态。
stat app
只输出指定字段。
stat -c '%n %i %s %a %U %G' app
输出可能如下。
app 105278 4096 755 dev developers
七、文件的时间属性
7.1 三种时间戳与出生时间
Linux 文件常见的时间属性包括 atime、mtime 和 ctime。
部分文件系统还支持 birth time,也就是创建时间。
atime 表示文件内容最后一次被读取的时间。
cat report.txt
理论上,这次读取可能更新 atime。
不过现代 Linux 为了减少磁盘写入,经常使用 relatime、noatime 等挂载策略,所以 atime 不一定每次读取都立刻更新。
查看挂载参数。
findmnt -no OPTIONS /
mtime 表示文件内容最后一次被修改的时间。
echo "new line" >> report.txt
这会改变 mtime。
ctime 表示 inode 状态最后一次变化的时间。
修改权限、所有者、链接计数或文件内容,都可能更新 ctime。
chmod 600 report.txt
这会更新 ctime,但不会因为权限变化而更新 mtime。
ctime 不是创建时间。
这个命名确实很容易误导。我刚入行时一直把它理解成 creation time,后来排增量同步问题才发现自己理解错了,挺尴尬。
ctime 中的 c 更接近 change。
POSIX 长期定义的是文件状态变化时间,而不是文件创建时间。早期 Unix 文件系统并没有统一保存创建时间,这个历史包袱一直留到了今天。
birth time 或 crtime 才表示创建时间,但不是所有文件系统、内核和工具组合都支持。
atime最后读取内容的时间mtime最后修改内容的时间ctime最后修改 inode 状态的时间birth time文件被创建的时间,部分文件系统支持
7.2 使用 stat 查看时间信息
stat report.txt
可能看到这样的输出。
Access: 2026-07-20 15:12:10.000000000 +0800Modify: 2026-07-20 14:30:00.000000000 +0800Change: 2026-07-20 14:35:21.000000000 +0800 Birth: 2026-07-18 09:10:11.000000000 +0800
Access 对应 atime。
Modify 对应 mtime。
Change 对应 ctime。
Birth 对应创建时间。
如果显示 -,通常说明当前文件系统或工具无法提供出生时间。
ls -l 默认显示 mtime。
ls -l report.txt
查看 atime 可以使用:
ls -lu report.txt
查看 ctime 可以使用:
ls -lc report.txt
需要精确分析时,还是 stat 更清楚。
7.3 使用 touch 修改时间戳
不带选项执行 touch,会把 atime 和 mtime 更新为当前时间。
touch report.txt
只修改 atime。
touch -a report.txt
只修改 mtime。
touch -m report.txt
指定时间。
touch -t 202607201530 report.txt
格式大致为:
YYYYMMDDhhmm
参考另一个文件的时间戳。
touch -r template.txt report.txt
需要知道的是,使用 touch 修改 atime 或 mtime 时,文件状态发生变化,ctime 通常也会被系统更新。ctime 不能像 mtime 一样被普通用户任意设成某个历史值。
7.4 时间戳的实际用途
增量备份工具经常使用 mtime 和文件大小判断文件是否变化。
rsync -av source/ backup/
默认快速检查通常会参考文件大小和修改时间。
如果必须逐字节确认内容,可以使用校验和模式。
rsync -avc source/ backup/
不过 -c 会读取所有文件计算校验和,数据量大时很慢。
用 find 查找最近一天修改的文件。
find /srv/project -type f -mtime -1
查找最近 30 分钟变化的日志。
find /var/log -type f -mmin -30
查找 30 天前修改的备份。
find /backup -type f -mtime +30
日志轮转通常会综合配置周期、文件大小、状态文件和服务行为判断,而不是只盯着某一个文件的 mtime。
典型配置可能是:
/var/log/myapp/*.log { daily rotate 14 compress missingok notifempty postrotate systemctl reload myapp endscript}
这里表示按日轮转,保留 14 份,并在轮转后通知服务重新打开日志文件。
八、综合实战 —— 从零搭建项目与常见错误排查
8.1 搭建 Web 项目目录
假设我们要部署一个名为 myweb 的 Web 服务。
项目包含程序代码、配置、日志、持久数据、发布版本和临时文件。
我不太喜欢把所有东西塞进一个目录,然后靠文件名猜用途。测试环境勉强能忍,生产环境迟早会翻车。
可以设计成下面这样。
/srv/myweb├── current -> releases/20260731├── releases│ ├── 20260720│ └── 20260731├── shared│ ├── config│ ├── data│ ├── logs│ └── uploads└── tmp
releases 保存每次发布版本。
current 指向当前版本。
shared 保存跨版本共享的数据。
tmp 保存项目级临时文件。
创建目录。
sudo mkdir -p /srv/myweb/{releases,shared/{config,data,logs,uploads},tmp}
创建一个发布版本。
sudo mkdir -p /srv/myweb/releases/20260731
复制程序文件。
sudo rsync -a ./dist/ /srv/myweb/releases/20260731/
建立共享目录链接。
sudo ln -s /srv/myweb/shared/config \ /srv/myweb/releases/20260731/configsudo ln -s /srv/myweb/shared/uploads \ /srv/myweb/releases/20260731/uploads
切换当前版本。
sudo ln -sfn /srv/myweb/releases/20260731 /srv/myweb/current
这样发布新版本时,只需要创建新目录,再原子更新 current 链接。
回滚也很快。
sudo ln -sfn /srv/myweb/releases/20260720 /srv/myweb/current
当然,真实部署还要考虑进程重启、数据库迁移、健康检查和缓存兼容。符号链接不是魔法,但它把版本路径管理得很干净。
8.2 权限规划与设置
创建项目用户和用户组。
sudo groupadd mywebsudo useradd -r -g myweb -d /srv/myweb -s /usr/sbin/nologin myweb
让部署用户进入项目组。
sudo usermod -aG myweb deploy
设置目录所有者。
sudo chown -R myweb:myweb /srv/myweb
如果部署用户也需要写入发布目录,可以把组权限放开。
sudo chmod -R g+rwX /srv/myweb
X 只会给目录和原本已有执行权限的文件增加执行权限,比无脑 chmod -R g+rwx 更稳。
让新文件继承项目组。
sudo find /srv/myweb -type d -exec chmod g+s {} \;
设置默认 ACL。
sudo setfacl -R -m g:myweb:rwx /srv/mywebsudo setfacl -R -d -m g:myweb:rwx /srv/myweb
配置文件不应该让所有用户读取。
sudo chmod 640 /srv/myweb/shared/config/app.env
日志目录需要服务写入,但未必需要所有人修改。
sudo chmod 2750 /srv/myweb/shared/logs
上传目录通常需要服务读写。
sudo chmod 2770 /srv/myweb/shared/uploads
团队协作环境可以在部署脚本开头设置:
umask 002
服务进程则可能使用更严格的:
[Service]User=mywebGroup=mywebUMask=0027
8.3 路径导航与软链接
日常排错时,可以快速进入当前版本。
cd /srv/myweb/current
查看真实路径。
readlink -f /srv/myweb/current
输出可能是:
/srv/myweb/releases/20260731
查看路径每一层权限。
namei -l /srv/myweb/current/config/app.env
查看符号链接本身。
ls -l /srv/myweb/current
查看链接指向的目录内容。
ls -l /srv/myweb/current/
注意末尾这个 /。
没有斜杠时,ls 更偏向显示链接本身。
加上斜杠后,Shell 和程序会按目录目标处理。
版本切换的结构可以画成这样。
/srv/myweb/current │ └────────────> releases/20260731 │ ├── app ├── public ├── config ───> shared/config └── uploads ──> shared/uploads
8.4 常见错误排查
Permission denied
常见原因包括文件权限不足、父目录缺执行权限、文件系统只读、ACL 限制或 SELinux 拒绝。
先看基础权限。
ls -l file
再看完整路径。
namei -l /path/to/file
查看 ACL。
getfacl /path/to/file
查看挂载是否只读。
findmnt -no OPTIONS /path
在启用 SELinux 的系统中,还要看审计日志。
ausearch -m AVC -ts recent
别一上来就 chmod 777。那叫绕过排查,不叫解决问题。
No such file or directory
路径写错是最常见原因。
ls -l /path/to/file
符号链接失效也会出现这个错误。
readlink link-namereadlink -f link-name
脚本明明存在却报这个错,还有一种可能是解释器路径不存在。
head -n 1 script.sh
如果第一行是:
#!/bin/bash
就检查 /bin/bash 是否存在。
Windows 换行符也可能捣乱。
file script.sh
看到 CRLF 时,可以转换。
dos2unix script.sh
No space left on device
先看文件系统容量。
df -h
定位大目录。
du -xhd1 /var | sort -h
继续向下查。
du -xhd1 /var/log | sort -h
du 和 df 结果差异很大时,要怀疑已删除但仍被进程占用的文件。
lsof +L1
df 显示有空间却仍然提示空间不足
可能是 inode 耗尽。
df -i
如果 inode 使用率达到 100%,即使还有很多 GB 空间,也不能创建新文件。
查找小文件数量特别多的目录。
for d in /*; do printf '%s ' "$d" find "$d" -xdev 2>/dev/null | wc -ldone | sort -k2 -n
缓存目录、Session 目录、邮件队列和某些程序生成的小碎文件,都是 inode 杀手。
文件删除后空间未释放
lsof +L1
找到对应进程后,让程序重新打开文件或重启服务。
systemctl restart service-name
不要在没确认的情况下直接杀进程。生产环境里,一个重启可能引发流量抖动、主从切换或任务重复执行。
Device or resource busy
卸载文件系统时看到这个错误,通常说明有进程正在使用挂载点。
lsof +D /mnt/data
目录很大时,lsof +D 可能较慢。
可以使用:
fuser -mv /mnt/data
还要检查当前 Shell 是否正位于该目录。
pwd
切出去再卸载。
cd /sudo umount /mnt/data
懒卸载可以使用:
sudo umount -l /mnt/data
-l 会先从目录树中分离,等引用释放后再完成清理。它适合某些恢复场景,不应该被当成所有 busy 问题的万能按钮。
Operation not permitted
权限看着没问题,root 也改不了文件,有可能设置了不可变属性。
lsattr file
如果看到 i:
----i-------------- file
去掉不可变属性。
sudo chattr -i file
然后再修改或删除。
错误排查表可以压缩成这样。
| | | |
|---|
| | ls -l | |
| No such file or directory | | ls -l | |
| | df -h | |
| | df -i | |
| | lsof +L1 | |
| | fuser -mv | |
| | lsattr | chattr -i |
8.5 日志目录迁移实战
假设 /var 分区快满了,新磁盘挂载在 /data,需要把 /var/log 迁移到新磁盘。
这类操作不要直接在线上边写边搬。
日志服务还在写,复制结果会不断变化。部分程序还会保持原文件描述符,操作顺序不对,很容易出现日志丢失或者服务启动失败。
先确认新磁盘。
lsblk -fdf -hT /data
创建目标目录。
sudo mkdir -p /data/var-log
可以先进行一次在线预同步。
sudo rsync -aHAX /var/log/ /data/var-log/
正式切换时,进入维护窗口,停止主要日志写入服务。
不同系统服务名称可能不同。
sudo systemctl stop rsyslogsudo systemctl stop nginxsudo systemctl stop myapp
再做一次最终同步。
sudo rsync -aHAX --delete /var/log/ /data/var-log/
用户要求使用 cp -rp 时,也可以这样:
sudo cp -rp /var/log/. /data/var-log/
不过 cp -rp 未必完整保留 ACL、扩展属性和 SELinux 上下文。生产迁移我更倾向 rsync -aHAX。
保留原目录作为回滚。
sudo mv /var/log /var/log.old
建立符号链接。
sudo ln -s /data/var-log /var/log
检查。
ls -ld /var/logreadlink -f /var/log
启动服务。
sudo systemctl start rsyslogsudo systemctl start nginxsudo systemctl start myapp
观察日志是否正常产生。
sudo tail -F /var/log/messages
Ubuntu 或 Debian 系统可能查看:
sudo tail -F /var/log/syslog
新磁盘还需要开机自动挂载。
查 UUID。
blkid /dev/sdb1
编辑 /etc/fstab。
UUID=a932xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /data ext4 defaults,nofail 0 2
测试配置。
sudo mount -a
检查挂载。
findmnt /data
这里有个坑。
如果 /data 开机挂载失败,而 /var/log 是指向 /data/var-log 的符号链接,日志服务可能无法启动,或者程序在系统盘的空目录里写入意外内容。
nofail 也不代表业务一定安全,它只是避免某些挂载失败直接阻塞启动。
更稳的生产方案通常是把新文件系统直接挂载到 /var/log,或者使用 bind mount,而不是长期依赖符号链接。
直接挂载方案可以在复制完成后配置:
UUID=a932xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /var/log ext4 defaults 0 2
bind mount 方案可以写成:
/data/var-log /var/log none bind 0 0
systemd 服务还可以增加挂载依赖,确保日志目录就绪后再启动。
[Unit]RequiresMountsFor=/var/log
回滚也要提前想好。
停止相关服务。
删除符号链接。
恢复原目录。
sudo rm /var/logsudo mv /var/log.old /var/log
再启动服务。
8.6 链接的妙用与误区
软链接很适合做版本切换。
/opt/nginx -> /opt/nginx-1.28.0
升级新版本。
ln -sfn /opt/nginx-1.29.0 /opt/nginx
程序和脚本始终访问 /opt/nginx,不需要到处修改路径。
软链接也适合提供稳定入口。
/srv/app/current/data/latest/usr/local/bin/tool
硬链接看起来像“备份”,其实不是。
ln report.txt report-backup.txt
修改 report.txt,report-backup.txt 会一起变化。
清空其中任何一个,另一个也会变空。
: > report-backup.txtcat report.txt
内容同样没了。
硬链接只能防止某一个文件名被误删,不能防止内容被修改、文件系统损坏、磁盘故障和勒索软件。
真正的备份至少要有独立数据副本。
cp -a source backup
更适合持续同步的方式:
rsync -aHAX --delete source/ backup/
代码和配置还可以进入版本控制。
git add .git commit -m "save configuration"
重要数据最好满足类似 3-2-1 的思路。
至少三份数据。
使用两种不同介质或存储形式。
至少一份放在异地或独立故障域。
硬链接真的夯爆了,但它夯的是文件名管理,不是灾难恢复。这个边界要分清。
附录 A 常用命令速查表
| |
|---|
| pwd |
| ls -lah |
| ls -li |
| cd /path |
| cd .. |
| cd - |
| mkdir dir |
| mkdir -p a/b/c |
| touch file |
| rm file |
| rm -r dir |
| rmdir dir |
| cp src dst |
| cp -a src dst |
| mv old new |
| cat file |
| less file |
| head -n 20 file |
| tail -n 100 file |
| tail -F file |
| find /path -name "*.log" |
| locate filename |
| grep -rn "text" /path |
| type command |
| which command |
| file filename |
| stat filename |
| ln source link |
| ln -s target link |
附录 B FHS 核心目录清单
| |
|---|
/ | |
/bin | |
/sbin | 系统管理命令,现代系统常链接到 /usr/sbin |
/boot | |
/dev | |
/etc | |
/home | |
/root | |
/lib | |
/media | |
/mnt | |
/opt | |
/proc | |
/run | |
/srv | |
/sys | |
/tmp | |
/usr | |
/usr/local | |
/var | |
/var/lib | |
/var/log | |
/var/cache | |
/var/tmp | |
附录 C 权限数字与 umask 示例
基础权限数字如下。
r = 4w = 2x = 1
常见组合如下。
7 = rwx6 = rw-5 = r-x4 = r--3 = -wx2 = -w-1 = --x0 = ---
常见权限如下。
600 rw------- 私密文件640 rw-r----- 服务配置644 rw-r--r-- 普通文本文件700 rwx------ 私密目录或脚本750 rwxr-x--- 服务目录755 rwxr-xr-x 普通目录和公开程序775 rwxrwxr-x 组协作目录
umask 为 022。
目录 777 → 755文件 666 → 644
umask 为 002。
目录 777 → 775文件 666 → 664
umask 为 077。
目录 777 → 700文件 666 → 600
特殊权限数字如下。
4 SUID2 SGID1 Sticky Bit
示例:
chmod 4755 programchmod 2775 shared-dirchmod 1777 public-temp
附录 D find 常用组合
按名称:
find /path -name "*.conf"
忽略大小写:
find /path -iname "*.jpg"
只查文件:
find /path -type f
只查目录:
find /path -type d
大于 500MB:
find /path -type f -size +500M
最近一小时修改:
find /path -type f -mmin -60
七天前修改:
find /path -type f -mtime +7
查找空文件:
find /path -type f -empty
查找空目录:
find /path -type d -empty
查找指定用户文件:
find /path -user alice
查找全局可写文件:
find /path -type f -perm -0002
删除七天前的临时文件:
find /tmp/myapp -type f -mtime +7 -delete
执行删除前先预览:
find /tmp/myapp -type f -mtime +7 -print
批量修改权限:
find project -type d -exec chmod 755 {} +find project -type f -exec chmod 644 {} +
附录 E 三种时间戳对比
查看全部时间:
stat filename
查找最近修改文件:
find /path -mmin -30
只修改访问时间:
touch -a filename
只修改内容修改时间:
touch -m filename
附录 F 常见错误速查
| |
|---|
| |
| |
| |
| |
| |
| lsattr |
| df -h |
| df -i |
| lsof +L1 |
| fuser -mv |
| readlink -f |
| |
| |