命令行里最常见的困惑,往往不是“这个命令会不会用”,而是“它到底在哪里工作”。同样一条 ls,换个目录就会看到不同内容;同样一个 projects,从不同位置出发,可能指向不同目录,也可能什么都找不到。
我们先把命令表放到一边,建立一张能长期使用的地图:Linux 向进程展示一棵从 / 开始的目录树;每个进程都有自己的当前工作目录;路径则是从某个起点沿树查找目录项的指令。想清楚这三件事,pwd、cd 和 ls 就不再是三个孤立的命令。

一张图记住主线:pwd 回答“我在哪里”,ls 回答“这里有什么”,cd 改变下一次查找的起点。
在日常使用中,你可以先把 Linux 的可见文件空间想成一棵树。树的起点是根目录 /,下面有 etc、home、usr、var 等目录项,每个目录又可以继续包含子目录和文件。路径 /home/learner/projects 的意思就是:从 / 开始,先在根目录中找 home,再在其中找 learner,最后找 projects。
这棵树是进程看到的命名空间,不等于“只有一块磁盘”。一个系统可以有多块磁盘、内存文件系统、网络文件系统和内核生成的虚拟文件系统。它们通过挂载点接到目录树的某个位置。例如,移动存储可以挂载在 /mnt/usb;沿路径进入这个目录时,你看到的内容来自被接入的文件系统。

路径描述树上的位置;挂载决定该位置背后的内容来自哪个文件系统。
某个进程究竟能看到哪些挂载,还会受挂载命名空间影响。容器中的进程和容器外的进程可能看到不同的挂载集合,但它们仍然都是从各自可见的 / 开始解析绝对路径。
目录是一类能容纳“名称 → 文件对象”关系的文件。我们在目录里看到的名字,可以理解为目录项。路径是一串由 / 分隔的名称,系统要沿着这串名称逐级查找。文件系统则负责组织和保存这些对象,它可以在某个挂载点加入整棵可见树。
这个区分能解释一个容易误判的现象:路径 /mnt/usb 存在,不代表那块存储一定已经挂载;反过来,一个底层文件系统也可在不同挂载点可见。日常导航处理的是路径,不应仅凭目录名推断背后的存储设备。
命令不给绝对路径时,必须有一个“从哪里开始找”的答案。这个答案就是当前工作目录(current working directory)。它是进程状态的一部分,不是全系统只有一个的公共指针。
你在两个终端窗口中分别运行 cd,两个 shell 可以停在不同目录。shell 启动一个子进程时,子进程通常继承当时的工作目录,所以 ls、cat、编辑器和编译器都可以用相对路径找到同一工作树中的文件。
pwd 输出当前工作目录的绝对路径。命令提示符有时会显示目录名,但提示符可以被定制、缩短,甚至完全不显示位置。所以在要执行批量操作、检查相对路径或阅读别人的复现步骤时,直接运行 pwd 比猜提示符更可靠。
$ pwd
/home/learner/projects遇到符号链接时,“当初怎样走进来”和“真实目录在哪里”可以得到两个不同的字符串。显式写选项可以避免不同 shell 或实现的默认差异:
pwd -L 显示逻辑路径,在条件满足时保留你经过的符号链接名称。pwd -P 显示物理路径,解析路径中的符号链接。
两条输出可以定位到同一目录对象,只是分别强调导航轨迹和解析结果。
cd 改变的是当前 shell 自己的工作目录。如果它只是一个普通外部程序,shell 会启动子进程运行它;子进程可以改变自己的工作目录,退出后却无法把父 shell 的状态一起改掉。因此,cd 由 shell 直接执行。
cd /var/log # 使用绝对路径
cd projects # 使用相对路径
cd # 回到当前用户的主目录
cd - # 切换到前一个工作目录cd - 的实用之处是在两处目录之间往返。Bash 会用 OLDPWD 记住前一个位置,切换时还会把目标路径输出。如果当前会话还没有可用的前一位置,cd - 就会失败;它不是一个名为 - 的目录。
路径不是一个“完整地址”才能成立。它可以从根目录开始,也可以借用当前工作目录,还可以先由 shell 把 ~ 等简写展开,再交给 cd 或其他命令。看到一条路径时,先判断起点,再从左到右处理每个分量。

选择路径写法时,要考虑它会在哪个起点执行,而不是只比较字符数。
绝对路径以 / 开头。它明确要从根目录开始查找,所以不会因当前工作目录不同而改变含义。在命令、文档和配置中需要指向稳定系统位置时,绝对路径很清楚。
cd /home/learner/projects
ls /var/log相对路径不以 / 开头。它通常从当前工作目录出发。如果已经在 /home/learner,cd projects 就会尝试进入 /home/learner/projects。相对路径适合表达项目内部的关系:整个项目搬到别处后,assets/logo.svg 仍可以表示从项目根到资源文件的相对关系。
. 表示当前目录,.. 表示父目录。它们可以像其他路径分量一样组合:
cd . # 仍在当前目录
cd .. # 进入父目录
cd ../bin # 先回到父目录,再进入其中的 bin
cd ./scripts # 显式表示当前目录中的 scripts在 /usr/lib 中,../bin 的解析步骤是 /usr/lib → /usr → /usr/bin。当路径不以 / 开头时,./scripts 和 scripts 通常指向同一个子目录;但 ./ 在处理以短横线开头的名称时很有用,因为 ./-draft 明确是路径,不会在字面上以 - 开头。
根目录是树的顶点。在 / 中使用 ..,结果仍然是 /,不会跳出这棵树。
~ 不是根目录下的真实目录名。在 Bash 中,一个未引用且位于单词开头的 ~ 会先被 shell 展开。单独的 ~ 通常展开为 $HOME,~/projects 则变成“主目录路径 + /projects”。
$ printf '<%s>\n' ~
</home/learner>
$ printf '<%s>\n' "~"
<~>第二条命令中,引号阻止了波浪号展开,所以输出的是字面量 ~。这也说明,路径交给 cd 之前,shell 已经可能对命令行做了处理。
- 在 cd - 中也是 shell 内建功能接受的特殊操作数,表示 OLDPWD 记录的前一工作目录。它不像 . 和 .. 那样是通用的特殊目录项,也不应在其他命令中当成“前一目录”使用。
ls 列出文件信息。不给操作数时,它按当前目录 . 处理;给它一个目录路径,默认列出该目录的内容;给它一个普通文件路径,则列出该文件本身的信息。
ls # 列出当前目录内容
ls /var/log # 列出 /var/log 的内容
ls README.md # 列出 README.md 本身
ls -d projects # 列出 projects 目录本身,不展开内容不要依赖 ll、颜色或自定义别名来理解基本行为。这些设置可能在某个交互式 shell 中存在,换到脚本、容器或另一账户后却不一定存在。课程使用明确的 ls 选项,这样才能知道哪个选项造成了哪个结果。
在 Unix 风格的命名约定中,名称以 . 开头的文件或目录常用于配置和内部状态,例如 .bashrc、.config 和 .ssh。所谓“隐藏”不是一个额外安全属性,只是很多工具默认不展示点开头名称。知道路径后,仍然可以显式访问它。
ls -a # 包括所有点开头名称,也包括 . 和 ..
ls -A # 包括点开头名称,但排除 . 和 ..-A 适合想检查配置项、又不想让 . 和 .. 占两行的情况。-a 则更完整地呈现所有点目录项。两者都没有改变文件,只是改变列表筛选规则。

隐藏项没有消失;是 ls 的默认筛选让它没出现在列表里。
ls -F 常见指示符包括:目录后的 /、可执行文件后的 *、符号链接后的 @。这些字符是列表的注解,并不是文件名被改了。
ls -l 的第一列以一个类型字符开头:d 表示目录,- 表示普通文件,l 表示符号链接。后面的权限位会在专门讲文件权限时继续拆解。这里先掌握一个读图方法:颜色不可靠时,-F 和 -l 仍能提供明确类型线索。
ls 的多列布局会随终端宽度变化,排序和时间字符串还会受 locale 影响,文件名本身又可包含空白甚至换行。因此,面向人阅读的 ls 输出不是稳定的脚本数据格式。
在 Linux 文件系统接口中,文件名的法律边界其实很宽:一个单独的名称不能包含 /,因为 / 用来分隔路径分量;也不能包含 NUL 字节,因为它用来结束字符串。空格、制表符、换行、引号、星号和短横线等字符并不必然非法。
但命令行本身也使用空格分词、使用 * 做文件名展开、使用 - 引入选项。于是,合法文件名不一定容易正确输入。对需要跨 shell、跨脚本、跨工具传递的项目,字母、数字、点、下划线和短横线组成的简洁名称更少制造意外,但这是可操作性建议,不是把空格说成非法字符。
假设目录名是 course notes,直接写下面的命令会出错:
cd /home/learner/course notesshell 会先按未引用的空格分词,于是 cd 收到了 /home/learner/course 和 notes 两个参数,而不是一条路径。Bash 常会报 too many arguments。正确做法是让整条路径作为同一个参数:
cd "/home/learner/course notes"
cd /home/learner/course\ notes双引号通常是阅读性更好的写法。如果路径来自变量,也应优先写 cd "$target",因为变量值中可能包含空格或其他会被 shell 解释的字符。

引号不是路径名的一部分;它告诉 shell:这些字符应留在同一个参数里。
在常见 Linux 原生文件系统上,目录项名称通常区分大小写,File、file 和 FILE 可以同时存在。实际操作时要按原样精确输入。某些挂载的其他类型文件系统可能有不同的大小写规则,这正是不要凭经验猜文件名、而要用 ls 观察的另一个原因。
以 . 开头只会影响很多工具的默认展示规则,不会自动加密或限制访问。同样,.txt、.jpg、.sh 这些后缀是命名约定的一部分,Linux 不需要依靠扩展名才能在目录中保存或定位文件。应用程序可以根据后缀选择处理方式,但后缀本身并不能保证内容真实类型。
文件名 -draft 是合法的,但很多命令会把以 - 开头的参数当成选项。在当前目录中明确写成 ./-draft 可以消除这个歧义:
ls -l ./-draft许多命令还接受 -- 表示选项到此结束,但不同工具的语法需要查看它们各自的帮助。./-draft 的优点是直接把参数写成一条不以短横线开头的路径。
*、?、[ ]、$、;、&、|、(、) 等字符也可能在命令行解析中有特殊意义。处理已经存在的特殊名称时,要使用引用或转义;命名新文件时,如果没有明确理由,用简单字符可以减少之后每次输入的成本。
根目录下的名称不是随意摆放的。Filesystem Hierarchy Standard(FHS)给出了文件和目录放置的共同约定,让发行版、管理工具和软件包对常见位置有相近理解。它更像一张城市功能分区图,不是每个发行版都必须以完全相同的内部实现呈现每条街道。一些路径可能是符号链接,一些可选目录可能不存在。

不必一次背下所有目录。先记住它们解决的问题,再用路径不断验证。
这四个目录经常同时出现,但分工不同:
/usr 主要容纳安装的程序、库和与架构无关的共享数据。它不是“某个 user 的目录”。/var 容纳系统运行时会累积或变化的数据,如日志、缓存和队列。/run 容纳与正在运行的进程和服务相关的短期状态,通常在启动后重建。/tmp 是临时工作空间。它很适合课程实验,因为练习树可以完整删除;也正因如此,不应存放没有其他副本的重要数据。看到一个陌生路径时,可以问:这是程序与共享数据、长期变化的数据、当前启动周期的状态,还是可能被清理的临时文件?用这个问题比硬背缩写更容易建立关系。
下面的练习只在 /tmp/welearn-ch02 下建立一棵一次性目录树。所有路径都显式写在变量中,结束时整体删除。这样做不只是为了“好收拾”:固定边界使命令输出可比较,也能防止练习文件混入其他目录。
先建立训练树。course notes 故意包含空格,.env 故意以点开头,File 和 file 故意只差大小写;这些都是之后要观察的变量。
ROOT=/tmp/welearn-ch02
rm -rf "$ROOT"
mkdir -p "$ROOT/home/learner/projects/course notes"
mkdir -p "$ROOT/home/learner/projects/scripts"
mkdir -p "$ROOT/mnt/usb/docs"
touch "$ROOT/home/learner/projects/README.md"
touch "$ROOT/home/learner/projects/.env"
touch "$ROOT/home/learner/projects/File" "$ROOT/home/learner/projects/file"
touch "
再比较绝对路径、相对路径与点目录项。每次 cd 后紧跟 pwd,是为了把“命令没报错”转换成“位置确实符合预期”的可观察证据。
$ cd "$ROOT/home/learner/projects"
$ pwd
/tmp/welearn-ch02/home/learner/projects
$ cd ./scripts
$ pwd
/tmp/welearn-ch02/home/learner/projects/scripts
$ cd ../"course notes"
$ pwd
/tmp/welearn-ch02/home/learner/projects/course notes
$ cd ~
$ pwd
/tmp/welearn-ch02/home/learner../"course notes" 可以分两层理解:.. 先从 scripts 回到 projects,然后被引用的 course notes 作为一个完整路径分量。cd ~ 中的 ~ 由 Bash 展开为刚刚设置的 HOME。
然后用不同 ls 选项观察同一目录。目录内容没变,只改变列出规则,所以输出差异能直接对应到选项语义。
$ cd "$HOME/projects"
$ ls -1
File
README.md
course notes
file
scripts
$ ls -A -1
.env
File
README.md
course notes
file
scripts
$ ls -a -1 | sed -n '1,4p'
.
..
.env
File
接着验证引用和逻辑/物理路径。错误命令特意放在 set +e 和 set -e 之间,是为了记录失败而不让整个验证提前停止。
$ cd "$HOME/projects/course notes"
$ pwd
/tmp/welearn-ch02/home/learner/projects/course notes
$ set +e
$ cd $HOME/projects/course notes
bash: cd: too many arguments
$ set -e
$ ln -s "$HOME/projects" "$HOME/work-link"
$ cd -L "$HOME/work-link"
未引用的空格让错误命令产生两个参数,因此失败。同一个目录通过 work-link 进入后,pwd -L 记录导航时使用的链接名,pwd -P 则给出解析后的 projects。
最后删除实验树并检查结果。清理命令仍然引用变量,是为了保证包含空格时也只传递一个路径参数。
$ rm -rf "$ROOT"
if [ ! -e "$ROOT" ]; then
printf 'cleaned: yes\n'
fi
cleaned: yes导航出错时,不要立即重复敲同一条命令。先把问题分成四类:起点是否正确、路径分量是否存在、shell 是否改写了参数、权限是否允许通过目录。这样比把所有错误都理解成“路径不对”更快。
错误时不要为了“先进去再说”就把命令全部改成高权限执行。路径不存在、大小写错误和参数被拆分都不是提高权限能修复的问题。cd 又是 shell 内建命令,即使在另一进程环境中改变目录,也不会让当前 shell 留在那里。
导航熟练以后,不会每走一层都严格照着流程做。但在路径陌生、命令将批量处理文件,或需要把步骤交给别人复现时,下面的顺序很稳妥:
pwd -L 确认当前导航路径;如果涉及符号链接或安全边界,再用 pwd -P 对照真实位置。ls -A -F -1 观察当前目录:-A 补上隐藏项,-F 提供类型线索,-1 让每个名称独占一行。cd "$target"。名称以 - 开头时,使用 ./-name 明确它是路径。cd 后再用 pwd 确认结果。命令没有报错只说明切换成功,pwd 才能说明切到了哪里。这条流程的核心不是多敲几条命令,而是把每一个隐含条件变成可观察信息:起点由 pwd 给出,目录项由 ls 给出,路径写法由你根据执行语境选择,切换结果再回到 pwd 校验。