01 我为什么会困惑?
上一篇把"命令怎么跑起来"讲通了。这一篇回到最基础的东西:文件在哪、我怎么描述它在哪。
我一开始卡住的是这四个问题:
- 一个文件名叫
/home/whb/test/file.txt,为什么这么长?开头那个/和中间那些/,是一回事吗? - 每个目录里都有
.和..,这俩是谁放进去的?能删掉吗? ls和ls -l的输出差了十万八千里,那一列一列到底是什么?- 相对路径我总写错------它到底是相对于谁?
这四个问题的答案,都指向同一个东西:Linux 用一棵树来组织所有文件,而"路径"就是描述"怎么从树根走到那个节点"的路线。
02 先建立一个直觉
2.1 整个文件系统是一棵树
Linux 把磁盘上的所有文件和目录,组织成一棵多叉树:
text
/ ← 根节点,全系统唯一
┌───────────┼───────────┬──────────┐
home etc usr tmp
│ │ │
┌────┴────┐ passwd bin
whb zbc │
│ ls
┌─┴──┬──────┐
test temp file.txt
有两个关键性质,记住它们,后面全通:
- 每个节点只有一个父节点 → 所以从根往下走到任何一个节点,路线都是唯一的
- 根节点
/全系统只有一个 → 所以每条路线都有一个统一的起点
路径的唯一性就是这么来的。 不是为了好看,是为了让"定位一个文件"这件事没有歧义。
2.2 路径就是"从树根到目标的一条路线"
text
/home/whb/test/file.txt
↑ ↑ ↑ ↑ ↑
根 home whb test file.txt
一层一层往下走,每走一层就用一个 / 分开。就这么简单。
2.3 家目录:你一登录就被扔进去的地方
登录之后,默认会站在一个目录里。这个目录叫家目录。
- 普通用户:
/home/用户名 - root:
/root(注意,不是/home/root)
为什么 root 特殊?因为 root 是装系统时就有的唯一用户,系统单独给它安排了地方;普通用户是后来加的,统一放在 /home 下面。
bash
$ ls /home
bss gaobo luojie whb wudu
每建一个新用户,系统就在 /home 下建一个同名目录。 这就是你敲 pwd 会看到 /home/xxx 的原因。
03 正式理解
3.1 树上的两种节点
text
非叶子节点:非空目录(下面还挂着东西)
叶子节点 :空目录,或者普通文件
有个细节值得注意:普通文件一定在叶子上 (它下面挂不了东西),但目录可以在叶子上(空目录)。
3.2 绝对路径和相对路径
这两种路径,区别只有一个问题:你的起点是哪里?
| 绝对路径 | 相对路径 | |
|---|---|---|
| 起点 | 根目录 / |
你当前所在的目录 |
| 写法 | 一定以 / 开头 |
一定不以 / 开头 |
| 特点 | 不随你人在哪而变 | 你挪个位置,它指的东西就变了 |
| 常用在 | 配置文件 | 命令行日常操作 |
bash
# 绝对路径:从 / 一路写到底
[whb@bite-alicloud ~]$ cd /home/whb/test
[whb@bite-alicloud test]$ pwd
/home/whb/test
# 相对路径:从我这儿开始走
[whb@bite-alicloud test]$ cd ../temp/
[whb@bite-alicloud temp]$ pwd
/home/whb/temp
⭐ 为什么配置文件里基本都用绝对路径?
因为配置文件的执行环境不确定。服务可能是开机自启的,也可能被人在任何位置手动拉起,那时它站在哪个目录完全说不准。用相对路径,同一个配置换个位置就失效了。
绝对路径不依赖"你当时在哪",这是它唯一但关键的优势。
3.3 路径里的特殊符号
| 符号 | 意思 | 谁能理解它 |
|---|---|---|
/ |
分隔符(层与层之间);开头的那个是根目录 | 内核 |
. |
当前目录 | 内核 |
.. |
上级目录 | 内核 |
~ |
当前用户的家目录 | 只有 shell |
* |
通配符,匹配任意长度(含零个)的任意字符 | 只有 shell |
- |
上一次所在的目录 | 只有 cd 这个命令 |
⚠️ 这张表最后三行是重点。 它们不是"路径语法",是别人帮你做的手脚------第 6.7 节会讲清楚谁干的。
最容易搞混的地方:/ 其实是两个东西
text
/home/zjy/test
↑ ↑
根目录 分隔符
开头的 / 是根目录(一个节点),中间的 / 是分隔符(一个标点)。 它们长得一样,身份完全不同。
一句话记:
一个路径里,第一个
/表示"从根开始",后面的/表示"再下一层"。
所以 / 单独出现时,它表示的就是根目录本身:
bash
[whb@bite-alicloud ~]$ cd /
[whb@bite-alicloud /]$ pwd
/
💡 顺便记一个跨平台的坑:Linux 用正斜杠 /,Windows 用反斜杠 \。
以前的记法挺形象:"窗户(Windows)向上看,企鹅(Linux)往下看。"写代码时路径写错方向,编译器不会报错,文件就是打不开。
结尾到底要不要加 /?
以 /home/whb/test 为例:
| 位置 | 要不要 | 原因 |
|---|---|---|
| 开头 | 必须有(除非你就在根目录) | 表示从根开始 |
| 中间 | 必须有 | 分隔层级 |
| 结尾 | 建议不加 | 加了通常也能用,但容易和"这是个目录"混淆 |
3.4 . 和 .. 到底是谁放进去的
⭐ 这是这一节最值得搞明白的点。
bash
[whb@bite-alicloud test]$ ls -a
. .. dir file.txt
它们不是隐藏文件,是每个目录天生自带的两个目录项:
.指向自己..指向父目录
所以哪怕你新建一个空目录,里面也立刻有这两个东西:
bash
[whb@bite-alicloud test]$ tree mydir/
mydir/
0 directories, 0 files # 用 tree 看不到,但 ls -a 能看到
它们有什么用? 三个:
cd ..能回上级,靠它- 相对路径能写出来(
./a.out),靠它 - 它让"每个目录都知道自己是谁、爸爸是谁"------这是树结构能成立的基础
🔬 一个更深的线索:这两个目录项,能从 ls -l 的"链接数"那一列看出来。
bash
[whb@bite-alicloud test]$ ls -a -l
total 12
drwxrwxr-x 3 whb whb 4096 Jan 11 15:01 .
drwx------ 22 whb whb 4096 Jan 11 14:21 ..
drwxrwxr-x 2 whb whb 4096 Jan 11 14:22 dir
-rw-rw-r-- 1 whb whb 0 Jan 11 14:22 file.txt
看第二列:
| 条目 | 链接数 | 为什么是这个数 |
|---|---|---|
.(当前目录 test) |
3 | = 2 + 子目录数(这里有 1 个 dir) |
dir(空目录) |
2 | 空目录没有子目录,2 + 0 |
..(家目录 whb) |
22 | = 2 + 家目录下的子目录数(20 个) |
file.txt(普通文件) |
1 | 普通文件只有自己这一个名字 |
🔬 规律:目录的链接数 = 2 + 它下面子目录的个数。
那个 2 是固定的:
- 1 来自自己目录里的
. - 1 来自它自己在父目录里的名字
每多一个子目录,就多一个 .. 指回来,所以加 1。
💡 理解这一条的好处 :以后看到某个目录链接数异常大,就知道它下面塞了很多子目录------这是不用 ls 就能粗略判断目录规模的技巧。
04 它到底是怎么工作的?
4.1 内核怎么顺着路径一层层找
🔬 你写 /home/zjy/test/file.txt,内核并不是"一下子找到"的,而是一层一层剥:
text
打开 /home/zjy/test/file.txt
1. 从根目录 / 开始(全系统唯一确定的起点)
2. 在 / 里找名为 home 的条目 → 拿到 home 的 inode
3. 在 home 里找名为 zjy 的条目 → 拿到 whb 的 inode
4. 在 whb 里找名为 test 的条目 → 拿到 test 的 inode
5. 在 test 里找名为 file.txt → 拿到目标 inode
6. 检查权限 → 打开文件,返回 fd
每一步都是"在上一层的目录里查一个名字"。
这就解释了三件事:
- 为什么中间任何一层不存在,报错都是
No such file or directory------ 因为走到那一步就断了 - 为什么目录少了
x权限就进不去 ------ 因为"在目录里查名字"这个动作需要x(权限那篇讲过) - 为什么路径越长越慢 ------ 层数越多,要查的表越多
💡 "目录"的本质是什么?就是一张表:名字 → inode 号。
你在里面加一个文件,就是往这张表里加一行;删一个文件,就是删掉一行。
4.2 相对路径是相对于谁?
⭐ 相对于当前工作目录(current working directory,简称 cwd)。
这里有个必须说清楚的点:cwd 是"每个进程各有一份"的。
- 你所在的 shell 有一个 cwd
- 你跑起来的每个程序,也各有一个 cwd
- 程序启动时,它的 cwd 是从父进程继承来的(也就是继承你 shell 当时所在的位置)
这就顺手解释了一个经典现象:你在终端里 cd 到某个目录再启动程序,程序里的相对路径就是从那个目录算起的。
4.3 pwd 凭什么知道我在哪
💡 pwd = Print Working Directory,输出的就是当前 shell 的 cwd。
但这里有个细节:cwd 在内核里其实是以 inode 的形式记着的 ,不是一个字符串。要把 inode 反推成 /home/zjy/test 这样的字符串,得顺着 .. 一路爬到根,再从根往下列。
bash
$ pwd # -L,逻辑路径:直接用 shell 记着的 $PWD
$ pwd -P # 物理路径:真的从 inode 爬一遍,符号链接会被解开
💡 平时这两个输出一样。但如果你 cd 进了一个软链接指向的目录,两者就会不一样------前者保留你走进去的那条"软链接路线",后者给你真实位置。
4.4 cd 为什么必须是 shell 内建的
⭐ 这一点上一篇提过,这里补齐原因。
cd 改的是"当前进程的工作目录"。
- 如果
cd是个外部程序,shell 就得 fork 一个子进程来跑它 - 子进程改了自己的 cwd,但父进程(shell)的 cwd 一点没变
- 结果就是:你
cd完了,一回头还在原地
所以 cd 只能由 shell 自己执行。
🔬 这条规律在整个 Linux 里都成立:子进程改不了父进程的状态。
权限那篇里 su 为什么是"套一层"而不是"变身",原因也是这个。
4.5 ls 到底在读什么
💡 ls 做的事,本质上是:向内核请求"把这个目录里有哪些名字给我",然后格式化打印。
所以:
| 你敲的 | 本质 |
|---|---|
ls |
读目录这张表,打印名字 |
ls -a |
读表,包括 . .. 和以 . 开头的项 |
ls -l |
读表,然后对每一项再查一次属性(inode 里的字段) |
ls -i |
读表,顺便把每项对应的 inode 号打出来 |
🔬 注意 -l 比裸 ls 慢:因为它不只是读目录,还要为每一项去取 inode 属性。项一多就慢。
4.6 三个时间:atime / mtime / ctime
🔬 stat 会打出三个时间,初看很像,其实管三件不同的事:
| 简称 | 全称 | 什么时候变 |
|---|---|---|
| atime | Access time | 读了这个文件内容 |
| mtime | Modify time | 改了文件内容 |
| ctime | Change time | 改了文件的属性 / 元数据(权限、所有者、大小......) |
⚠️ 最容易误解的一点:ctime 不是"创建时间"。
它是"inode 状态最后一次变化的时间"。真正的创建时间叫 Birth ,而且很多文件系统压根不记录它------你看课件和参考博客里的 stat 输出,Birth: 那一行都是 -。
理解这三个时间的关系,抓住一句话就行:
改内容,会连带改 mtime 和 ctime;改属性,只改 ctime。
为什么?因为:
- 改内容 → 内容变了(mtime 变)+ inode 里的"大小 / 块数"也变了(ctime 变)
- 改权限 → 内容没动(mtime 不变),但 inode 变了(ctime 变)
💡 这条规律在下一篇讲 touch 时会再撞上------touch -a 之后你会看到 atime 变、ctime 也跟着变,用上面这条就能解释。
05 把几个概念串起来
text
/
│ ← 路径解析的起点,全系统唯一
┌───────────┼──────────┐
home etc usr
│ │
zjy bin
│
test ◄──── 你的 shell 的 cwd 在这
│
file.txt ◄──── 相对路径 "file.txt" 指的就是它
路径解析:/ → home → zjy → test → file.txt
每一步都是"在上一层目录里查一个名字"
同一个文件的三种写法:
绝对路径 /home/whb/test/file.txt
相对路径 file.txt (因为 cwd 就在 test)
家目录写法 ~/test/file.txt (~ 由 shell 展开)
一句话总结:
树结构决定了路径唯一,cwd 决定了相对路径从哪算起,而"查名字"这个动作是内核一层层做的。
06 实际命令
6.1 pwd
bash
$ pwd
/home/whb/test
💡 没有常用选项(-L / -P 前面讲过)。这是最没有意外的一个命令,但它是你迷路时第一个该敲的。
6.2 cd ------ 四个最常用的写法
bash
# 1. 回上级目录
[whb@bite-alicloud ~]$ cd ..
[whb@bite-alicloud home]$ pwd
/home
# 2. 回家目录
[whb@bite-alicloud test]$ cd ~
[whb@bite-alicloud ~]$ pwd
/home/whb
# 3. 回到上一次待过的目录
[whb@bite-alicloud ~]$ cd /
[whb@bite-alicloud /]$ cd -
/home/whb
# 4. 用绝对路径直接跳过去
[whb@bite-alicloud home]$ cd /home/whb/test
⭐ cd - 值得单独说。
它不是"退回上一级",是"回到我上一次待过的目录"。这两个完全不是一回事:
| 命令 | 去哪 |
|---|---|
cd .. |
上一级目录(父目录) |
cd - |
上一次所在的目录(可能是任何地方) |
cd - 还有个细节:它会在两个目录之间来回弹。
bash
[whb@bite-alicloud ~]$ pwd
/home/whb
[whb@bite-alicloud ~]$ cd /
[whb@bite-alicloud /]$ cd - # 回到 /home/whb
/home/whb
[whb@bite-alicloud ~]$ cd - # 又跳回 /
/
[whb@bite-alicloud /]$ cd - # 再跳回来
/home/whb
它是怎么做到的?shell 一直记着你的上一个目录(存在 $OLDPWD 里) ,所以本质上是 cd $OLDPWD,外加把目标路径打印出来。
💡 改代码时的实用场景 :你经常要在"项目目录"和"日志目录"之间反复横跳,cd - 比敲一长串 cd ../../.. 快得多。
6.3 ls 选项速查
| 选项 | 干什么 | 什么时候用 |
|---|---|---|
-a |
显示所有条目,包括 . .. 和以 . 开头的隐藏项 |
找配置文件(.bashrc、.gitignore 都是隐藏的) |
-l |
显示详细信息 | 最常用 |
-d |
把目录当成一个整体显示,而不是列出它的内容 | ls -ld /home |
-i |
显示 inode 号 | 理解硬链接、排查同名文件 |
-n |
用数字 UID / GID 代替用户名和组名 | 用户名显示不出来时 |
-k |
以 KB 为单位显示大小 | 看大文件 |
-F |
每个名字后面加一个符号标明类型 | 快速区分目录和文件 |
-r |
反向排序 | |
-t |
按时间排序 | 找最近改过的文件 |
-s |
在名字前面输出大小 | 找目录下最大的文件 |
-R |
递归列出所有子目录 | |
-1 |
一行只输出一个 | 给别的命令消费 |
-F 加的那些符号是什么意思:
| 后缀 | 类型 |
|---|---|
/ |
目录 |
* |
可执行的普通文件 |
@ |
符号链接 |
| ` | ` |
= |
套接字 |
选项可以连写
bash
ls -a -l # 分开写
ls -al # 连写
ls -la # 顺序无所谓
这三种完全等价。惯例是先写 -l 这类"主选项",再写修饰性的。课件里那条最长的:
bash
[whb@bite-alicloud test]$ ls -alFn
拆开就是 -a(全都要看)+ -l(详细信息)+ -F(标类型)+ -n(数字 UID)。
实例对照
bash
[whb@bite-alicloud test]$ ls
dir file.txt
[whb@bite-alicloud test]$ ls -a
. .. dir file.txt
[whb@bite-alicloud test]$ ls -a -l
total 12
drwxrwxr-x 3 whb whb 4096 Jan 11 15:01 .
drwx------ 22 whb whb 4096 Jan 11 14:21 ..
drwxrwxr-x 2 whb whb 4096 Jan 11 14:22 dir
-rw-rw-r-- 1 whb whb 0 Jan 11 14:22 file.txt
[whb@bite-alicloud test]$ ls -a -l -F
total 12
drwxrwxr-x 3 whb whb 4096 Jan 11 15:01 ./
drwx------ 22 whb whb 4096 Jan 11 14:21 ../
drwxrwxr-x 2 whb whb 4096 Jan 11 14:22 dir/
-rw-rw-r-- 1 whb whb 0 Jan 11 14:22 file.txt
[whb@bite-alicloud test]$ ls -alFn
total 12
drwxrwxr-x 3 1003 1003 4096 Jan 11 15:01 ./
drwx------ 22 1003 1003 4096 Jan 11 14:21 ../
drwxrwxr-x 2 1003 1003 4096 Jan 11 14:22 dir/
-rw-rw-r-- 1 1003 1003 0 Jan 11 14:22 file.txt
对着这四条看变化,比背选项表有用得多:
-a让...冒出来了-l让每行信息量暴涨-F让目录后面多了/-n让whb变成了1003
⚠️ 顺便注意:-l 输出开头那一行 total 12 ,是这个目录占用的磁盘块总量(单位通常是 1KB 的块),不是文件个数,也不是总字节数。很多人第一次看到会猜错。
6.4 看懂 ls -l 的每一列
text
-rw-rw-r-- 1 whb whb 0 Jan 11 14:22 file.txt
↑↑↑ ↑↑↑ ↑↑↑ ↑ ↑ ↑ ↑ ↑ ↑
││ │ │ │ │ │ │ │ └─ 文件名
││ │ │ │ │ │ │ └─ 最后修改时间(mtime)
││ │ │ │ │ │ └─ 大小(字节)
││ │ │ │ │ └─ 所属组
││ │ │ │ └─ 所有者
││ │ │ └─ 链接数(见 3.4)
││ │ └─ 其他人的权限
││ └─ 同组的权限
│└─ 所有者的权限
└─ 文件类型
⭐ 记住这张图,ls -l 就不再是天书。
6.5 stat ------ 比 ls -l 更全的属性
bash
[whb@bite-alicloud test]$ stat newFile.txt
File: 'newFile.txt'
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: fd01h/64769d Inode: 1844719 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1003/ whb) Gid: ( 1003/ whb)
Access: 2023-01-11 15:06:45.832787572 +0800
Modify: 2023-01-11 15:06:45.832787572 +0800
Change: 2023-01-11 15:06:45.832787572 +0800
Birth: -
逐行读:
| 字段 | 什么意思 |
|---|---|
Size |
文件内容大小(字节) |
Blocks |
实际占了多少个 512B 的块 |
IO Block |
文件系统的块大小(这里是 4096) |
regular empty file |
文件类型描述 |
Device |
文件所在设备(十六进制 / 十进制两种写法) |
Inode |
inode 号 |
Links |
硬链接数 |
Access (0664/-rw-rw-r--) |
权限,数字和字符两种写法都给了 |
Uid / Gid |
所有者和组,括号里是数字 ID 和名字 |
Access / Modify / Change |
三个时间(见 4.6) |
Birth |
创建时间,这里没记录,所以是 - |
💡 stat 比 ls -l 强在哪? 两个地方:
- 给你精确到纳秒的时间
- 给你 inode 号、块数、设备号 这些
ls -l根本不显示的东西
6.6 file 和 tree
bash
$ file test.c
test.c: C source, ASCII text
$ file /dev/pts/0
/dev/pts/0: character special (136/0)
file 是干嘛的? 它不看你文件名的后缀(test.txt 完全可以是一个可执行程序),而是真的去读文件开头那几个字节,根据"魔数"判断这是什么。
💡 这就是它比"看后缀"靠谱的原因。从别人那儿拿到一个不知道是什么的文件,file 一下比猜半天强。
bash
$ tree path1
path1
└── path2
└── path3
└── path4
tree 是递归展示目录结构 ,复习时用它比 ls -R 直观得多。
💡 装法:root 直接 yum install -y tree;普通用户要 sudo yum install -y tree(权限那篇讲过为什么)。
6.7 通配符 * ------ 其实是 shell 干的
🔬 这一节是我觉得最值得记住的一个反转。
bash
[whb@bite-alicloud test]$ cp *.txt dir
你以为 cp 收到了 *.txt 这个字符串吗?
没有。 真实过程是:
text
1. shell 看到 *.txt
2. shell 去当前目录里翻一圈,把所有以 .txt 结尾的名字找出来
3. shell 把它们展开成一个个真实文件名,再交给 cp
4. cp 收到的其实是 "a.txt b.txt c.txt" 这样的东西
------ cp 从来没见过那个星号
⭐ 所以"通配符"是 shell 的功能,不是命令的功能。
这一条能解释好几个现象:
- 为什么在 C 程序里
argv永远收不到*------ shell 在程序启动前就替换掉了(第 08 节展开) - 为什么
rm -rf *这么危险 ------ 它不是"模糊删除",而是先展开成一长串真实文件名,再一次性全删 - 为什么
~也是同理 ------ 你写ls ~,shell 先把它换成/home/whb,ls收到的是替换后的路径
常用写法:
bash
ls *.txt # 所有以 .txt 结尾的
ls mulu* # 所有以 mulu 开头的
ls *test* # 名字中间带 test 的
⚠️ 一个必须知道的坑:* 匹配不到以 . 开头的隐藏文件。
bash
$ rm -rf * # 删掉当前目录所有内容,但 .bashrc 这类隐藏文件还在
这不是 bug,是故意设计 ------防止你一个 * 就把配置全删了。隐藏文件要单独处理。
07 常见误区
| 误区 | 真相 |
|---|---|
路径里所有 / 都一样 |
开头那个是根目录,后面的是分隔符 |
. 是"隐藏文件"的意思 |
. 是当前目录 。ls -a 里之所以一堆,是因为文件名以 . 开头才算隐藏 |
cd .. 和 cd - 差不多 |
.. 是父目录,- 是上一次待过的目录,完全不是一回事 |
| 相对路径相对于"我上次待的目录" | 相对于当前工作目录(cwd),跟历史无关 |
ls -l 里的 total 12 是文件总数 |
是这个目录占的磁盘块总量,不是文件个数 |
stat 里的 Change 是创建时间 |
是inode 状态最后变化的时间 。创建时间是 Birth,很多文件系统不记录 |
* 是 ls / rm 的功能 |
是 shell 的功能,在命令执行前就已经展开了 |
* 能匹配所有文件 |
匹配不到以 . 开头的隐藏文件 |
| Linux 和 Windows 的路径斜杠通用 | Linux 用 /,Windows 用 \,写代码时最常见的翻车点 |
~ 到处都能用 |
只有 shell 认它 。程序里写 fopen("~/a.txt") 是打不开的 |
ls -l 目录 和 ls -ld 目录 一样 |
-l 列的是目录里的东西 ,-ld 列的是目录自己 |
目录删掉就没了 . 和 .. |
它是目录结构的一部分,删不掉------你能删的是以 . 开头的隐藏文件,不是这两个目录项 |
08 和 C/C++ 联系起来
8.1 程序里的相对路径,是从哪算起的
c
fopen("data.txt", "r");
这个 data.txt 从哪找?从进程的当前工作目录(cwd)算起 ,而进程启动时的 cwd 是从父进程继承的------也就是你敲命令时 shell 所在的位置。
bash
$ cd /home/whb/project
$ ./a.out # 这里的 "data.txt" 指 /home/whb/project/data.txt
$ cd /
$ /home/whb/project/a.out # 同一个程序,现在 "data.txt" 指 /data.txt(大概率打不开)
💡 这就是"我明明编译好了,怎么换个目录就找不到文件"的根因。 以后遇到先想 cwd,别先怀疑代码。
8.2 ~ 和 * 不是内核认的
这一条 6.7 讲过,放到 C 里更明显:
c
fopen("~/data.txt", "r"); // ❌ 打不开。~ 是 shell 的展开,内核不认识
fopen("/home/whb/data.txt", "r"); // ✅
同样:
c
int main(int argc, char *argv[]) {
// 你敲 ./a.out *.txt,argv[1] 收到的不是 "*.txt"
// 而是 shell 已经展开好的一堆文件名
}
⭐ 记住这句话:~、*、$变量 都是 shell 层的语法,程序永远看不到它们原本的样子。
8.3 chdir 和 getcwd
程序里也能"换目录",靠系统调用:
c
#include <unistd.h>
chdir("/tmp"); // 改变当前进程的 cwd,对应 shell 里的 cd
char buf[4096];
getcwd(buf, sizeof(buf)); // 拿到当前 cwd 的字符串,对应 shell 里的 pwd
💡 注意 chdir 只影响当前进程自己 。你写个程序调 chdir,跑完之后你的 shell 还站在原地。
------这就是 4.4 那条规律的实证:子进程改不了父进程的状态 ,也是 cd 必须内建的原因。
8.4 路径有长度上限
c
#include <limits.h>
PATH_MAX // Linux 上通常是 4096
路径不是能无限长的。写代码拼接路径时别用固定大小的小数组,容易溢出------用 snprintf 并检查它的返回值。
09 最后真正记住什么?
text
【定位一个文件,靠的是四个概念】
树(结构) 路径(描述) cwd(起点) 内核(执行)
│ │ │ │
│ 每个节点只有 │ 绝对:从 / 开始 │ 每个进程各一份 │ 一层层查名字
│ 一个父节点 │ 相对:从 cwd 开始 │ 从父进程继承 │ 每层都查权限
│ │ │ │
└───────────────────┴────────────────────┴───────────────────┘
│
▼
路径具有唯一性
这部分读完,脑子里留下四个模型就够了:
- 文件系统是一棵树 ,每个节点只有一个父节点,所以路径能唯一定位。
.和..是每个目录天生自带的两个目录项,也是树结构能成立的基础------它们能从ls -l的链接数那一列被验证。 - 绝对路径和相对路径的区别只有一个问题:起点是
/还是 cwd。 配置文件用绝对路径,是因为它不能依赖"你当时在哪"。 - cwd 是每个进程各一份、从父进程继承的。 这就是
cd必须内建、程序换目录不影响 shell、程序换个位置就跑不起来的原因。 ~、*、$变量都是 shell 干的活,不是内核的语法。程序永远看不到它们原本的样子。
下一篇 :创建、删除、复制、移动文件,以及把文件内容打出来看(touch / mkdir / rm / rmdir / cp / mv / cat / more / less / head / tail)。