找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4400

积分

0

好友

576

主题
发表于 1 小时前 | 查看: 4| 回复: 0

我折腾服务器也有些年头了,从物理机、虚拟机再到容器,从 CentOS 6 一路追到 Rocky Linux 和 Ubuntu Server。坦白讲,Linux 文件系统这东西,看着基础,里头藏着的细节却能让运维半夜睡不着觉。

很多人会敲 cdlsrm,却没搞明白 inode 到底是个啥;知道 chmod 777 能“治好”权限问题,却不知道它顺手打开了多大的安全窗口;磁盘空间告警,一把 rm 下去,日志没了,可用空间还是纹丝不动。

这篇文章不聊玄学,只聊文件、目录、路径、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.log
drwxr-xr-x  2 dev dev   4096 Jul 20 10:20 config
lrwxrwxrwx  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 README
file server
file image.png

目录的类型字符是 d。目录本质上也是文件,只不过里面存的不是正文,而是文件名与 inode 编号的对应关系。这个概念后面讲到 inode 的时候还会碰头。

符号链接的类型字符是 l,有点像 Windows 的快捷方式,但本质不同:它是一个独立文件,里面保存着目标路径。

字符设备的类型字符是 c,按字节流处理数据,终端就是典型。

ls -l /dev/tty

块设备的类型字符是 b,像硬盘、分区,可以按数据块随机访问。

ls -l /dev/sda
ls -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 MOUNTPOINTS
nvme0n1     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 -f

完整目录树和挂载关系可以这样理解:

/
├── boot                  ← 单独的启动分区
├── dev                   ← devtmpfs
├── etc
├── home
├── proc                  ← proc 虚拟文件系统
├── run                   ← tmpfs
├── sys                   ← sysfs 虚拟文件系统
├── data                  ← /dev/sdb1
├── usr
└── var

目录看着像普通目录,背后却可能来自完全不同的磁盘,甚至来自网络。df -hT 能查看各挂载点使用的文件系统类型和空间。

2.2 FHS 标准 —— 目录的“规矩”

不同发行版如果各玩各的目录,写脚本的人真的要崩溃。FHS(Filesystem Hierarchy Standard)就是来给这件事立规矩的——它规定了根目录下该放哪些核心目录,/usr/var 内部又该怎么组织。有这套共识,至少不会乱得离谱。

这几年目录结构还有一个明显趋势:/usr 合并。传统结构里,根下有一套基础命令目录(/bin/sbin/lib),/usr 下又有一套(/usr/bin/usr/sbin/usr/lib)。现在很多现代发行版已经把它们统一了。

ls -ld /bin /sbin /lib /lib64

在现代 Ubuntu、Debian、Fedora 等系统中,你看到的很可能是符号链接:/bin -> usr/bin/sbin -> usr/sbin。这样做的好处是程序文件都集中到 /usr,根目录结构更清爽。

传统结构
/
├── bin
├── sbin
├── lib
└── usr
    ├── bin
    ├── sbin
    └── lib

现代结构
/
├── bin  ────────> /usr/bin
├── sbin ────────> /usr/sbin
├── lib  ────────> /usr/lib
└── usr
    ├── bin
    ├── sbin
    └── lib

所以,别死背那些“/bin 只放普通用户命令,/sbin 只放管理员命令”的老教条,先看看你眼前系统的真实结构再说。

2.3 核心目录逐一解读

  • /bin:传统上放基础命令,现代系统多为 /usr/bin 的符号链接。
  • /sbin:传统上放系统管理命令,现在多指向 /usr/sbin
  • /etc系统级配置文件的集中地passwdfstabssh/sshd_confignginx/nginx.conf 等等都在这里。这里该放文本配置,不该扔二进制文件或运行日志。
  • /dev:设备文件的家,/dev/null(黑洞)、/dev/zero/dev/random 等都由内核动态创建。
  • /home:普通用户的家目录,通常这里约定,但不是铁律。
  • /rootroot 用户的家目录,千万注意别和根目录 / 搞混。它单独放在根文件系统里,即便 /home 没挂载,管理员也能登录维护。
  • /var:放经常变化的数据:/var/log(日志)、/var/lib(应用状态)、/var/cache/var/spool 等。生产服务器磁盘爆满,十有六七都能在 /var 找到“凶手”。
  • /usr现代 Linux 用户空间程序和只读共享数据的核心位置。别简单理解成“某个用户的目录”,普通用户自己的东西在家目录。/usr/local 通常留给管理员手工安装的软件,防止被包管理器覆盖。
  • /tmp:临时文件,很多系统重启后会清空。通常开放给所有用户创建,但不能乱删别人文件,这依赖 t 权限(Sticky Bit)。
ls -ld /tmp   # 通常看到 drwxrwxrwt
  • /proc:内核虚拟文件系统,不占磁盘空间,把进程和内核状态以文件形式暴露出来。/proc/cpuinfo/proc/meminfo/proc/1234/ 等都是。很多监控工具的本质就是在读 /proc
  • /sys:同样是虚拟文件系统,主要展示设备、驱动、内核对象等。运维手动操作不多,但 udev 和性能调优都依赖它。
  • /boot:内核、initramfs、grub 等启动文件。别随手清,清理旧内核要老老实实用包管理器。
  • /run:本次启动的运行时数据(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 这种,只要文件系统结构不变,从哪儿访问都指向同一个位置。脚本、配置文件、systemd service 文件里,强烈建议用绝对路径——因为你永远不知道脚本会从哪个目录被启动。

在脚本里可以这样安全地动态生成绝对路径:

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/config/app.yaml"

相对路径从当前工作目录开始,交互操作时很方便,但前提是你得清楚自己此刻站在哪儿。一个 pwd 下去,心里就有数了。

绝对路径更稳,相对路径更快;脚本用前者,手工敲命令用后者。

3.2 特殊目录符号

  • . 当前目录。执行 ./start.sh 就是为了明确告诉 Shell:从当前目录运行。
  • .. 上级目录。cd ..,多个 .. 连用 cd ../../ 可以跳多级。
  • ~ 当前用户的家目录。cd ~cd ~/projects,还能 cd ~alice
  • - 上一个工作目录。cd /var/logcd /etc/nginx 后,一个 cd - 就回到 /var/log,来回切换特别顺手。

文件名里带空格时,要用引号或反斜杠转义。

cat "my file.txt"
cat my\ file.txt

文件名以 - 开头更麻烦,因为命令容易把它当选项。正确姿势是用 -- 或显式写 ./

rm -- -danger.txt
rm ./-danger.txt

四、文件的身份证 —— inode 与链接

4.1 inode 才是文件的真正身份

很多人下意识觉得文件名就等于文件。但在 Linux 文件系统里,文件名更像一个“入口”,文件真正的元数据全登记在 inode 里。目录表里存放的,只是文件名与 inode 编号的映射。

目录项
┌───────────────┬──────────────┐
│ 文件名        │ inode 编号   │
├───────────────┼──────────────┤
│ report.txt    │ 105278       │
│ app.log       │ 105311       │
└───────────────┴──────────────┘

inode 里记录着文件类型、权限、所有者、大小、链接计数、时间戳、数据块位置等信息。文件名不在 inode 里,在目录项里;文件内容在数据块里。

ls -i 看 inode 编号,用 stat 看完整状态。

ls -i report.txt
stat report.txt

4.2 硬链接

硬链接就是多个目录项指向同一个 inode。创建方式是 ln source.txt backup.txt。两个文件的 inode 编号相同,链接计数从 1 变成 2。

source.txt ──┐
             ├── inode 105278 ── 数据块
backup.txt ──┘

修改任意一个文件,另一个立刻同步变化,因为它们就是同一个文件,只不过有多个名字。

硬链接的几个限制:

  • 不能跨文件系统,inode 只在当前文件系统内部有意义。
  • 通常不能链接目录(系统不允许随便制造目录环路)。
  • 所有硬链接完全平等,没有主次之分。

删除操作:每删一个文件名,链接计数减一。只有链接计数归零且没有进程打开文件时,inode 和数据块才会真正释放。

4.3 符号链接

符号链接(软链接)是一个独立的文件,里面保存目标路径。

ln -s source.txt shortcut.txt

两个 inode 不同,shortcut.txt 的内容只是字符串 source.txt符号链接可以跨文件系统,也可以指向目录,非常灵活。但原目标一旦被删除、移动或改名,软链接就会失效,直接报“No such file or directory”。

相对软链接的解析基准不是你的当前目录,而是软链接自身的所在目录。这一点曾让不少人迷糊过。

4.4 硬链接与软链接的区别

对比项目 硬链接 符号链接
inode 与目标相同 拥有独立 inode
保存内容 直接指向同一文件 保存目标路径
跨文件系统 不支持 支持
链接目录 通常不支持 支持
删除原文件名 不受影响 链接失效
修改内容 所有链接共同变化 实际修改目标
文件大小 与目标一致 通常是路径字符串长度
常见用途 同文件多入口 快捷入口、版本切换

硬链接不是备份,软链接也不是备份。它们解决的是路径引用问题,不是数据冗余问题。

4.5 文件删除的底层真相

执行 rm 时,Linux 通常不是立刻把数据擦掉,而是调用类似 unlink 的操作,删掉目录项,再把 inode 的链接计数减一。只有链接计数归零,并且没有进程继续占用文件时,系统才真正释放空间。

执行 rm report.log
        │
        ▼
删除目录项 report.log
        │
        ▼
inode 链接计数减一
        │
        ├── 仍大于 0
        │       文件继续存在
        │
        └── 等于 0
               │
               ▼
检查是否仍被进程打开
               │
        ┌──────┴──────┐
        │             │
      没有           仍然打开
        │             │
释放 inode 和数据   等进程关闭后再释放

这就是为什么经常出现日志文件明明删了,磁盘空间却不回来的原因——服务进程还握着文件描述符,继续往已删除的 inode 里写数据。排查这类问题用:

lsof | grep deleted
lsof +L1

输出类似:

java  2318 app  12w REG  253,0  21474836480  0 105311 /var/log/app.log (deleted)

正确的做法是通过 systemctl reload 或重启让进程重新打开日志文件。谨慎的做法是通过 /proc/PID/fd/ 清空内容,但务必看准 PID 和文件描述符。应对这种场景,更成熟的方案是配合 logrotate 和合理的重载机制。

五、文件的通行证 —— 权限管理

5.1 读、写、执行三种权限

ls -l 输出的权限串可以这样拆开看:

-    rwx    r-x    r--
│     │      │      │
│     │      │      └── 其他用户 o
│     │      └───────── 所属组 g
│     └──────────────── 所有者 u
└────────────────────── 文件类型

三种基础权限对应的数字:r=4, w=2, x=1。常见的 755 就是所有者 rwx,所属组 r-x,其他人 r-x。

  • 普通文件:r 读内容,w 改内容,x 执行程序/脚本。
  • 脚本还需要正确的 shebang,如 #!/usr/bin/env bash

5.2 目录权限为什么不一样

这是最容易踩坑的地方。 目录的 r 允许读取目录项(看到文件名);x 允许“穿过”目录去访问里面的文件;w 允许增删改名,但一般需要配合 x

如果目录只有 r 没有 x,你会看到文件名,但 ls -l 出来全是问号。因为 ls 能读目录项,却无法对文件执行 stat 穿过目录。

能不能删除文件,主要看父目录权限,而不是文件自身的 w 即使文件是只读的,只要你对父目录有 wx,通常就能删。反过来,文件可写,但父目录不给 w,你能改内容但无法删或改名。

排查逐级权限的神器:namei -l /srv/web/config/app.yaml

5.3 修改权限与所有者

数字法一次性设置完整权限,符号法用于局部微调。

chmod 755 script.sh
chmod u+x script.sh

递归改目录务必小心,别一刀切把所有普通文件都加上了执行权限。更合理的做法是分开:

find project -type d -exec chmod 755 {} \;
find project -type f -exec chmod 644 {} \;

修改所有者用 chown,修改所属组用 chgrp

chown alice:developers report.txt
chgrp developers report.txt

chmod 777 当万能药是不可取的。 它能让 Permission denied 暂时消失,也让所有用户拥有了所有权限。服务器上这么干,等于修门锁的时候直接把门拆了。

5.4 默认权限与 umask

新建文件的默认权限通常从 666 开始(不带执行),目录从 777 开始(需要执行位)。umask 会按位清除相应的权限。

比如 umask=022,目录结果是 777 掩掉 ----w--w- 得到 755,文件结果是 666 掩掉后得到 644。严格说不是做十进制减法,而是做位掩码清除。

常用 umask 值:

  • 022:目录 755,文件 644,适合普通环境。
  • 002:目录 775,文件 664,适合团队共享。
  • 077:目录 700,文件 600,适合极高安全要求。

umask 只影响新创建的文件,更改后需写入 /etc/profile、用户 Shell 配置或 systemd 服务文件才能永久生效。

5.5 特殊权限与 ACL

  • SUID:可执行文件的所有者执行位变成 s,程序运行时临时获得文件所有者的权限(如 passwd)。风险高,有漏洞的程序可能成为提权口。
  • SGID:用在目录上时,新建文件会自动继承目录的所属组,非常适合团队共享。
  • Sticky Bit:共享目录尾部出现 t,所有人都可以创建文件,但只能删除自己的。

ACL 可以给指定用户或组单独授权,突破三类身份的限制。

setfacl -m u:alice:rw report.txt
setfacl -m d:u:alice:rwx shared   # 默认 ACL,新文件继承

注意 ACL mask,它可能把你明明给了 rwx 的权限给限缩成 r-x,碰到奇怪现象,先用 getfacl 看一眼。

六、动手实践 —— 文件与目录常用命令

6.1 浏览与导航

ls -lah         # 长格式、隐藏文件、人性化大小
ls -lt          # 按修改时间排序
ls -lhS         # 按大小排序

cd /etc
cd ..
cd ~
cd -            # 返回上一个目录

pwd

tree -L 2      # 树形展示,限制层级

有些环境没装 tree,可以 sudo apt install treednf install tree

6.2 创建与删除

mkdir -p project/{src,config,logs,data,tmp}  # 一条命令创建多组目录

touch app.log

rm app.log
rm -r project
rm -rf project    # 务必再三确认路径

我平时执行危险删除前,会先打印路径确认,脚本里还会检查变量是否为空:

if [ -z "$TARGET_DIR" ] || [ "$TARGET_DIR" = "/" ]; then
    echo "拒绝删除危险路径"
    exit 1
fi
rm -rf -- "$TARGET_DIR"

删除空目录用 rmdir,目录非空时它会拒绝,这种“胆小”反而让人更安心。

6.3 复制、移动与重命名

cp source.txt target.txt
cp -r project project-backup
cp -p app.conf app.conf.bak     # 保留时间戳和权限
cp -a source/ target/            # 归档模式

rsync -aHAX source/ target/     # 大量文件迁移,保留硬链接、ACL、扩展属性

mv old.txt new.txt
mv app.log archive/

同一文件系统内 mv 只改目录项,非常快;跨文件系统则接近“复制后删除”。

6.4 查看文件内容

cat app.conf               # 短文件直接看
less app.log               # 大文件分页,支持 / 搜索、n 跳转
head -n 20 app.log         # 前 20 行
tail -n 100 app.log        # 末 100 行
tail -f app.log            # 实时追踪
tail -F app.log            # 日志轮转后自动重新打开

more 还能分页,但交互能力不如 less,属于老古董了。

6.5 查找文件与目录

find 是实时遍历,功能强大但慢;locate 基于数据库,极快但可能滞后。

find /var/log -name "*.log"
find /var/log -type f -mtime -1
find /tmp -type f -mmin -60
find /srv -type f -perm 0777
find /tmp -type f -mtime +7 -exec rm -f {} \;    # 先换成 -exec echo {} \; 试运行

which 找可执行文件,type 能告诉你某个命令到底是别名、内建还是外部程序。whereis 查二进制、源码、手册路径。grep 搜索文件内容:

grep -rn "ERROR" /var/log/
grep -i "warn" app.log
grep -F "a.b.c" config.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

七、文件的时间属性

7.1 三种时间戳与出生时间

  • atime:最后读取内容的时间(受 relatimenoatime 挂载策略影响,不一定每次访问都更新)。
  • mtime:最后修改内容的时间,ls -l 默认显示的就是它。
  • ctime:最后修改 inode 状态的时间(chmod、chown、写入内容等都可能更新)。ctime 不是创建时间,那个 c 更接近“change”。
  • birth time / crtime:真正的创建时间,需要文件系统支持,很多老工具或系统直接显示 -

7.2 使用 stat 查看时间信息

stat report.txt

输出可能包含 AccessModifyChangeBirthls -l 默认看 mtime,ls -lu 看 atime,ls -lc 看 ctime。但完整分析还是 stat 最实在。

7.3 使用 touch 修改时间戳

touch report.txt           # 更新 atime 和 mtime 到当前
touch -a report.txt        # 只改 atime
touch -m report.txt        # 只改 mtime
touch -t 202607201530 report.txt
touch -r template.txt report.txt  # 参照另一个文件的时间

注意:用 touch 修改 atime 或 mtime 时,ctime 仍会被系统更新为当前时间,没法像 mtime 那样任意设回历史值。

7.4 时间戳的实际用途

增量备份依赖 mtime 和大小(rsync -av 默认快速检查),find 结合 -mtime-mmin 做文件清理,logrotate 依赖时间戳判断轮转周期。时间戳不是摆设,是很多自动化工具运转的基石。

八、综合实战 —— 从零搭建项目与常见错误排查

8.1 搭建 Web 项目目录

假设部署一个叫 myweb 的服务,设计成这样的结构:

/srv/myweb
├── current -> releases/20260731
├── releases
│   ├── 20260720
│   └── 20260731
├── shared
│   ├── config
│   ├── data
│   ├── logs
│   └── uploads
└── 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/config
sudo ln -s /srv/myweb/shared/uploads /srv/myweb/releases/20260731/uploads
sudo ln -sfn /srv/myweb/releases/20260731 /srv/myweb/current

回滚也只是重新原子更新 current 链接,非常快。不过真实部署还要考虑进程重启、健康检查等,但符号链接帮我们把版本路径管理做得非常干净。

8.2 权限规划与设置

创建专用用户和组,设置目录所有权,用 SGID 和 ACL 保证新文件归属。

sudo groupadd myweb
sudo 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 更稳
sudo find /srv/myweb -type d -exec chmod g+s {} \;
sudo setfacl -R -m g:myweb:rwx /srv/myweb
sudo setfacl -R -d -m g:myweb:rwx /srv/myweb

配置文件加严:chmod 640 /srv/myweb/shared/config/app.env;日志目录用 chmod 2750;上传目录用 2770。团队协作时把 umask 002 加进部署脚本。

8.3 路径导航与软链接

日常排错时,cd /srv/myweb/current 快速进入当前版本,readlink -f /srv/myweb/current 看清真实路径。ls -l /srv/myweb/current 看链接本身,末尾加 / 则查看目录内容。

8.4 常见错误排查

  • Permission denied:依次用 ls -lnamei -lgetfaclfindmnt 排查权限、路径执行位、ACL 和只读状态;有 SELinux 时别忘了 ausearch -m AVC别上来就 chmod 777
  • No such file or directory:先核对路径、检查符号链接是否失效,再看看脚本的解释器路径是否存在、换行符是否有 CRLF。
  • No space left on devicedf -h 看磁盘,du -xhd1 /var | sort -h 逐级定位大目录;若 dfdu 差距大,多半是“已删除但仍被进程占用的文件”在作怪,用 lsof +L1 定位。
  • df 显示有空间却报空间不足:极可能是 df -i 看到 inode 耗尽,清理海量小文件。
  • Device or resource busy:卸载前先 fuser -mv /mnt/datalsof +D /mnt/data 看谁在占用,也别忘记检查当前 Shell 是否刚好站在该目录里。懒卸载 umount -l 适用于恢复场景,但别当万能按钮。
  • Operation not permitted:root 都删不掉?用 lsattr 看看文件是否被设了不可变属性 i,有就去掉:sudo chattr -i file

错误排查速查表:

错误提示 优先排查命令 处理要点
Permission denied ls -lnamei -lgetfacl 修权限、补目录 x、查 ACL
No such file or directory readlink -ffilehead -n 1 修路径、重建链接、换行符
No space left on device df -hdu -xhd1 清理大文件、迁移或扩容
有空间却提示空间不足 df -i 定位并清理海量小文件
删除日志后空间不释放 lsof +L1 重载或重启进程
Device or resource busy fuser -mvlsof +D 退出目录、结束进程或懒卸载
Operation not permitted lsattr chattr -i 去掉不可变属性

8.5 日志目录迁移实战

假设 /var 分区告急,新磁盘挂载在 /data,需要把 /var/log 迁过去。这类操作最好在维护窗口进行:

  1. 确认磁盘:lsblk -f && df -hT /data

  2. 创建目标:sudo mkdir -p /data/var-log

  3. 在线预同步:sudo rsync -aHAX /var/log/ /data/var-log/

  4. 停相关服务(如 systemctl stop nginx myapp),做最终同步:

    sudo rsync -aHAX --delete /var/log/ /data/var-log/

  5. 备份原目录并创建软链接:

    sudo mv /var/log /var/log.old
    sudo ln -s /data/var-log /var/log
  6. 验证 ls -ld /var/logreadlink -f /var/log,然后启动服务,tail -F /var/log/syslog 观察日志正常产出。

  7. 配置 /etc/fstab 确保新磁盘开机自动挂载(使用 UUID,加 nofail)。

更稳的方案是直接把新文件系统挂载到 /var/log 或用 bind mount,避免长期依赖软链接。

8.6 链接的妙用与误区

软链接非常适合版本切换:/opt/nginx -> /opt/nginx-1.28.0。升级时 ln -sfn /opt/nginx-1.29.0 /opt/nginx,所有业务配置都不需要改。

硬链接看起来像“备份”,实则大坑。 ln report.txt report-backup.txt 后,修改 report.txtreport-backup.txt 跟着变;清空其中一个,另一个也空了。它只能防止误删一个名字,但对内容损坏、文件系统故障、勒索软件毫无抵抗力。真正的备份,必须生成独立的数据副本,cp -arsync -aHAX --delete,再配合版本控制(git),最好有 3-2-1 策略(三份数据、两种介质、一份异地)。


附录 A 常用命令速查表

目标 命令
查看当前目录 pwd
列出文件 ls -lah
显示 inode 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 基础用户命令,现代系统常链接到 /usr/bin
/sbin 系统管理命令,现代系统常链接到 /usr/sbin
/boot 内核、initramfs、引导程序
/dev 设备文件
/etc 系统级配置
/home 普通用户家目录
/root root 用户家目录
/lib 基础共享库,现代系统常链接到 /usr/lib
/media 可移动介质自动挂载位置
/mnt 临时手动挂载位置
/opt 附加或第三方软件
/proc 进程与内核虚拟文件系统
/run 本次启动的运行时数据
/srv 服务对外提供的数据
/sys 设备与内核对象虚拟文件系统
/tmp 短期临时文件
/usr 用户空间程序、库和共享资源
/usr/local 本机管理员手动安装的软件
/var 日志、缓存、队列和动态数据
/var/lib 应用持久状态数据
/var/log 系统和应用日志
/var/cache 可重新生成的缓存
/var/tmp 保留时间通常长于 /tmp 的临时文件

附录 C 权限数字与 umask 示例

基础数字:r=4,w=2,x=1;常见组合:7=rwx,6=rw-,5=r-x,4=r--。常见权限:

600  rw-------   私密文件
640  rw-r-----   服务配置
644  rw-r--r--   普通文本文件
700  rwx------   私密目录或脚本
750  rwxr-x---   服务目录
755  rwxr-xr-x   普通目录和公开程序
775  rwxrwxr-x   组协作目录

umask=022 → 目录 755,文件 644;002 → 775,664;077 → 700,600。特殊权限数字:4 SUID,2 SGID,1 Sticky Bit。

附录 D find 常用组合

find /path -name "*.conf"
find /path -iname "*.jpg"
find /path -type f           # 只查文件
find /path -type d           # 只查目录
find /path -type f -size +500M
find /path -type f -mmin -60   # 1小时内修改
find /path -type f -mtime +7   # 7天前修改
find /path -type f -empty
find /path -user alice
find /path -type f -perm -0002  # 全局可写
find /tmp/myapp -type f -mtime +7 -delete   # 先换成 -print 预览

附录 E 三种时间戳对比

时间属性 含义 常见触发操作
atime 最后读取内容的时间 cat、程序读取
mtime 最后修改内容的时间 写入、追加、截断
ctime 最后修改 inode 状态的时间 chmod、chown、写入、链接变化
birth time 文件创建时间(部分系统支持) 创建文件时

附录 F 常见错误速查

现象 优先检查
无法读取文件 文件 r、路径中各目录的 x
无法进入目录 目录 x
无法列出目录名 目录 r
能看文件名但 ls -l 全是问号 目录有 r 但没有 x
无法删除文件 父目录缺少 wx
root 也删不掉文件 lsattr 检查不可变属性
磁盘空间满 df -h
inode 用完 df -i
删除日志后空间不变 lsof +L1
挂载点无法卸载 fuser -mv
符号链接失效 readlink -f
脚本存在却无法执行 检查解释器、换行符和执行权限
配置修改后服务仍读旧内容 检查真实路径、符号链接和进程是否重载



上一篇:Podman v6.0 发布、LUKS 加密漏洞与 PeerTube 去中心化:Hacker News 技术热点深度杂谈
下一篇:峰值Token降20%!Meta&CMU开源长程Agent自主上下文管理框架ACM
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-8-3 06:24 , Processed in 0.791106 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表