1. 为什么一个"cd"值得单独写一篇
先说个真事。我见过不止一个入职三五年的后端同事,到现在还在终端里敲cd敲到手指起茧,却不知道cd -能回上一个目录,也不知道cd ~/project/app里那个波浪号到底是怎么被解释的。更别提刚接触 Linux 的新人,第一周就被cd和各种路径搞到崩溃,甚至有人直接放弃命令行退回图形界面。
但你回头看,这确实是所有 shell 操作里最基础、也最容易被人低估的一条命令。你可以不会grep、不熟awk,但只要你还在用终端,cd就是每天至少敲几十次的东西。它简单到只有一个参数,却牵扯出绝对路径、相对路径、环境变量、通配符展开、shell 内建机制、历史记录管理、甚至是 CI/CD 流水线里的构建上下文选择。
而且这个命令还有一些很有意思的"周边纠纷":比如 Windows 的 PowerShell 里,cd实际上是Set-Location的别名,用错了会直接报"无法将 set-location 项识别为 cmdlet"——这个报错我见太多了;再比如 CI/CD 这个词里也有个 cd,但它跟目录切换半毛钱关系都没有,指的是持续交付。弄混这两个 cd,恰好是入门者特别常见的认知卡点。
这篇我不打算只列几条cd用法完事。我想把这条命令背后那套路径系统、shell 内建机制、以及几个容易踩的坑一次性讲透。内容对纯新手友好,但对已经用了两三年终端的人,也保证你能挖到一些平时没注意到的细节。
2. cd 命令的三种形态与一条核心规律
2.1 最原始的三条:裸cd、cd路径、cd回家
任何一个 shell,只要实现了 POSIX 标准,cd的最核心行为就是"改变当前工作目录"(change directory),这个不用多解释。你敲:
cd /etc/nginx pwd输出变成/etc/nginx,你就在这个目录里了。后续所有相对路径操作,比如ls .、cat nginx.conf,都是基于这个位置展开的。
但这里有个特别重要的点:我见过非常多的人把cd等同于"进入目录",然后把cd /理解成"进入根目录",这个理解对了但容易忽略一个细节——cd其实还可以直接不接参数。cd单独敲,效果等于cd ~,也就是回到当前用户的主目录。cd ~的波浪号由 shell 在展开阶段替换为$HOME变量指向的路径,通常就是/home/用户名。
还有cd .和cd ..这种。cd .一般没人用,它表示"切到当前目录",等于什么都没变,但配合cd $(dirname ...)这种脚本场景时会有存在感。cd ..才是高频操作,表示切到上一级目录。特别说明:cd ..是向上翻一层,不是退到根目录,更不是回退。到根目录要cd /。这个看似废话,但确实有新人会以为cd ..一直按下去就能到根目录,其实要连续执行多次cd ..才会从/a/b/c逐级到/a、再到/。
2.2 绝对路径、相对路径和一种日常习惯
cd的参数本质就是一个目标路径。路径有两种写法:
绝对路径:从根开始写。比如:
cd /var/log/nginx不管你在哪个目录,这条命令都让你进入/var/log/nginx。相对路径:从当前位置开始写。比如你现在在/var/log,想进nginx,那就:
cd nginx而不是cd /var/log/nginx。大部分日常操作里,相对路径用得更频繁,原因很简单:少打字。但脚本和自动化任务里,我会强烈建议用绝对路径,因为相对路径依赖"当前工作目录",而脚本一旦被别人或定时任务以别的目录启动,相对路径就可能全部失效。这种问题排查起来特别头疼——脚本逻辑没错,但cd scripts之后cd ../config就找不到文件了,因为你根本不在预期的起点。
一个我特别推荐的日常习惯是:cd进去之前,先pwd一下确认自己在哪。很多路径相关的问题,根子就出在"我以为我在 X 目录,其实在 Y 目录"。这种问题不是靠记性好能避免的,靠的是把pwd变成肌肉记忆。
2.3 特殊路径符号:~、-、.. 各自的脾气
这几个特殊符号是cd的高频搭档,但它们的展开机制完全不同,很多人一起搞混了。
~是波浪号展开(tilde expansion),由 shell 负责,替换成当前用户的$HOME。cd ~等价于cd $HOME,也等价于裸敲cd。cd ~/projects/my-app就是进入 home 目录下的 projects/my-app。需要注意:如果有多个用户,写~otheruser也可以,比如cd ~postgres会切到 postgres 用户的主目录,前提是你有权限读进去。
-就更有意思了。cd -表示"回到上一个工作目录",相当于一个历史位置的快捷切换。比如你在/a,然后cd /b,再cd -,你就回到/a了。再敲一次cd -,又回到/b。它内部实现是靠 shell 记住OLDPWD这个特殊变量。你也可以cd $OLDPWD等价实现。这个命令在"两个目录之间来回切"的场景下效率极高,很多老手都在用,但新手几乎不知道。比如你编辑配置文件时经常要在/etc/nginx/conf.d和/var/log/nginx之间来回看日志、改配置,cd -就比你把完整路径反复敲几十遍高效得多。
..前面说过了,但我想再补一个冷门组合:cd ../..是向上两级,cd ../../..是向上三级。这在深层目录结构里很常用。另外cd ~/../..这种不常见但合法,它先展开成/home/用户名/../..,最终效果是/。我一般不建议这么写,可读性太差,容易让后来的人看不懂。
2.4 参数为空时,shell 到底做了什么
这里值得深入一嘴。因为很多人以为cd不带参数会报错,其实不会。在大部分 shell 中,裸cd等价于cd ~,目的就是"回家"。但如果你的$HOME环境变量没设置,shell 就会报cd: HOME not set。这个报错在容器里很常见,尤其是那种精简版的 Alpine 镜像,某些场景下你切到非 root 用户后,$HOME可能没给,这时候裸cd就会翻车。
另外在一些特殊 shell 里,比如sh的 POSIX 模式,cd裸用还有些细节差异,这些细节日常用不到,知道有这个坑就行了。
3. cd 与路径解析背后的那一套规则
3.1 shell 怎么解释你给它的那串字符
上一条我们都在聊cd这个命令本身,但终端里你敲的其实不是只有cd。你敲的是"一行命令",shell 会先用空格拆词、再做各种展开(波浪号、变量、命令替换、通配符),最后才把结果交给cd这个内建命令处理。
举个例子。你在终端敲:
cd $BACKUP_DIRshell 先展开$BACKUP_DIR变量,得到/data/backup/2024,再执行cd /data/backup/2024。如果你敲:
cd ~/logs/*那就是先波浪号展开、再通配符展开。如果~/logs/下只有一个目录app,那最终执行的是cd ~/logs/app。但如果匹配到多个文件,cd就会报too many arguments——因为它只接受一个目标路径参数。这个坑很隐蔽,我见过不少人在脚本里写cd $SOME_PATH/*然后死活报错,其实问题不出在cd上,是通配符展开了多个结果。
另一个常见问题是路径里有空格。比如目录名是My Documents,你直接cd My Documents,shell 会当成两个参数,cd直接就炸了。必须写成:
cd "My Documents"或者转义:
cd My\ Documents当然我强烈建议创建文件目录的时候就别用带空格的名称,但这世界不是围着你转的,总会碰到的。
3.2 cd 能私闯禁地吗?权限与错误提示
cd能不能进去,取决于目录的权限位。Linux 下需要目录的x(执行)权限才能进入。你在自己的 home 目录一般没感觉,但切到/root或者别人家目录,经常遇到Permission denied。这个报错跟cd本身无关,是内核的权限检查拒绝了。
但有一种更隐蔽的情况:目录存在、也有权限,但你是通过软链接进去的。比如:
ls -l /var/lib/docker你发现/var/lib/docker是个软链接,指向/mnt/docker-data。那么执行:
cd /var/lib/docker pwd你会看到pwd输出的路径很可能还是/var/lib/docker,而不是实际路径/mnt/docker-data。这个是因为大部分 shell 的pwd默认走的是逻辑路径(你 cd 的时候用哪个字符串进的,就显示哪个),而不是物理路径。想看到真实物理路径,用pwd -P。反过来,cd也可以带-P或-L参数,cd -P会切到物理路径,cd -L保留逻辑路径。日常使用不用太在意,但写脚本判断路径时,这个差异会咬人。
3.3 cd 的参数太少太杂?不存在的——它不止一条命令
很多人觉得cd就是cd,其实不同 shell 对cd的实现细节有一定差异。bash 的cd支持-L、-P和-e等选项;zsh 的cd支持一个特别强的功能:即使你只给了路径的一部分,只要没有歧义,它就能自动补全。比如你在/home/yourname/projects/下有个目录my-awesome-project,在 bash 里你得敲完整路径或用 Tab 键补全,但在 zsh 里,只要你敲的片段能唯一定位到目标目录,敲回车就能进去。
对于日常使用,我特别推荐做一件事:给cd配上几个高频别名。比如:
alias ..='cd ..' alias ...='cd ../..' alias ....='cd ../../..'这是很多老手配置里的标准件。别的先不说,光是少按 Shift 和点号这件事,长期下来都是一大省力。有人喜欢把cd改成pushd,用目录栈来切换,这个后面细说。
4. 实测场景:从"只会进目录"到"灵活穿梭"
4.1 五个常见操作的完整演示
我直接拉一套终端实录出来,每一步都带解释。
假设初始位置是/home/amy:
pwd # /home/amy cd projects/web pwd # /home/amy/projects/web cd .. pwd # /home/amy/projects cd ~/data pwd # /home/amy/data cd - pwd # /home/amy/projects cd /var/log/nginx cd - pwd # /var/log/nginx 上一次的 /var/log/nginx 和这次一样,因为 cd - 的回跳有状态,上面先回 projects,再进 nginx,所以 - 记录的是 projects注意看,cd -的行为是"在上一次与当前位置之间反复横跳"。它只记忆一个OLDPWD,不是完整的最近访问列表。想实现"最近访问的多个目录之间切换",就得借助其他机制,比如z或者autojump,后面会聊。
4.2 脚本中 cd 的黄金守则:先判断再进入
脚本里使用cd是新手翻车重灾区。常见错误是直接:
cd /some/important/dir rm -rf *如果cd因为目录不存在失败了,脚本却不会自动终止(除非你写了set -e),那rm -rf *就会在你当前所在的目录里执行——这可能导致灾难性后果。所以脚本里的cd必须带着检查:
cd /some/important/dir || { echo "无法进入目录,终止"; exit 1; }或者开启set -e,让任何失败的命令直接终止脚本。这两种办法我现在都会叠加使用,双保险。写脚本时,对cd的每一个使用场景都问一句:如果这个目录进不去,后续会怎样?这个习惯能避免大量半夜被叫醒的事故。
另一个脚本相关坑是子 shell。你在一个脚本里cd,不会影响你打开的那个终端窗口,因为每个脚本都运行在独立的子 shell 中。这也是为什么你写一个脚本里cd到某处、pwd、然后脚本结束,你的终端还是原来那个目录。理解"每个 shell 有自己独立的工作目录"这个模型,对理解许多脚本问题极有帮助。
4.3 目录栈:pushd、popd、dirs 的高级玩法
cd的短板是它只记住"上一次"的目录。如果你需要在三个甚至更多目录之间快速切换,比如/project/backend、/project/frontend、/project/config,每次都cd来cd去也可以,但更顺滑的是目录栈。
bash 里内置了pushd、popd、dirs三个命令来管理"目录栈"。用法很简单:
pushd /project/frontend dirs # 显示栈内容 pushd /project/config popd # 弹出栈顶,回到上一个刚上手会觉得记不住,但用熟了之后,多目录协作的工作流会很舒服。比如你在后端的目录里改了接口,需要快速去前端看调用代码、改完再切回来,pushd和popd组合能省掉大量路径输入。我自己的习惯是把dirs显示做成提示符的一部分,一看就知道栈里有什么。这个功能在 zsh 里更加强大,可以自动补全栈内路径。
4.4 cd 与 CDPATH:新手的认知盲区
bash 和 zsh 里还有一个 CDPATH 环境变量,很多写了很久脚本的人都没碰过。它的作用是:当你执行cd命令且参数不是绝对路径时,shell 不是只在你当前目录下找,还会按 CDPATH 中列出的目录依次搜索。
举个例子。如果设置了:
export CDPATH=.:~:/etc然后你执行cd nginx,shell 会依次尝试当前目录、~、/etc下有没有nginx目录,有就进去。如果都没有,才会报no such file or directory。
这个变量对"频繁进入固定几个目录"的场景很有用,但副作用是:你的cd行为会变"跨目录",如果配合脚本使用,可能产生一点点意外效果——shell 可能切到一个你没想到的位置。我自己日常不会设置它,只在特定场景(比如经常要cd到分布式环境的多个工作目录)才考虑。新手我建议别碰,熟悉了再按需开启。
5. 别把两个 cd 搞混:CI/CD 与 Windows 下的 cd
5.1 当"切换目录"遇上"持续交付"
这个必须单独讲,因为标题里的热词把cd和CI/CD放到了一起,很多人一搜就懵了:cd到底是命令还是流水线?答案是两个世界。
命令行的cd是 change directory,持续交付的cd是 continuous delivery,后者常跟 CI(持续集成)连写,组合成一套自动化流水线的理念:代码提交后,自动完成构建、测试、部署。很多团队自建的 GitLab Runner 流水线里,第一步往往就是cd到某个工作目录或者通过 checkout 把代码拉取到指定目录。这里面的"切换目录"仍然用的是cd命令,但它的上下文已经迁移到"自动化构建环境"里。
写 CI/CD 流水线脚本时,我强烈建议做到"路径显式化":在每个 job 开头先用pwd把当前工作目录打出来,再决定要不要cd。你本地跑得好好的脚本,部署到 CI runner 的默认工作目录里,可能整个目录结构都不一样,一个cd ../就把道路断了。我踩过很深的坑:本地路径是/home/me/project,CI 的路径是/builds/group/project,脚本里写死的cd ../config到了 CI 里切到了一个完全不相关的位置。
5.2 PowerShell 里 cd 报错的前因后果
热词里有一条特别具体:powershell cd : 无法将“set-location”项识别为 cmdlet、函数、脚本文件。这个报错几乎人人都会碰到,解释起来也不复杂。
在 PowerShell 里,cd是Set-Location的内置别名。正常使用cd是没有问题的。但这个报错通常出现在以下几种场景:
- 你把
cd写进了某个脚本,而这个脚本的上下文丢失了对Set-Location的引用。 - 你自定义了一个名为
cd的函数或别名,导致原本的别名被覆盖。 - 你在某些增强了安全策略的环境中,比如执行策略(Execution Policy)限制脚本运行,导致内建函数不可用。
- 你定义了一个变量
$cd,然后执行了$cd = "SomeScript.ps1",随后在脚本里用& $cd调用,这不会出问题,但如果你cd被重新赋值为字符串,那cd somepath可能被解析成别的东西。
最常见的其实是第一种或第二种。排查思路很简单:在 PowerShell 中执行:
Get-Alias cd看输出是不是指向Set-Location。如果显示的是别的东西,那就是别名被覆盖了。再执行:
Get-Command Set-Location确认内建命令是否存在。如果存在,就直接用Set-Location替代cd来绕过问题。
还有一条热词是"无法将文件作为 cd 映像",这个多见于 Windows 下做 ISO 镜像的场景,跟目录切换毫不相关。这个不要混在一起。Windows 下的磁盘切换(比如从 C: 切到 D:)直接在命令行敲D:就行,这个行为不是cd负责的,而是 cmd 和 PowerShell 的命令行解释器约定的驱动器切换语法。
5.3 Windows 用户学 cd 的三个需要特别注意的差异
很多从 Windows 转向 Linux 的朋友,最容易在cd用法上迷路,这里我列三个最常见的差异:
路径分隔符:Windows 用反斜杠\,Linux/Unix 用正斜杠/。在 bash 里写cd C:\Users\amy是不行的,得写cd /c/Users/amy(Git Bash 环境)或者cd C:/Users/amy(某些终端模拟器能识别)。
盘符概念:Linux 没有盘符,一切目录都挂在根/之下。cd /d不是去 D 盘的意思,是去根目录下的d文件夹。这是 Windows 用户特别容易绕晕的一点。
感性与大小写:Windows 路径不区分大小写,Linux 区分。cd /Home/Amy和cd /home/amy在 Linux 是两回事。这个差异带来的错误频率,可能比初学者想象中的高得多。
5.4 一个与 cd 谐音/同名的领域:游戏里的"冷却时间"与安装光盘
这个属于延伸八卦了。热词里出现了"魔兽世界 清除cd gm命令",还有"catia三个cd安装步骤"。这里的 cd 分别是 cooldown(技能冷却时间)和 compact disc(光盘)。cd这个缩写本身在英文世界就横跨了多个领域,初学命令行的朋友在搜索引擎里查资料时经常被这些词干扰,我在这提醒一下:看到cd开头的词条,先确认是技术文档还是游戏攻略。我自己见过不少人在查cd /d时搜到 Windows 下"进入 D 盘"的教程,然后一脸疑惑地拿到 Linux 里用。分清语境,是自学路上很重要的一个习惯。
6. cd 的邻近武器库:那些和路径导航强相关的能力
6.1 Tab 补全:比 cd 更重要的反而是快捷键
我始终觉得,终端效率的第一功臣不是命令本身,而是补全机制。在 bash 里,输入cd /ho再按 Tab,你会看到候选列表,再按几次 Tab 或者输入更多字符,直到唯一匹配,按回车。这个习惯直接决定了你用命令行的舒适度。
zsh 的补全更聪明,它能补全路径的中间片段。比如:
cd /var/l/ng在 zsh 里,这串字符可能被自动补全成/var/log/nginx,因为它能识别模糊匹配。bash 默认不支持这个,但你安装bash-completion项目后也能获得类似能力。
6.2 autojump、z、fasd:让 cd 学会"记住"你的习惯
如果你觉得cd还是太繁琐,可以试试更高阶的目录跳转工具。它们的核心思路是:记录你进入过的目录的频率和最近时间,然后让你用一小段模糊的关键字跳过去。
比如z这个工具,你经常访问/home/amy/work/company-project,之后你只要:
z company它就能自动匹配到你最常访问的那个包含company的目录,并切换过去。对于目录层级深、名字又长的项目,这种工具能省掉近乎全部的路径输入。
但我个人的建议是:新手不要一上来就用这些工具,先把裸cd用利索,把路径系统理解透,再加这些外挂。因为那些工具本质上是对"目录访问数据"的模糊判断,一旦你脑子里没有清晰的路径模型,你可能都不知道z把你切到了哪儿。
6.3 软链接与 bind mount:那些"看起来不在预期位置"的目录
日常开发中遇到cd后pwd显示的路径和预期不一致,多半是软链接捣的鬼。举个例子。你在/home/amy/project下工作,但这个目录其实是一个符号链接,指向/data/disk2/project,当你执行:
cd /home/amy/project pwdbash 的pwd默认输出/home/amy/project——它是逻辑路径。但你执行pwd -P,输出是/data/disk2/project——物理路径。
这个差异在写脚本时相当坑。比如你用pwd结果拼一个日志路径,然后上传到远端服务器,可能就和你预期不一致。解决手段是:在脚本里统一用cd -P或pwd -P,确保拿到的路径是物理路径,这样跨系统行为更可预测。
容器里的 bind mount 也有类似现象:宿主机的目录挂载进容器后,你在容器内看到的路径和宿主机路径完全不同。理解"路径只是人在用户空间里定义的逻辑视图,真实文件系统结构未必和展示一致"这一点,会帮你少踩很多坑。
6.4 cdrecord 与刻录命令的干扰:此 cd 非彼 cd
顺便说一句,Linux 里还有个古老命令叫cdrecord,用来刻录光盘,这个命令也有个缩写层面的干扰:有时候你在终端敲cd会联想到刻录。但实际上只要你的 shell 里cd是内建命令(builtin),它就优先于任何外部命令执行。这也是 shell 内建命令和外部命令的一个基本关系:内建优先。你可以用type cd查看:
type cd # cd is a shell builtin这解释了为什么你哪怕安装了一个叫cd的外部程序,在常规 shell 里也几乎不会生效。
7. 常见问题与排查技巧实录
7.1 报错速查表
| 报错信息 | 原因 | 解决方案 |
|---|---|---|
No such file or directory | 路径不存在或写错 | pwd确认当前位置,用ls看目录名 |
Permission denied | 无目录执行权限 | ls -ld查看权限,考虑sudo或更换目录 |
cd: HOME not set | $HOME环境变量缺失 | 检查环境变量设置,容器场景常见 |
too many arguments | 参数超过一个,通常是通配符展开 | 用引号包裹路径或检查目录名 |
bash: cd: ... Not a directory | 试图把文件当目录进入 | 检查目标是否是目录,用ls -l看类型 |
PowerShell: 无法将 set-location 项识别为 cmdlet | 别名或上下文被破坏 | 执行Get-Alias cd、Get-Command Set-Location排查 |
7.2 经验:cd 失败后的那一刻,别急着执行下一条命令
这个我放在最后讲,因为它可能是整篇文章里最值钱的一条建议。
不管你是在交互式终端还是在写脚本,只要cd执行失败,不要急着往下执行任何依赖路径的命令。多问一句:为什么失败?是目录不存在,还是权限不够,还是我在一个符号链接的迷宫里?养成这个习惯之后,你几乎可以避免所有"rm -rf 删错目录"级别的事故。有些人以为加了set -e就万事大吉,但交互式终端里根本不吃这套,你得靠自己的判断力。
我还特别喜欢在长命令前加一个"安全锚点":
cd /target/path && ./deploy.sh这样cd成功才继续执行,失败就直接终止,不会带着错误的工作目录去跑下一个命令。这个习惯不花成本,但救过我好几次。
8. 最后分享一点我个人的使用习惯
我用了十几年终端,现在的cd习惯和刚开始有了很大不同。早期我喜欢用绝对路径,觉得稳妥;后来各种项目目录层级越来越深,我开始依赖z这类模糊跳转;再后来写 CI/CD 和容器相关的东西多了,我发现"路径确定性"比"路径便捷性"更重要——流水线脚本里我故意写长而完整的绝对路径,宁可多敲几个字母,也不要让相对路径在某个环节悄悄改变行为。
另外一个小习惯是:在提示符里带上当前目录。这样我一抬眼就知道自己在哪个目录,根本不需要执行pwd。配置方法很简单,bash 里在PS1里加\w,zsh 里加%d。我会显示完整路径,因为项目里同名目录实在太多了,只显示最后一个目录名反而容易混淆。如果你经常在多个项目间切换,强烈建议试一下,体验提升非常明显。
cd这条命令本身算不上"技术含量高",但把这个基础命令相关的整套路径模型、shell 展开机制、环境差异和脚本风险点串起来之后,你的终端使用体验会有一个质变。下次再遇到"明明执行了 cd 却还在原目录"这种初级困惑时,你至少知道从哪里开始排查了。