阶段三 · 文件与系统命令

文件操作

1. mkdir: 创建目录

$ mkdir new-project          # 创建一个目录
$ mkdir -p a/b/c/d # 递归创建中间的父目录(最常用)
$ mkdir -p project/{src,test,docs} # 花括号展开:一次创建多个目录
$ ls project/
docs/ src/ test/

-p 是几乎每次都该用的选项. 不带 -p 时,如果父目录不存在会报错; 带 -p 则自动创建缺失的中间目录,如果目录已存在也不报错--省心.

2. touch: 创建空文件 / 更新时间戳

$ touch README.md            # 文件不存在 → 创建空文件
$ touch existing.txt # 文件已存在 → 更新修改时间为 "现在"
$ touch -t 202501010830 a.txt # 设置指定的时间戳(格式:[[CC]YY]MMDDhhmm)

为什么叫 touch? 因为它的本意是 "摸一下" 文件--只更新它的访问和修改时间戳,不修改内容. 创建空文件只是副业.

3. cp: 复制文件与目录

3.1 基础复制

$ cp source.txt dest.txt     # 复制文件
$ cp a.txt b.txt c.txt dir/ # 复制多个文件到目录
$ cp -i source.txt dest.txt # 如果 dest.txt 已存在,提示确认
$ cp -n source.txt dest.txt # 如果 dest.txt 已存在,跳过(不覆盖)

macOS 的 cp 和 Linux 的 cp 默认行为不同: macOS 默认不提示覆盖(源自 BSD),Linux 默认也不提示. 如果从 Linux 迁移过来,可能会以为 cp 会问 "是否覆盖"--不会的,它直接覆盖.

3.2 复制目录: 必须加 -r

$ cp -r project/ backup/      # 递归复制整个目录(最常用)
$ cp -a project/ backup/ # 归档模式:保留权限, 时间戳, 符号链接

cp -rcp -a 的区别: -r 简单递归复制,-a(archive)额外保留原文件的权限, 所有者, 时间戳,不跟随符号链接. 做备份用 -a.

3.3 常见陷阱

陷阱一: 源目录带不带 /,结果完全不同. 当目标目录已存在时:

# 假设 project/ 里有 a.txt b.txt c.txt
# 假设 backup/ 已存在,里面有 old.txt

# 写法一:不带斜杠 → 把整个 project 目录塞进 backup
$ cp -r project backup
$ ls backup/
old.txt project/ ← project 被整体放入
$ ls backup/project/
a.txt b.txt c.txt

# 写法二:带斜杠 → 把 project 的内容倒进 backup
$ cp -r project/ backup
$ ls backup/
old.txt a.txt b.txt c.txt ← 文件直接进入 backup

陷阱二: cp -r project/ backup 不是 "清空 backup 再放入新内容",而是逐一拷贝. 只有同名文件才会被覆盖(静默!),不同名的文件不受影响--比如上面的 old.txt 在写法二中完好无损. 用 cp -i 可以在覆盖前获得确认.

4. mv: 移动与重命名

Linux 中,移动文件和重命名文件是同一个命令--因为底层是同一个系统调用 rename().

$ mv old.txt new.txt          # 重命名
$ mv file.txt dir/ # 移动到另一个目录
$ mv a.txt b.txt dir/ # 移动多个文件到目录

# 批量重命名技巧(循环)
$ for f in *.jpg; do mv "$f" "${f/.jpg/.webp}"; done # .jpg → .webp

mv 和 cp 一样的覆盖陷阱: 目标文件如果已存在,静默覆盖. 用 mv -i 开启确认,mv -n 不覆盖已存在文件.

5. rm: 删除文件与目录

rm 没有回收站

命令行中的 rm 不经过废纸篓--删了就没了. macOS 的 Finder 删除会进废纸篓,但 rm 直接解除文件在磁盘上的链接. 没有 Ctrl+Z,没有 "撤销删除".

5.1 基础删除

$ rm file.txt                 # 删除单个文件
$ rm -i *.log # 每个文件都提示确认(i = interactive)
$ rm -f lockfile # 强制删除,不提示,不报错即使文件不存在
$ rm -r project/ # 递归删除整个目录(r = recursive)
$ rm -rf node_modules/ # 递归 + 强制 = 最危险的组合

5.2 安全实践

# 好习惯:删之前先 ls 看一下
$ ls *.log # 先看这些文件是什么
$ rm *.log # 确认无误再删

# 用 trash 命令代替 rm(macOS 需要 brew install trash)
$ trash file.txt # 进废纸篓,可以恢复

# dry-run 思维:用 echo 预览会删什么
$ echo rm -rf node_modules/ # 先看看命令长什么样
# 确认无误后去掉 echo
$ rm -rf node_modules/

陷阱一: rm * .txt -- *.txt 之间的空格. 这行的意思是 "删除当前目录下所有文件,然后删除 .txt 文件". 想写的可能是 rm *.txt(无空格).

陷阱二: rm -rf / -- 经典的 "核弹命令". 现代 macOS 和 Linux 需要加 --no-preserve-root 才会真的执行,但别因此放松警惕. 同理 rm -rf ~ 会清空整个家目录.

陷阱三: rm -rf "$DIR/*" -- 如果 $DIR 为空(变量没设置),这行变成 rm -rf "/*"(删除根目录下所有文件). 用 rm -rf "${DIR:?}"/*--如果 DIR 为空,命令直接报错不执行.

6. ln: 硬链接与符号链接

这是文件操作中最容易被误解的概念. Linux 有两种 "链接"--它们的行为截然不同.

硬链接是同一个文件实体的另一个名字. 它不是 "快捷方式"--它就是文件本身,只是多了一个目录入口指向它.

$ echo "hello" > original.txt
$ ln original.txt hardlink.txt # 创建硬链接

$ ls -l
-rw-r--r-- 2 yutong staff 6 Jun 12 10:00 hardlink.txt
-rw-r--r-- 2 yutong staff 6 Jun 12 10:00 original.txt
# ↑ 链接数 = 2(两个目录入口指向同一个 inode)

$ echo "world" >> hardlink.txt # 修改 hardlink
$ cat original.txt # original 也变了
hello
world
$ rm original.txt # 删除 original
$ cat hardlink.txt # hardlink 还在!
hello
world # 链接数降到 1,数据仍然存在

硬链接的限制:

  • 不能跨文件系统--硬链接指向 inode,不同分区/磁盘有独立的 inode 空间
  • 不能给目录创建--防止形成循环(macOS 的 Time Machine 是少数例外,用了特殊 API)
  • 删除最后一个硬链接后数据才被释放--因为链接数降为 0

符号链接是一个独立的特殊文件,内容是目标路径的字符串. 它就像 "快捷方式"--指向目标,但本身不是目标.

$ ln -s original.txt symlink.txt   # -s 表示 symbolic
$ ls -l
lrwxr-xr-x 1 yutong staff 12 Jun 12 10:00 symlink.txt -> original.txt
# ↑ l = 符号链接 ↑ 箭头显示指向谁

$ cat symlink.txt # 读 symlink → 自动读到 original 的内容
hello
world
$ rm original.txt # 删掉目标
$ cat symlink.txt # symlink 还在,但指向的目标没了
cat: symlink.txt: No such file or directory # 断掉的链接

硬链接 = 一个人有两个名字--喊哪个名字,同一个人都会回头. 删掉一个名字,人还在.
符号链接 = 路牌写着 "餐厅 → 往前 100 米". 路牌本身不是餐厅. 餐厅拆了,路牌还在但指向一片废墟.

6.3 对比总结

硬链接 符号链接
本质 同一个 inode 的另一个名字 独立文件,内容是路径字符串
跨文件系统 不能 可以
链接目录 不能 可以
删掉原始文件后 链接仍能访问数据 链接断裂(dangling symlink)
ls -l 中显示 普通文件,链接数 > 1 l 开头,有 箭头
创建命令 ln target link ln -s target link

日常使用建议: 99% 的场景用符号链接(ln -s). 硬链接主要用于特定场景--比如文件系统快照, 备份系统中的去重. 日常用到的 brew link, npm link 等都是符号链接.

快速回顾

  • mkdir -p 递归创建 + 不报错,日常必用; 花括号 {a,b,c} 一次创建多个目录
  • touch 的本职是更新时间戳,副业才是创建空文件
  • cp -r 复制目录; cp -a 归档模式保留权限和时间戳; cp 默认静默覆盖,用 -i 确认
  • mv 移动 = 重命名,同一个系统调用
  • rm 不经过回收站,删了就没了. 先 ls 看再 rm,或者用 trash 命令
  • 硬链接 = 同一个 inode 的另一个名字,不能跨分区,不能链接目录,删掉原始文件不影响数据
  • 符号链接 = 独立文件存路径字符串,可以跨分区,可以链接目录,原始文件删了就断裂
  • 日常用 ln -s(符号链接); 硬链接用于去重和快照场景

动手练习

  1. 创建项目骨架: 用 mkdir -p project/{src,test,docs} 一次性创建项目骨架
  2. 对比 cp 选项: 创建文件后,用 cp -a 复制它和用 cp 复制它,对比 ls -l 中时间戳的差异
  3. mv -n 保护: 用 mv -n 尝试覆盖一个已存在的文件,观察它的保护效果
  4. 硬链接实验: 创建一个测试文件,用 ln 创建硬链接,然后 ls -l 观察链接数. 删掉原始文件,硬链接还在吗?
  5. 断裂的符号链接: 用 ln -s 创建一个符号链接指向不存在的文件,然后用 ls -l 看--终端怎么标示断裂的链接?
  6. 系统中的符号链接: 运行 ls -l /binls -l /usr/bin--有没有看到 l 开头的符号链接? 找出来并看它们指向哪