在 Linux 终端里,cd 命令通常是大多数新手继 ls 之后认识的第二个命令。它看起来简单到让人懒得深究:cd 后面跟个目录名,按下回车,位置就变了。可一旦把场景放到真实运维和开发环境里,事情就没那么简单——有人切进一个深层目录后想回到原来的位置,只能一层层敲 cd ../..,最后却发现退过了头;有人在脚本里用 cd /some/path 切换目录,没检查返回值,后续命令跑在了错误目录里;还有人发现 cd 一个名字居然跳到了完全不相干的目录,查了半天才知道是 CDPATH 在捣鬼。这篇就来系统地梳理 cd 命令的核心用法:返回上级目录、返回根目录、进入目录、返回上一次目录、返回指定目录,同时把与之相关的 OLDPWD、路径解析、权限、符号链接这些底层机制一并讲清楚。不管你刚学 Linux,还是已经常年在终端里工作,应该都能从里面找到平时不太注意的细节。
1. 先搞懂路径概念:cd 命令的所有变化都绕不开它
很多人一上来就背命令,cd /、cd ..、cd ~ 背得滚瓜烂熟,但真到用的时候就蒙了:为什么 cd ../../.. 退着退着就不知道自己在哪了?为什么同一个命令在别人的机器上能用,到自己这里就"No such file or directory"?根源基本都在路径理解上。这一节把地基打好,后面拆解各种 cd 用法才不悬空。
1.1 绝对路径与相对路径:决定你能去哪
路径分成两种:绝对路径和相对路径。
绝对路径是从根目录 / 开始写的一整条路径,比如 /etc/nginx/nginx.conf,它的特点是稳定、完整,不管当前在哪个目录,都能准确指向同一个地方。相对路径则是从当前目录出发写的路径,比如当前在 /etc/nginx 下,想访问 sites-enabled 里的文件,直接写 sites-enabled/default.conf 就行,前面不用带 /。
这两者的选择,直接影响 cd 命令的写法和切换结果。想进入指定目录,最容易成功的方式是给绝对路径,因为不依赖当前状态。但相对路径在脚本和批量操作里更灵活,能避免写死机器上的绝对位置。举个例子,你写一个部署脚本,要把配置复制到某个相对目录,如果全用绝对路径,脚本换个项目就废了;用相对路径配合当前目录,反而通用。
实际使用中,我建议养成一个习惯:不知道当前在哪、不确定目标在哪的时候,先用下面两条命令定位自己:
pwd ls -lapwd 会输出当前目录的绝对路径,ls -la 能让你看到当前目录里有什么、上级目录入口在哪里。很多新手切着切着迷路了,其实不是 cd 的问题,是连"我现在在哪"这个基准点都丢了。
1.2 特殊目录符号:. .. / ~ - 的含义与组合规则
cd 命令之所以能玩出那么多花样,靠的是一套特殊目录符号。把它们记牢,比死记命令有效得多:
| 符号 | 含义 | 典型用法 |
|---|---|---|
| . | 当前目录 | cd ./conf |
| .. | 上一级目录 | cd .. |
| / | 根目录 | cd / |
| ~ | 当前用户主目录 | cd ~ |
| - | 上一次所在的目录 | cd - |
这套符号可以组合。cd ../.. 是向上退两级;cd ../../etc 是向上退两级之后,再进入 etc。注意,../ 和 .// 这种写法在处理时会被 shell 简化,但逻辑上不影响结果。
一个常见的误区是认为 ~ 只能表示当前用户的主目录。实际上,在绝大多数 shell 里,~ 后面可以直接跟用户名,表示那个用户的主目录,比如 cd ~postgres 会进入 postgres 用户的主目录。这在多用户服务器管理场景里特别有用,你不需要知道 postgres 用户的主目录到底在 /home/postgres 还是 /var/lib/postgresql,系统会自动解析。
1.3 先用 pwd 确认"我是谁、我在哪"
前面提到 pwd 很重要,这里单独展开。pwd 的全称是 print working directory,输出的是当前 shell 进程的工作目录。它看起来只是打印一行路径,但在下面这些场景里,它是救命的:
- 迷路了,不知道自己在哪个目录,先 pwd。
- 写脚本前,确定脚本运行时的工作目录是否符合预期。
- 排查文件找不到、权限报错这类问题时,pwd 能帮你定位"问题发生在哪个目录"。
顺带提一个提升体验的办法:在 shell 提示符里直接显示当前目录。默认的 PS1 变量很多只显示用户名和主机名,我会把 PS1 改成显示当前路径的最后一段,具体可以在 ~/.bashrc 或 ~/.zshrc 里配置。这样你日常根本不用频繁敲 pwd,一眼就知道自己待在哪。
2. 五种核心用法逐个拆解:从进目录到返回指定目录
标题里列了五类典型用法:返回上级目录、返回根目录、进目录、返回上一次目录、返回指定目录。这一节把它们逐个拆开,配合实际命令讲清楚使用场景和注意事项。
2.1 cd 单独敲:一键回到用户主目录
先说一个很多人没注意的细节:cd 后面不带任何参数时,等价于 cd ~,也就是回到当前用户的主目录。
cd为什么会有这个默认行为?因为系统会自动读取当前用户的 HOME 环境变量。执行 cd 无参数,shell 就认为你想去 $HOME。这在日常操作里非常顺手,比如你在某个深层项目目录里忙活半天,想回家,直接敲 cd 就完事,不需要敲一长串绝对路径。
这里有一个容易踩的坑:用 sudo 执行命令时,HOME 环境变量有时候不会自动切换成 root 的 /root,而是仍然保留原用户的主目录。所以在 sudo su 切换用户之后,敲 cd 回的地方可能不是你预期的那样。建议在使用 sudo 或 su 切换到其他用户后,先验证一下环境变量:
echo $HOME确认当前有效的用户主目录到底是什么,再决定用不用 cd 无参数。
2.2 cd / 与 cd ..:返回根目录和上级目录
返回根目录是最简单的:
cd /执行之后,当前目录变成根目录 /。根目录是所有绝对路径的起点,在根目录下你能看到 /etc、/usr、/var、/home 这些系统核心目录。有时候某个目录权限异常,或者你想快速脱离当前目录树,直接 cd / 是干净利落的选择。
返回上级目录:
cd .... 是上一级目录的符号。如果你想返回上两级,写 cd ../..;返回上三级,写 cd ../../..。注意每多一个 ../ 就往上一层,中间用斜杠分隔。这个过程看起来机械,但有一点值得提醒:在路径非常深的目录树里,一味用 cd .. 逐级往上退,效率不高也容易数错层级。更靠谱的做法是先用 pwd 看清自己当前在哪,然后直接给一个绝对路径切到目标位置,比如知道自己在 /var/log/nginx 下面,想回 /var/log,直接 cd /var/log 就行,没必要 cd ../。
还有一个关于 .. 的细节,放在后面符号链接部分细说,因为它的解析结果在某些场景下会出乎你的意料。
2.3 cd -:返回上一次所在的目录
这个绝对是日常效率利器。cd - 的含义是"切换到上一个工作目录",也就是你执行本次 cd 之前所在的那个目录。
cd /var/log cd /etc/nginx cd - # 当前目录变回 /var/log执行 cd - 时,shell 会打印出切换后的目录路径,方便你确认结果。我见过很多人不知道这个用法,在两个目录之间来回切换时,反复敲绝对路径或者敲一堆 cd ../..,其实一行 cd - 就解决了。尤其是在编辑配置、对比日志、来回看文件的时候,cd - 能帮你省下大量键盘寿命。
不过要注意,cd - 依赖的是 shell 内部维护的"上一次目录"记录,具体机制下一节会详细讲。这里先说结论:它是当前 shell 进程内的状态,新开一个终端或者在一个新的 shell 脚本里运行 cd -,不一定会按你预期那样工作。
2.4 cd ~:进入用户主目录,但不只是"回家"
cd ~ 看起来和 cd 无参数功能一样,但它有一个额外用途:切换到其他用户的主目录。
cd ~ # 当前用户主目录 cd ~postgres # 切换到 postgres 用户的主目录第二种用法在管理数据库服务、查看其他用户部署的应用时很常用。比如你的服务器上有个 git 用户专门管理代码仓库,你想快速看它的部署目录,不需要知道它的家目录具体在 /home/git 还是 /srv/git,直接 cd ~git 让系统自己解析就行。
这里提醒一点:~ 的展开是 shell 完成的,在脚本里如果使用了单引号包裹变量,展开可能不会发生。比如你写 cd '~postgres',那么 shell 会把它当成一个真的叫 ~postgres 的目录,而不是用户主目录。正确的做法是不加引号,或者写成 cd ~"postgres"。类似这种细节,在命令行里很容易被忽略,但在脚本自动化里会实实在在踩坑。
2.5 cd /path 与 cd 相对路径:进入指定目录的底层逻辑
进入指定目录,既可以给绝对路径,也可以给相对路径:
cd /var/lib/docker cd /etc cd ../nginx/conf绝对路径的好处是明确,不会因为当前目录不同导致结果不同。相对路径的好处是简短,但它强依赖当前目录。一个经常被忽略的问题是:cd 后面的参数不一定是直接写的路径,也可以是变量。
MY_DIR="/opt/myapp/config" cd "$MY_DIR"这在脚本里非常常见。但这里有一个安全性和稳定性都很重要的细节:变量值里如果包含空格,cd 命令会把它拆成多个参数,导致进入错误目录或报错。所以一定要用双引号把变量包起来:
# 错误示例 cd $MY_DIR # 正确示例 cd "$MY_DIR"我的建议是,在命令行里手动敲路径时可以随意,但在脚本中凡是用变量作为 cd 目标,一律加双引号,这能避免大概率出现的路径分割问题。另外,命令行的通配符也可以用在 cd 上,比如当前目录下有 app1 和 app2 两个目录,你想进入 app1,可以敲 cd app?,但这种写法在生产环境里不建议,因为容易匹配到多个目录导致报错。
3. 深入 cd -:OLDPWD 与目录切换状态的秘密
cd - 用起来很爽,但很多人只是记住了"小编号命令",并不知道它为什么能记住上一次目录。这一节把背后的 OLDPWD 机制拆开,同时说清楚在脚本里使用它的注意事项。
3.1 为什么 cd - 能记住上一次目录:OLDPWD 变量
在兼容 POSIX 的 shell(bash、zsh、sh 等)里,有一个环境变量叫 OLDPWD,它记录的是"当前 shell 在最近一次成功切换目录之前的工作目录"。
每次执行 cd 命令并成功切换后,shell 会做两件事:
- 把当前目录保存进 OLDPWD。
- 把新目录设置为当前目录,并更新 PWD 变量。
你可以直接查看这个变量:
echo $OLDPWD echo $PWD这样就会清晰看到"上一次目录"和"当前目录"分别是什么。cd - 的官方等价写法其实就是 cd "$OLDPWD",只不过 shell 额外做了两件事:把目标目录打印出来,同时加入对 OLDPWD 为空时的错误提示。
这个机制解释了为什么新开一个终端后 cd - 会提示"OLDPWD not set"——因为新的 shell 进程还没有执行过任何 cd,OLDPWD 自然为空。
3.2 cd - 的输出行为和隐藏细节
执行 cd - 时,你会在终端看到类似这样的输出:
/var/log这个输出是 shell 故意打印出来的,目的是让你知道这次切换到了哪里。cd 后面跟普通目录时不会有这个输出,所以 cd - 算是特例。这个输出可以用于在脚本里做日志记录,但更常见的用途是让你在手工操作时有个确认。
还有一个容易忽略的细节:cd - 只认最近一次的目录,它不会记录"上一次的上一次"。如果你想在多个目录之间循环切换,cd - 就不够用了,需要用到第四节讲的目录栈。
3.3 脚本中使用 OLDPWD 的注意事项
在实际写 shell 脚本时,不要想当然地依赖 cd - 或 OLDPWD。原因有三个:
- 脚本可能是在一个新的 shell 环境里执行的,OLDPWD 初始为空。
- 脚本中可能已经有别的 cd 执行过,OLDPWD 已经被覆盖,不再是你期望的"上一次"。
- 如果 cd 失败,OLDPWD 可能不会被更新,导致后续判断出错。
更稳妥的做法是手动保存目录到变量里:
BASE_DIR="$PWD" cd /somewhere/else # 想回到原位置 cd "$BASE_DIR"这样不依赖 OLDPWD 的状态,逻辑上更可控。尤其是部署脚本、备份脚本这种需要来回切换工作目录的场景,用变量保存路径比用 cd - 可靠得多。我在写多步骤运维脚本时,基本都会在一开始就定义好所有目标目录变量,中途切换只引用变量,不碰裸的 cd -。
4. 实战中的坑:从权限报错到 CDPATH 陷阱
cd 命令看似人畜无害,但真正到了复杂的服务器环境和自动化脚本里,它引发的麻烦一点不比服务崩溃少。下面几个问题,都是我实际遇到过或者身边同事踩过的。
4.1 权限不足:cd 报 Permission denied 怎么办
进入一个目录需要该目录的执行权限(x 权限)。没有执行权限时,即使你有读取权限,cd 也会报错:
cd /root # bash: cd: /root: Permission denied这种报错在普通用户试图进入其他用户主目录时非常常见。有些人第一反应是加 sudo:
sudo cd /root这里必须提醒:sudo cd 在绝大多数发行版和 shell 上是不生效的,因为 cd 是 shell 内建命令,sudo 只能对外部命令生效。你要做的是进入到 root 用户的 shell 环境,比如 sudo -i 或 sudo -s,然后再正常 cd。
处理权限问题,核心思路是理解目录的 x 权限:只要某层路径上的任意一级目录缺少执行权限,你就进不去。排查时可以使用 namei 命令看看哪一级目录卡住了:
namei -l /root/secret/data它会展示路径上每一级的权限情况,比我靠肉眼一层层 ls 快得多。
4.2 符号链接目录下的 cd .. 行为
符号链接(symlink)是 Linux 里常见的目录跳转方式。假设 /tmp/link 是指向 /var/log 的符号链接,你执行:
cd /tmp/link pwd你猜 pwd 显示的是 /tmp/link 还是 /var/log?答案是:取决于 shell 的逻辑路径设置。默认情况下,大多数 shell 的 pwd 会显示逻辑路径,也就是 /tmp/link。但如果你执行 pwd -P,会显示物理路径 /var/log。
问题出在使用 cd .. 时。在 /tmp/link 目录下执行 cd ..,你以为会进入 /tmp,但很多情况下它会进入 /var。为什么?因为 shell 默认把 .. 解析为逻辑路径的上一级,它可能基于物理路径解析。具体行为在不同 shell 和设置下会不一样,所以我强烈建议:在符号链接较多的环境中,如果需要精确定位上一级目录,先用 pwd -P 确认物理路径,再决定 cd 目标。另一种做法是使用命令:
cd -P /tmp/linkcd -P 会强制切换到符号链接的真实物理路径,后续的 .. 行为会符合直觉。反过来,想保留逻辑符号链接,则用 cd -L。
这个点做前端工程、多版本并行部署的人最容易遇到,npm 的 node_modules 里一堆符号链接,目录一深,cd .. 就神不知鬼不觉跑到了别的项目。
4.3 CDPATH 环境变量引发的"灵异跳转"
CDPATH 是个很少被提及、但一旦配置就非常影响 cd 行为的环境变量。正常情况下,cd 命令只会在当前目录里查找参数对应的子目录。但如果设置了 CDPATH,cd 会按照 CDPATH 里的路径列表去搜索匹配的目录。
比如你把 CDPATH 设置成了 /etc,那么当你在任意目录执行 cd nginx 时,如果 /etc/nginx 存在,shell 就会直接把你切到 /etc/nginx,而不是在当前目录里找。这个特性用好了很爽,用不好就是灾难——有人配置了 CDPATH 之后,在项目目录里敲 cd config,shell 没有进入项目自己的 config 目录,反而跳到了系统某个完全不相关的 config 目录,排查了半天。
诊断方法很简单:
echo $CDPATH如果确实配置过,可以临时清空再测试:
CDPATH= cd config在写跨环境脚本时,我的建议是一律不依赖 CDPATH,入口处显式 export CDPATH= 或直接使用绝对路径,避免脚本因为执行环境的 CDPATH 不同而出现诡异行为。如果你确实觉得 CDPATH 方便,那也要注意,它的查找优先级是当前目录最先,找不到再去 CDPATH 里搜,但项目里同名目录一旦出现,很容易被忽略当前目录的预期而跳错。
4.4 在脚本里频繁 cd 的常见坑
脚本里用 cd 最典型的问题是:cd 失败但脚本继续执行。
cd /nonexistent rm -rf ./*如果目录 /nonexistent 不存在,cd 会返回非零退出码,但 bash 默认不会停止,rm -rf ./* 会继续在当前目录执行。轻则删错文件,重则清空项目目录。这绝对是用 cd 引发的最危险情况。
解决办法是给 cd 加上返回码判断:
cd /nonexistent || exit 1或者更严格一点,在脚本开头加上:
set -eset -e 会让任何非零退出码导致脚本退出,能有效避免 cd 失败后继续跑后面的命令。但要注意,set -e 在某些命令的返回值处理上也有坑,最稳妥的组合是:cd 显式判断成功与否,再决定继续还是退出。
另一个脚本层面的优化是尽量在子 shell 里切换目录:
( cd /var/log grep "error" *.log )括号里的内容在子 shell 中执行,切换目录只影响子 shell,父 shell 的当前目录不会变。这样脚本主流程不会因为某个子任务 cd 而干扰后续逻辑,是处理多目录操作的好习惯。
5. 提升效率:那些比裸敲 cd 更好用的替代方案
到这里,cd 的基础用法和坑都讲得差不多了。下面分享一些我日常更常用的提升方案,它们不是要取代 cd,而是让目录切换这个动作在特定场景下变得更快、更安全。
5.1 pushd / popd / dirs:目录栈化管理
如果你的工作流需要在多个目录之间来回切换,比如同时操作前端项目、后端服务、日志目录三个地方,cd - 就不够用了,因为只能记住上一次。这时候可以用目录栈:
pushd /var/log pushd /etc/nginx dirs # 显示目录栈列表pushd 会切换目录并把路径压入栈中。popd 会弹出栈顶目录并切换过去。dirs 查看当前栈内容。这等于给频繁切换提供了"历史记账本",比记忆一串 cd - 可靠很多。
我个人的习惯是:最多三层以内的来回切换用 cd -,需要同时维护多个工作区时用 pushd,因为 pushd 的栈结构存储在 shell 里,切换顺序清晰可控。
5.2 给 cd 设置别名:几条实用 alias
日常操作里,连敲 cd .. 都嫌慢。可以在 ~/.bashrc 或 ~/.zshrc 里配置几个高频别名:
alias ..='cd ..' alias ...='cd ../..' alias ....='cd ../../..' alias cd..='cd ..' alias ll='ls -lha'配好之后,在当前目录想往上退两级,直接敲 ... 三个点,不用再打一长串 ../..。这些别名在 Mac 和 Linux 上都能用,本质还是把简写字符串替换成 cd 命令。还有的人习惯在 cd 成功后自动 ls,可以借助函数实现:
function chpwd() { ls -la; }zsh 下支持 chpwd 钩子,bash 下可以给 cd 定义一个函数,但注意别把它和平台默认行为弄冲突。
5.3 让 CDPATH 变成助手而不是坑
前面讲了 CDPATH 的坑,但它在特定场景下确实能提升效率。假如你的工作目录都集中在 /home/me/work 下,可以为这个目录配置 CDPATH:
export CDPATH=.:/home/me/work这样在任何地方敲 cd project-a,都会先找当前目录,如果没有,就去 /home/me/work 里找 project-a。关键是只把有限的可信目录加入 CDPATH,避免把 /etc 这种系统目录加进去。在配置 CDPATH 的环境里,我建议始终用 cd . 强制指定当前目录下的目标,或者直接用绝对路径,避免歧义。
5.4 高级玩法:fzf + cd 模糊搜索目录
如果经常要在很深的项目目录树里导航,光靠 cd 已经不够高效了。推荐给终端配上 fzf(fuzzy finder),配合别名实现模糊搜索目录切换:
alias fcd='cd "$(find . -type d -not -path "*/node_modules/*" | fzf)"'执行 fcd 后,会交互式列出当前目录下所有子目录,你输入关键字就能模糊匹配,回车直接 cd 过去。这个方案在大型代码仓库里非常爽,一套完整的服务端项目几十个层级,cd 过去再 cd ../.. 能让人崩溃,fcd 一条命令就搞定。需要说明的是,fzf 需要额外安装,但它几乎成了现代终端用户的标配工具,配合 cd 用法是压垮繁琐导航的最后一根稻草。
如果你用的是 zsh 且开启了 autocd 特性,甚至可以直接省略 cd 命令,把目录名当命令敲,shell 会直接切过去。不过这个特性偶尔会跟同名命令造成冲突,我的态度是:可以用,但要清楚它存在,出现问题第一时间检查是不是它惹的祸。
最后再分享一个我个人的小习惯:凡是需要在多个目录之间反复横跳又涉及文件操作的场景,我都在脚本开头先把关键目录存成变量,然后用绝对路径变量切换,绝不裸用 cd - 或依赖 shell 状态。这样做的好处是脚本可移植、可读性强,别人接手时一眼就能看出每段操作的目标目录。至于日常手工操作,cd - 和目录栈已经完全够用,没必要把终端搞得过于复杂。Linux 的使用乐趣,往往就是从这些看似微小的命令细节里一点点积累出来的。