Linux 目录结构及 VIM
前言
- 这篇讲两件事:文件系统与目录结构(Linux 的东西都放在哪儿、怎么找到它),以及 VIM(登上服务器后唯一一定能用的编辑器)。
- 环境说明:本文所有「实测」都是在《Linux 入门》里那套环境上跑出来的——Ubuntu 26.04.1 LTS(WSL2),内核
6.18.54.1-microsoft-standard-WSL2,VIM 9.1,日期 2026-11-03。Debian/Ubuntu 服务器上的结果基本一致;RHEL 系(Rocky/TencentOS)的差异我会点出来。
文件系统:先分清三种说法
「文件系统」这四个字在中文里同时指三个东西,混着讲就会晕:
| 说法 | 英文 | 它是什么 | 例子 |
|---|---|---|---|
| 存储格式 | filesystem type | 磁盘上组织文件和目录的数据结构,格式化(mkfs)时决定 | ext4、xfs、vfat、btrfs、NTFS |
| 管理层 | file system(软件机构) | 内核里负责读写这套结构的那一层代码 | VFS + ext4 驱动 |
| 挂载点 | mounted filesystem | 某个格式被挂到目录树的哪个位置 | / 挂在 /dev/sdd,/tmp 是 tmpfs |
原笔记那句「文件系统就是管理存储设备或分区上的文件的方法和数据结构」说的是第一种。第二种在 Linux 里有个专门的名字 VFS(Virtual File System,虚拟文件系统):它定义了一套统一接口(打开、读、写、列目录),底下插哪种格式都能用同一组系统调用访问——这就是为什么你 cat 一个文件时不需要关心它在 ext4 还是 9p 上。
第三种最重要,也最容易被新手忽略:Linux 的目录树和磁盘是不是一回事。Windows 是「一个盘符一棵树」,Linux 是「一棵树,多个来源拼起来」——ls -l / 看到的每个目录,可能来自不同的设备、不同的格式。
本机实测:谁挂在哪儿
三条命令就能看清整台机器的存储布局。下面是我这台的真实输出。
findmnt 看挂载关系(比老的 mount 好用,自带树形):
1 | $ findmnt --real -o TARGET,SOURCE,FSTYPE |
读这张表能得到三个结论:
- 根目录
/是 ext4,设备是/dev/sdd(WSL 把发行版的文件系统放在一个ext4.vhdx虚拟磁盘里,Windows 侧看是一个文件,Linux 侧看是一块盘)。 /mnt/c、/mnt/d这些是 9p 协议挂载的 Windows 分区,不是真的 Linux 文件系统——所以它们上面没有真正的权限位,chmod不起作用,读写也慢一个数量级。这就是《Linux 入门》里说「别把工程放在/mnt/...下面」的根本原因。/usr/lib/linux-tools/...这种是从另一块盘/dev/sdb里取子目录挂过来的(方括号里是卷内路径),WSL 用它塞内核工具和头文件。
df -Th 看容量和类型(T 就是显示 type):
1 | $ df -Th | head -6 |
注意 /dev/sdb 那行 Use% 100%——它是只读的固定大小卷,满了是正常的,不是故障。根分区显示 1007G 但只用了 1.3G,这是稀疏 vhdx 的特征:上限 1 TB,实际按需增长。
lsblk -f 看块设备本身:
1 | $ lsblk -f |
sdc 是 swap 分区(对应《Linux 入门》里 .wslconfig 配的 swap=4GB),它不挂在任何目录上,内核自己用。
内核支持哪些文件系统
/proc/filesystems 列出的是当前内核能用的文件系统类型,分两类:带 nodev 的不需要块设备(虚拟文件系统),不带的可以挂在真实磁盘上。
1 | $ cat /proc/filesystems |
我这台一次查了 15 种格式的实测结果:
| 格式 | 这台 WSL 内核 | 说明 |
|---|---|---|
| ext2 / ext3 / ext4 | 支持 | Linux 的看家格式,Ubuntu 根分区默认 ext4 |
| xfs | 支持 | RHEL/Rocky/TencentOS 的默认格式 |
| vfat | 支持 | 就是 FAT12/16/32,U 盘、ESP 分区都是它 |
| squashfs | 支持 | 只读压缩,snap/livecd 用 |
| overlay | 支持 | 分层联合挂载,Docker 的默认存储驱动 |
| fuse / 9p | 支持 | 用户态文件系统 / 虚拟机共享目录 |
| btrfs | 默认没加载 | 见下面的实测 |
| exfat | 默认没加载 | 同上 |
| ntfs / ntfs3 | 不支持 | 微软这个内核没编;Linux 读 NTFS 靠 ntfs-3g(FUSE),实测本机未安装,候选版本 1:2022.10.3-5ubuntu1.1 |
| f2fs | 不支持 | 闪存优化,安卓机内部存储用 |
关键的一点:/proc/filesystems 里没有 ≠ 内核不支持,可能只是模块还没加载。实测:
1 | # root 下执行 |
modprobe 之后同一份 /proc/filesystems 立刻多出了两行。桌面版 Ubuntu 内核把这些都编进去了,所以你在服务器上一般碰不到这个差别;WSL 用的是精简内核才需要自己加载。
顺带一个新手会撞上的坑:想格式化 U 盘却发现工具根本没装。
1 | $ for t in mkfs.btrfs mkfs.exfat mkfs.vfat mkfs.ext4; do printf '%-12s %s\n' "$t" "$(command -v $t || echo 未安装)"; done |
mkfs.* 属于各自的工具包(dosfstools、btrfs-progs、exfatprogs),最小化系统里只有 e2fsprogs,要用得先 apt install。
常见文件系统格式对照
原笔记那串 FAT16/FAT32/NTFS/EXT4/HFS+/EXFAT 的清单方向是对的,但少了 2019 年之后普及的几个,个别数字也含糊。下面这版把上限数字标清来源:带「实测」的是我这台机器上量出来的,其余是官方文档值(本机是 ext4/9p,没法凭空造出 FAT 卷来验证,mkfs.vfat 也没装)。
| 格式 | 出处 | 主要特点 | 常见上限(文档值) |
|---|---|---|---|
| FAT16 | MS-DOS | 16 位簇号,老 | 分区约 2 GB |
| FAT32 | Windows 95 OSR2 | 兼容性好,无日志 | 单文件 4 GB − 1 字节 |
| exFAT | Windows CE / 现在 U 盘默认 | 为闪存设计,无日志,跨 win/mac | 单文件 16 EB(实际受平台限制) |
| NTFS | Windows NT 起 | 日志、ACL、压缩、卷影副本 | 单文件 16 TB(Windows 实现) |
| ext2 | Linux 早期 | 无日志 | 已被 ext3/4 取代 |
| ext3 | Linux | 给 ext2 加了日志 | 同上量级 |
| ext4 | Linux,2008 起 | 扩展性日志文件系统,Ubuntu 默认 | 单文件 16 TB、卷 1 EB(4 KB 簇) |
| xfs | SGI IRIX → Linux | 大文件/高并发强,RHEL 系默认 | 单文件 8 EB 量级 |
| btrfs | Oracle 主导 | 写时复制、快照、校验、内建 RAID | 单文件 16 EB |
| f2fs | Samsung | 闪存友好,安卓内部存储 | — |
| HFS+ | Mac OS 9 → 10.13 | 苹果旧格式 | 已被 APFS 取代 |
| APFS | macOS 10.13 起 | 写时复制、快照、加密 | 单文件 8 EB |
| squashfs | Linux | 只读压缩,装了就改不了 | — |
| tmpfs | Linux | 内存/交换分区当磁盘,重启就没 | 受内存与 mount size 限制 |
| 9p / virtiofs / NFS / CIFS | — | 网络或跨系统共享,性能与语义都有坑 | — |
最后一行不是「格式」而是「远程/跨系统文件系统」,但它们在真实服务器上天天遇到,值得单列。
关于 tmpfs,本机实测很直观:
1 | $ findmnt /tmp |
结论:/tmp 是内存文件系统,重启内容就没了;/var/tmp 在磁盘上,重启还在。这就是 FHS 规范里「/tmp 里的东西程序退出后可能随时被销毁,/var/tmp 允许长期保存」的由来。这个 /tmp 是 systemd 的 tmp.mount 单元挂上来的:
1 | $ systemctl status tmp.mount --no-pager | head -6 |
而传统上负责挂载磁盘的 /etc/fstab,在这台机器上是空的:
1 | $ cat /etc/fstab |
WSL 不需要它,Windows 的盘由 WSL 自己按 /etc/wsl.conf 挂载(实测该文件只有 [boot] systemd=true 和 [user] default=loong 两段)。在真服务器上,/etc/fstab 是开机自动挂载表,改错了会起不来,改之前务必备份。
根目录结构(实测版)
Linux 没有 C 盘 D 盘,一切都从 / 这棵树长出来。下面这版是我这台机器上 ls -l / 的真实输出,条目数也是量的:
1 | $ ls -l / |
| 目录 | 是什么 | 本机实测 |
|---|---|---|
/ |
根目录,唯一没有上级 | 挂在 /dev/sdd,ext4 |
/bin |
用户命令 | 软链接 → usr/bin,905 个条目 |
/boot |
内核、initramfs、GRUB 配置 | 空(WSL 的启动不经过它,内核由 Windows 侧提供) |
/dev |
设备文件 | 189 项,块设备 28 个、字符设备 140 个 |
/etc |
全局配置文件 | 172 项,*.conf 递归一层就有 73 个 |
/home |
普通用户家目录 | 1 项:loong,权限 drwxr-x---(750) |
/init |
WSL 自己的启动程序 | 3.4 MB 可执行文件,物理机/普通服务器上不存在 |
/lib、/lib64 |
共享库 | 都是软链接 → /usr/lib、/usr/lib64 |
/lost+found |
ext4 文件系统检查时捡回来的碎片 | 空,权限 drwx------(只有 root 能进) |
/media |
桌面环境插入 U 盘时的自动挂载点 | 空(服务器/WSL 上没有桌面自动挂载) |
/mnt |
手工挂载的约定位置 | 6 项:c d e f wsl wslg,被 WSL 占用了 |
/opt |
第三方大包(自己装一套进去) | 空 |
/proc |
进程与内核信息的虚拟文件系统 | 不占磁盘空间,dr-xr-xr-x |
/root |
root 用户的家目录,和 / 不是一回事 |
权限 drwx------(700),普通用户进不去 |
/run |
开机以来的运行时信息(PID、socket) | tmpfs,28 项,重启清空 |
/sbin |
系统管理命令 | 软链接 → usr/sbin,176 个条目 |
/srv |
本机对外提供的数据(网站根目录等) | 空 |
/sys |
内核/设备/驱动的虚拟文件系统 | dr-xr-xr-x |
/tmp |
临时文件 | tmpfs,drwxrwxrwt(注意末尾的 t) |
/usr |
系统自带的程序与数据(Unix System Resources) | 10 个子目录,见下一节 |
/var |
会变的数据:日志、缓存、队列 | 14 个子目录 |
必须记住的两处变化:
/bin、/sbin、/lib已经是符号链接。这件事叫 usrmerge: Debian 从 10、Ubuntu 从 22.04 起把可执行文件和库统一放进/usr,根目录只留软链接。所以「/bin放命令、/usr/bin也放命令」这种区分在今天只是历史,写脚本时用哪个都能跑,但别去ls -l /bin找实体目录。/cdrom不存在(原笔记表里有)。它只是老 Ubuntu 安装器留下的挂载点,服务器和 WSL 上都没有。查一下实测就是ls: cannot access '/cdrom'。/snap在,但基本是空的。里面只有一个README,内容是「This directory presents installed snap packages」;snap list输出「No snaps are installed yet」。snapd 2.76.3 装了但没在用。原笔记「/snap 是 Ubuntu 自己搞的包管理系统」这句描述没错,但要补一句:它是包管理器的挂载目录,不是数据目录,真正的包数据在/var/lib/snapd。
四个最常进去的目录展开一层
背整棵树没用,日常真正反复进的就这四个。
/usr:程序和数据,装完基本不变
1 | $ ls /usr |
| 子目录 | 放什么 | 你会什么时候进去 |
|---|---|---|
/usr/bin |
所有用户命令(本机 905 个) | which/command -v 找程序 |
/usr/sbin |
管理命令(176 个) | 只有 sudo 时用得到 |
/usr/lib |
共享库、插件、systemd 单元文件 | 查 /usr/lib/systemd/system/*.service |
/usr/local |
你自己装的东西,包管理器不碰它 | 源码 make install 默认装这里 |
/usr/share |
与架构无关的数据:文档、图标、字典 | /usr/share/doc 本机 545 个包各有一份文档 |
/usr/include |
C 头文件 | 编译时报「找不到头文件」先查这里 |
/usr/src |
内核源码/头文件 | 装驱动时 |
/usr/local 是「不想被包管理器覆盖」的标准去处,它内部结构和 /usr 一模一样(实测:bin etc games include lib libexec man sbin share src)。
/etc:配置,全是文本文件
1 | $ ls /etc | head -24 |
三个和新手最相关的点:
/etc下的文件不该由你手动创建,但该由你手动读——服务改端口、加用户、配定时任务都在这里。- 带
.dpkg-old、.dpkg-new、.distrib后缀的是包管理器升级配置时留下的备份,看到别慌。 - 敏感文件靠权限保护,实测:
1 | $ stat -c '%n %A %U %G' /etc/passwd /etc/shadow /root /home/loong |
/etc/passwd 人人可读(只有账号信息,没有密码),/etc/shadow 是密码哈希,属组 shadow、权限 640,普通用户读不了。注意最后一行:Ubuntu 现在把用户家目录设成 750,别的用户进不了你的 ~,这是安全默认值,不是坏了。
/var:会变的数据
1 | $ ls /var | tr '\n' ' ' |
排查问题最常去的是 /var/log(本机 17 MB,apt、dpkg.log、auth.log、dmesg、bootstrap.log 等都在),其次是 /var/lib(服务的状态数据,比如 Docker 镜像、数据库文件)。
/run:只在本次开机存在的运行时状态
1 | $ ls /run | head -10 |
crond.pid 这种「进程号文件」就放这里。它挂的是 tmpfs,所以重启自动清空——这也是为什么 /run 里绝不该放需要持久化的东西。
一切皆文件:设备文件与虚拟文件系统
Linux 把「磁盘、终端、键盘、随机数发生器」都表达成 /dev 下的一个文件,用同一套 open/read/write 访问。实测看类型:
1 | $ ls -l /dev/null /dev/zero /dev/random /dev/tty /dev/sda /dev/sdd |
怎么读这行:
- 第一个字符
c= character device(字符设备,一次一个字节,如终端、/dev/null),b= block device(块设备,按块读写,如磁盘)。本机/dev实测 140 个字符设备、28 个块设备、0 个普通文件。 1, 3是主设备号, 次设备号,内核靠它找到驱动;8, 48就是「第 8 号 SCSI 磁盘驱动的第 48 个设备」= 第 4 块盘的第 1 个分区位置算法。- 设备文件的时间戳是系统启动时间,不是文件被改的时间。
用起来最顺手的是 /dev/null:把不要的输出丢进黑洞。
1 | $ echo hello > /dev/null |
/dev/zero 读出来全是 0(用来造测试文件),/dev/null 写多少都不占空间。所以「command > /dev/null 2>&1」的意思是把正常输出和错误输出都丢掉,只留下退出码。
/proc 和 /sys 是内核内存的目录化视图,不占磁盘:
1 | $ head -5 /proc/cpuinfo |
MemTotal 8134332 kB ≈ 7.8 GiB,正是 .wslconfig 里 memory=8GB 的效果——这些文件是「真相」,比任何 GUI 显示的数字可靠。/proc 偏进程与内核状态,/sys 偏设备与驱动属性(sysfs)。
路径的四个特殊记号与隐藏文件
原笔记的「尝试」四条,我全部实测了一遍:
1 | $ cd /tmp/probe && touch .hidden visible |
| 记号 | 含义 | 补充 |
|---|---|---|
. |
当前目录 | ls 默认不显示,./脚本.sh 用来执行当前目录里的脚本 |
.. |
上一级目录 | /.. 还是 /,根目录没有上级 |
~ |
当前用户的家目录 | ~loong 是 loong 的,~root 是 root 的;echo ~ 与 $HOME 实测一致 |
/ |
根目录 | 也是路径分隔符,Windows 的反斜杠在这儿是普通字符 |
以 . 开头的文件 |
隐藏文件 | ls -a 才显示;ls -A 显示但省掉 . 和 .. 两项 |
家目录里到底有什么,实测(新建用户时这些是从 /etc/skel 复制来的,那里就 .bash_logout .bashrc .profile 三个):
1 | $ ls -A ~ |
.viminfo 是 VIM 留下的(后面会讲)。
一个原笔记没提但一定会踩的坑:文件名以 - 开头时,命令会把它当选项。
1 | $ touch ./-dash && rm ./-dash && echo "用 ./-dash 可以删" |
正确写法是加 ./ 前缀(或用 -- 结束选项解析:rm -- -dash)。
新手最容易迷路的三件事
- 相对路径是相对「当前目录」,不是相对「你脑子里的项目目录」。
cd /etc之后ls nginx看的是/etc/nginx,不是/usr/share/nginx。任何时候先pwd。 - 绝对路径和相对路径混着写会出诡异错误。 脚本里
./run.sh和/opt/app/run.sh都没问题,但run.sh(不带./)会去PATH里找,找不到就command not found——这是当前目录不在PATH里的安全设计,不是 bug。 - 权限不足时报的是「Permission denied」,目录不存在时报的是「No such file or directory」,两者别搞混。前者要么
sudo要么改属主,后者是路径写错了(注意 Linux 区分大小写,/etc/X11和/etc/x11是两个东西;博客里图片路径大小写写错,本地 Windows 一切正常、线上 Linux 直接 404,我踩过)。
vi、vim、vim.tiny 是同一个人吗
原笔记说「vi 是默认自带的编辑器,后来有了加强版 vim」,方向对,但今天 Ubuntu 上的实际情况是这样(实测):
1 | $ command -v vi vim view vimdiff |
也就是说:
/usr/bin/vi是 alternatives 机制的一个软链接,当前指向vim.basic,所以vi和vim跑的是同一个程序(vim-tiny也在候选列表里,优先级更低才没被选中)。- 但
vi启动时 Vim 会按名字进入兼容模式(nocompatible不生效),行为差异主要在退格键、可视块等细节上。写脚本、配 git 时看到vi别以为是另一个编辑器。 - 本机版本与包:
1 | $ vim --version | head -2 |
- 编译特性决定能力边界,实测这台是
+mouse +syntax +clipboard +terminal +python3 +virtualedit +job +channel,-lua -perl -xterm_save。看到-mouse才需要装完整版,这里+mouse说明:set mouse=a能用。 nvim(Neovim)没装;nano装了(/usr/bin/nano)。新手在服务器上如果 vim 操作不熟,nano是一定能用的退路:底部一行快捷键提示,改完Ctrl+O保存、Ctrl+X退出。vimtutor在/usr/bin/vimtutor,这是官方自带的两小时教程,比背任何表格都有效:vimtutor直接跑(本机/usr/share/vim/vim91/tutor下有 50 多个语言版本,含tutor1.zh_cn)。
安装命令照旧是 sudo apt install vim,但要知道 Ubuntu Server/WSL 镜像里通常已经带了,先 command -v vim 再装。
VIM 的三种模式
VIM 的一切困惑都来自「同一个按键在不同模式下意思不同」。三种模式:
| 模式 | 怎么进 | 能干什么 | 屏幕上的表现 |
|---|---|---|---|
| 正常模式(命令模式) | 打开文件时的默认模式;任何时候按 ESC 回到它 |
移动光标、删除、复制粘贴、撤销、按 : 进单行模式 |
没有提示符 |
| 插入模式 | i a o I A O s S 等 |
打字 | 左下角出现 -- INSERT -- |
| 单行模式(ex 命令) | 正常模式下按 :(或 / ?) |
存盘、退出、查找替换、改配置 | 光标跑到最底行,前面有个 : |
原笔记漏了一个同样常用的:可视模式(v 按字符选、V 按行选、Ctrl-V 按列选),选中后可以直接对这块区域执行 d/y/>。
还有一个新手最容易慌的点:ESC 按了没反应、: 打不出来——很可能是上一次插入模式没退干净,连按几次 ESC 就回来了。想退出又不敢退的时候,:q! 是万能逃生门(不保存退出)。
按键实测:正常模式
下面每一行都是我在本机 vim 9.1 上真跑出来的结果,测试文件是 5 行 line1…line5(除注明外)。
| 操作 | 实测结果 |
|---|---|
2 然后 dd |
变成 4 行:line1 line3 line4 line5(删掉光标所在行) |
2 然后 3dd |
变成 2 行:line1 line5(从第 2 行起删 3 行) |
1 然后 yy 再 p |
变成 6 行:line1 被复制了一份 |
dd 再 u |
5 行完整恢复(撤销) |
dd 再 u 再 Ctrl-R |
又变成 4 行(反撤销,重做删除) |
第 3 行按 x |
line3 → ine3(删光标下那个字符,向右) |
第 3 行按 2x |
line3 → ne3 |
行首按 X |
不变(左边没字符可删,边界安全) |
hello world 行尾按 X |
→ hello word(向左删一个字符) |
gg |
光标到第 1 行 |
G |
光标到最后一行(实测 :put 写出的行号 = 文件末行) |
3gg |
到第 3 行 |
G2k |
末行往上 2 行 |
:4 |
光标到第 4 行(实测输出该行内容 a4) |
vim +4 file |
打开即定位第 4 行(实测当前行内容 a4) |
vim file + |
打开即定位末行(实测当前行内容 a6) |
0 / ^ / $ |
列号实测:0 在第 1 列,^ 在第一个非空字符(3 个空格缩进的行 = 第 4 列),$ 在最后一个字符( lead = 第 7 列) |
J |
把下一行并上来:a1 + a2 → a1 a2 |
w / b |
下一个单词开头 / 上一个单词开头(实测 0w 后列号 = 8,正好落在 the 之后的 q 前) |
3G 然后 dw |
第 3 行变空(删掉一个单词 line3) |
3G 然后 gUgg |
前 3 行全大写:A1/A2/A3/a4/a5/a6(gU 是「到大写」的操作符,配运动 gg) |
3G 然后 5y 再 p |
复制粘贴,寄存器内容实测为最后一行 |
原表里 h j k l(左下上右)、Ctrl-F/B(整页)、Ctrl-D/U(半页)、nx、nX、nyy、ndd 这些写法都成立,只是分页类按键需要真实屏幕高度,无终端环境下测不出滚动效果,我按官方文档保留。
补两个原笔记没写、但极其省事的:
f<char>跳到本行下一个该字符(t<char>停在它前面),配合;重复。:set number之后按5G和:5等价,但5G不用离开正常模式。
按键实测:插入模式与单行模式
进入插入模式的 8 个键,实测区别(第 3 行内容是 line3,缩进行是 lead):
| 键 | 实测结果 | 一句话记法 |
|---|---|---|
i |
line3 → XXXline3 |
光标前插入 |
I |
lead → XXXlead |
第一个非空字符前插入(不是第 1 列!) |
a |
abc → aZZZbc |
光标后插入 |
A |
lead → leadEND |
行尾插入 |
o |
在下方新开一行 APPENDED |
open below |
O |
在上方新开一行 ABOVE |
open above |
s |
删光标下字符并进插入 | 小写 = 字符级 |
S |
整行变成 REPL |
大写 = 整行级 |
规律就是大写比小写「范围更大」:i 在光标、I 在行首非空;a 在光标后、A 在行尾;s 删一字符、S 删整行;o/O 上下对称。
单行模式(按 : 之后)实测:
| 命令 | 实测 |
|---|---|
:w |
保存 |
:w 新文件名 |
另存为,本机确认生成了 alt.txt |
:q |
没有修改时退出 |
:q! |
有修改也强行退出,文件保持原样(实测内容仍是 orig) |
:wq / :x |
保存并退出,两个都被接受 |
:w! |
只读文件强制保存(需要文件本身可写或你有权限) |
:e! |
放弃全部修改,重新读入磁盘版本(实测内容回到 orig) |
:set nu / :set nonu |
显示 / 关闭行号,实测都生效 |
:set tabstop=4 |
一个 Tab 显示宽度设为 4,生效 |
:set mouse=a |
允许鼠标点击定位,本机 +mouse 支持,生效 |
:nohlsearch |
清掉当前搜索的高亮,生效 |
:1,3s/a/b/g |
第 1 到 3 行做替换,生效 |
:n |
跳到第 n 行,实测 :4 落在第 4 行 |
替换的 g 到底影响什么,实测最直观(文件内容 aaa bbb aaa / ccc aaa):
1 | :%s/aaa/X/ → X bbb aaa / ccc X ← 每行只替换第一处 |
所以「g 是全局」这个说法要讲准确:g 管的是「一行内多处」,跨行是靠地址范围(% 表示全部行),两者是正交的。原笔记那句「g 表示全局(默认只能替换一行中的第一处)」其实已经说对了,只是「全局」这个词容易让人以为是跨行。
原笔记里那两条写错的命令
第一条:表格里 :no nonu[mber] | 隐藏行号。实测不成立。
1 | $ vim -u NONE -N -es -c 'set number' -c 'no' -c 'set number?' -c 'q' |
原因是 :no 是 :nohlsearch 的缩写(清除搜索高亮),而 Vim 的命令行缩写要求唯一前缀,nonu、nu、n 这几个前缀在 :no... 这一族里不唯一,所以并不会被当成 :set nonumber。正确写法只有两种:
- 关行号:
:set nonu(或:set nonumber) - 清搜索高亮:
:no(完整:nohlsearch)
顺带把 :set number? 这个用法记住:问号结尾是「查询当前值」,:set mouse?、:set tabstop? 同理,是排查配置有没有生效的最快办法。
第二条:若文件没有生效,需要重新加载一次 source ~/ .vimrc。路径里多了一个空格,~/ .vimrc 会被解析成两个参数(家目录 + 一个不存在的 .vimrc)。正确写法:
1 | :source ~/.vimrc |
另外「打开文件后的配置是临时的,关闭就失效」这句要更正::set 的修改在退出时会写进 ~/.viminfo(shada)里的一部分(比如标记、撤销历史),但选项本身不会保存,所以确实需要写进 ~/.vimrc 才永久生效。
vimrc:让配置永久生效
Ubuntu 的系统级配置在 /etc/vim/vimrc,实测它的非注释内容只有这些:
1 | $ grep -v '^ *"' /etc/vim/vimrc | grep -v '^ *$' |
这就解释了为什么你的 vim 有语法高亮、却没有行号、鼠标也不能点。三种配置来源下的出厂默认值,实测对比:
| 启动方式 | 实测默认值 |
|---|---|
vim -u NONE(完全裸配置) |
nonumber mouse= tabstop=8 background=light |
vim -u DEFAULTS(Vim 自己的默认) |
nonumber mouse=nvi tabstop=8 background=light |
正常启动(读 /etc/vim/vimrc) |
nonumber mouse= tabstop=8 background=light,语法高亮开 |
要改就写在自己的 ~/.vimrc(本机实测这个文件默认不存在,需要自己建)。原笔记那份配置我逐条加载验证过,11 行全部被接受、没有报错:
1 | syntax on " 开语法高亮 |
实测加载后的取值:number autoindent smartindent tabstop=4 shiftwidth=4 showmatch ruler cindent background=dark mouse=a,全部生效。
两个补充建议:
- 写 Python 时把
cindent去掉——它是给 C/Java 那类花括号语言用的,和 Python 的缩进语义会打架。 set mouse=a在终端里有个副作用:选中文字复制会变成 VIM 自己的可视块选择,而不是终端选择。Linux/WSL 下按住Shift再用鼠标选,就能绕过。- 改完
~/.vimrc想立刻看效果,两种办法:重开 vim,或者在单行模式里:source ~/.vimrc。 - 想知道某条设置到底被谁设过,用
:verbose set tabstop?,它会打印「最后由哪个文件设置」。
交换文件与恢复
原笔记那段「若非正常关闭会产生临时交换文件」说的是真的,我把它量化了:用 screen 起一个真终端的 vim 打开 crash.txt,文件一被打开,同目录立刻出现隐藏交换文件:
1 | $ ls -A |
命名规则是 . + 原文件名 + .swp,所以它属于隐藏文件,只有 ls -a/ls -A 能看见。正常 :wq 退出后它会被自动删掉(实测:ls -A | grep -c swp 返回 0)。
不加文件名单用 vim -r,就是列出可恢复的交换文件:
1 | $ vim -r |
如果确实有残留,vim 打开同一文件时会弹一个 E325 提示,给你 Open read-only / Recover / Quit / Abort / Delete / Unedit 等选项,选 R 就从交换文件里把未保存的内容捞回来,然后按提示删掉 .swp。手动清理也很直接:rm .文件名.swp。
说明一句诚实的边界:我用 kill -9 试了两次想复现「残留 + E325 弹窗」,两次都没留住现场——vim 在无终端环境下会读到 EOF 正常退出并清理交换文件,而通过 screen 强杀时上层会话又会给它补一个退出机会。交换文件的创建和清理是实测的,E325 那个提示界面是 Vim 官方文档描述的(:help E325、:help swap-file)。
顺带两个同源的东西:
~/.viminfo(新版叫 shada)记录上次编辑的位置、标记、撤销历史、搜索历史。实测本机这个文件里存了 48 条最近文件,所以 vim 里:e #能回到上一个文件。- 原笔记「
!v打开最后使用 vim 打开的文件」这条不是 vim 功能,是 bash 的历史展开:!+ 前缀 = 重复最近一条以它开头的命令(实测set -o | grep histexpand显示histexpand on,交互式 bash 默认开启)。所以!v的意思是「再跑一遍最近那条以 v 开头的命令」,通常是vim 某文件。同一族的还有!!(上一条)、!$(上一条的最后一个参数)。
help 使用
--help 是绝大多数外部命令自带的短帮助,实测:
1 | $ ls --help | head -5 |
三个实测出来的坑:
-h不等于--help。ls -h是 human-readable(KB/MB 显示),grep -h是「不打印文件名」,tar -h是另一回事。查帮助请写全--help。- shell 内建命令没有
--help。实测echo --help直接把--help当字符串打印出来了,test --help则毫无输出。内建命令要用help:
1 | $ cd --help |
- 层次上从浅到深是:
命令 --help→man 命令→info 命令→ 上游文档。本机info已安装(/usr/bin/info),tldr(社区维护的「只看常用例子」版)没装,想用得apt install tldr。
VIM 自己的帮助是内置的,在 vim 里输入 :help 回车即可,它有 159 篇文档(实测 /usr/share/vim/vim91/doc 下 159 个文件)。常用的几个主题::help index.txt(快捷键总表)、:help quickref.txt(一页速查)、:help usr_t.txt(教程第 10 章「撤销/替换」)、:help tabstop、:help :wq。查完用 :q 关掉帮助窗口。
不会退出 vim 的时候,逃生路线永远是:ESC 多按几下 → :q! 回车。
man 使用
man 是 manual 的缩写,查系统标准手册页。本机实测:man-db 版本 2.13.1,装了 3834 份手册页,分 8 节:
1 | $ for s in 1 2 3 4 5 6 7 8; do printf 'man%-2s %s 页 例: %s\n' "$s" "$(ls /usr/share/man/man$s | wc -l)" "$(ls /usr/share/man/man$s | head -3 | tr '\n' '/')"; done |
八节的含义(这个背下来,查东西会准很多):
| 节 | 内容 | 典型例子 |
|---|---|---|
| 1 | 用户命令 | man 1 ls |
| 2 | 系统调用(内核给的接口) | man 2 open |
| 3 | C 库函数 | man 3 printf |
| 4 | 特殊设备文件 | man 4 console_codes |
| 5 | 文件格式与约定 | man 5 passwd、man 5 fstab |
| 6 | 游戏 | 基本用不到 |
| 7 | 杂项与协议 | man 7 regex、man 7 ip |
| 8 | 系统管理命令 | man 8 systemctl |
查配置文件要带节号,否则可能查到同名命令上:man passwd 是命令(改密码),man 5 passwd 才是 /etc/passwd 这个文件的格式说明。
实测这台机器的两个缺口:
1 | $ man 2 open |
原因是第 2、3 节的正文来自 manpages-dev 包,本机只装了 manpages 6.17-1,没装 manpages-dev——所以 man2 只有 1 页(intro)。写 C 语言要查函数得先 sudo apt install manpages-dev。
同名手册可能有多份实现,实测:
1 | $ man -w ls |
man -w 只打印路径不打开页面,用来确认「我到底会读到哪一份」。Ubuntu 26.04 上 ls 的手册页指向的是 rust 版 coreutils,这就是为什么 ls --help 的措辞和老 GNU coreutils 不一样。
man -f(等于 whatis)和 man -k(等于 apropos)是基于索引的快速查询,本机索引在 /var/cache/man/index.db,实测可用:
1 | $ man -f ls |
如果 man -k 报「没有索引」,跑一次 sudo mandb 重建即可。
翻页按键,原笔记那张表有几处错位,实测更正如下(man 用的是分页器,行为由 $PAGER 决定,Ubuntu 默认是 less):
| 按键 | 作用 |
|---|---|
空格 或 d 或 PageDown |
向下翻一页 |
b 或 u 或 PageUp |
向上翻一页 |
↑ / ↓ |
只滚动一行(不是页!原笔记把 ↓ 写成「向下翻一页」是错的) |
g / G |
跳到页首 / 页尾 |
/关键词 |
向后搜索,n 下一个、N 上一个 |
?关键词 |
向前搜索 |
h |
分页器自己的帮助 |
q |
退出 |
手册页自身的结构是固定的小节(实测 man 1 ls 的小节标题):NAME(一句话说明)、SYNOPSIS(用法,方括号表示可选、| 表示互斥)、DESCRIPTION(逐项解释)、OPTIONS、EXIT STATUS、BUGS、COPYRIGHT。
原笔记最后那句「有问题,找男人」——保留,但更准确的说法是:--help 看用法,man 看全量,/usr/share/doc/包名 看长文档,info 看 GNU 项目的教程,四个都试一遍再上网搜。
常见坑速查
- 以为
/bin、/sbin、/lib是真实目录:现在都是指向/usr/的软链接(实测readlink),别写脚本去find /bin找实体。 - 把工程放在
/mnt/c下:那是 9p 挂载的 Windows 分区,chmod无效、权限位全是 777、IO 慢一个数量级。 - 以为
/tmp里的东西会留着:它是 tmpfs,重启就没;需要跨重启请用/var/tmp。 - 分不清
/和/root:前者是根目录,后者是 root 用户的家目录(实测权限 700,普通用户进不去)。 ls看不到隐藏文件:以.开头的要ls -a(-A连.和..都不显示)。- 文件名以
-开头删不掉:用rm ./-dash或rm -- -dash。 :no nonu关不掉行号::no是:nohlsearch,关行号要:set nonu(实测)。source ~/ .vimrc多打一个空格:写成:source ~/.vimrc。- 以为
vi是另一个老编辑器:实测它是 alternatives 指向vim.basic的软链接。 echo --help:内建命令没有--help,它会把--help原样打印出来;用help echo。man passwd查配置文件:要写man 5 passwd;查不到系统调用是因为没装manpages-dev。- 以为
!v是 vim 的历史功能:那是 bash 的历史展开。 - 大小写敏感:
/etc/X11不是/etc/x11,图片路径也一样(Windows 本地正常、Linux 线上 404 的经典事故)。
练习
- 跑
ls -l /,把里面所有软链接找出来并解释它们指向哪儿(提示:第一个字符是l的行)。再回答:这台机器上/bin/ls实际在哪个目录? - 用
findmnt分别确认/、/tmp、/run、/mnt/c的文件系统类型,并说明哪两个重启后会清空、哪一个是 Windows 的盘。 cat /proc/filesystems里找不到的格式(比如 btrfs),用 root 执行modprobe btrfs后再看一次,解释发生了什么。- 造一个 5 行的文件,用 vim 依次完成:删第 2 行、复制第 1 行到末尾、撤销、反撤销、把第 1 到 3 行大写。每一步都用
:set number?之外的方式验证结果(比如另开终端cat)。 - 建
~/.vimrc并写入本文那 11 行,然后回答三个问题:为什么写 Python 时建议去掉cindent?:set mouse=a之后终端复制文字为什么会变奇怪?怎么确认某条设置最终是哪个文件生效的(提示:verbose set xxx?)? - 打开一个文件后
kill -9掉终端,回来找那个隐藏交换文件,用vim -r列出它,再走一遍恢复流程,最后把它删掉。 - 分别用
--help、man、man -f、man -k、info查同一个命令(建议tar),把五种输出的差别写下来。 - 查
man 5 fstab和man 8 mount,然后解释:/etc/fstab里一行由哪几列组成?为什么改它之前要备份?
参考
- Vim 官方手册:User Manual(含
:help主题索引) - Vim 手册页:
swap-file、E325与恢复流程 - Vim 手册页:
starting(vimrc 与:set选项) - Filesystem Hierarchy Standard 3.0(根目录各目录的权威定义)
- Debian 关于 usrmerge 的说明(
/bin→/usr/bin) - systemd 官方:file-hierarchy(7)(Linux 目录树与 /tmp、/run 的语义)
- Microsoft:FAT 与 NTFS 文件系统比较(各格式上限表)
- Linux 内核文档:ext4 与 xfs
- man-pages 项目(Linux 手册页上游,第 2/3/4/5 节的来源)
- Ubuntu Server 文档:文件与目录结构
- GNU coreutils 手册(
ls、chmod等命令的完整说明)