cd
cd(change directory)是唯一一个能让你在文件系统里自由穿梭的命令——但它也是 shell 脚本中最容易踩坑的命令:没有外部二进制,全部由 shell 内建实现,子 shell 里的cd对外面毫无影响。
速查卡片
| 需求 | 命令 |
|---|---|
| 回到家目录 | cd 或 cd ~ |
| 回到上一个目录 | cd - |
| 进入指定目录 | cd /path/to/dir |
| 进入上级目录 | cd .. |
| 进入用户家目录 | cd ~username |
| 物理模式(解析符号链接) | cd -P /path |
| 逻辑模式(保留符号链接) | cd -L /path |
| 全局物理模式 | set -P 写入 ~/.bashrc |
| 脚本中安全 cd | cd /path || exit 1 |
| 不输入 cd 也能切换 | shopt -s autocd(bash) |
概述
cd 是 shell 内建命令——不存在 /bin/cd 这样的外部二进制。这个事实有两个深远后果:
- shell 脚本中
cd只在子 shell 内生效,脚本退出后父 shell 工作目录不变 cd的所有行为细节——-L/-P、$CDPATH、$OLDPWD、错误处理——全部由 shell 自身实现,不同 shell 可能有细微差异
cd 也是 POSIX 规定最详尽的命令之一:规范用整整 10 个步骤精确描述了它的内部算法。
命令签名
# POSIX 标准签名
cd [-L | -P] [directory]
cd -
# bash 完整签名
cd [-L] [-@] [dir]
cd -P [-e] [-@] [dir]选项
| 选项 | 适用模式 | 说明 |
|---|---|---|
-L | POSIX | 逻辑模式——先处理 .. 再解析符号链接(默认) |
-P | POSIX | 物理模式——先解析符号链接再处理 .. |
-e | 仅 bash,需配合 -P | 若切换成功后无法确定新目录的物理路径,返回非零退出码 |
-@ | 仅 bash | 将文件扩展属性作为目录打开(极罕见) |
同时指定
-L和-P时,最后一个生效。POSIX 规定不指定选项时等效-L。
基础用法
示例一:最简——无参数
$ pwd
/tmp/some/deep/path
$ cd
$ pwd
/home/zolin不带参数时等价于 cd "$HOME"。如果 $HOME 未设置或为空则报错。
示例二:绝对路径和相对路径
$ cd /etc/nginx # 绝对路径:一个 / 直达
$ pwd
/etc/nginx
$ cd sites-available # 相对路径:从当前目录出发
$ pwd
/etc/nginx/sites-available
$ cd ../sites-enabled # 相对路径 + .. 组合
$ pwd
/etc/nginx/sites-enabled示例三:cd - —— 在两个目录间来回
$ cd /tmp
$ cd /etc
$ cd -
/tmp # 打印切换后的路径
$ cd -
/etccd - 等价于 cd "$OLDPWD"。shell 启动后 $OLDPWD 为空,首次 cd - 会报错 cd: OLDPWD not set。
cd -只能回到最近一个目录,不存在"更早的历史"。zsh 有cd -2等扩展,bash 需借助pushd/popd或第三方工具。
示例四:符号链接下 cd .. 的分歧
# 准备
$ mkdir -p /tmp/real/sub
$ ln -s /tmp/real /tmp/link
# 通过符号链接进入
$ cd /tmp/link/sub
# 默认 -L 模式下的 cd ..
$ cd .. && pwd
/tmp/link # 沿逻辑路径返回
# -P 模式下的 cd ..
$ cd /tmp/link/sub
$ cd -P .. && pwd
/tmp/real # 沿物理路径返回这就是 "cd .. 回不到上一级" 困惑的根因。它回到的是"你走过的路"的上一步,而不是真实文件系统的父目录。
-L vs -P:符号链接处理的完整对照
-L 和 -P 的区别不仅影响当前目录,还影响路径中 .. 分量的解析顺序:
-L(逻辑,默认) | -P(物理) | |
|---|---|---|
| 符号链接 | 保留,不解析 | 全部解析为真实路径 |
.. 的处理时机 | 先按逻辑路径去掉上一级分量,再解析符号链接 | 先解析路径中的符号链接得到真实路径,再去掉上一级 |
$PWD 更新值 | 逻辑路径(可能含符号链接分量) | pwd -P 的结果 |
| 典型场景 | 交互使用,保留导航路径的人性化 | 脚本中使用,确保路径判断基于真实文件系统 |
全局切换默认行为
# bash:使 cd 和 pwd 默认使用物理模式
set -P
set -o physical
# 恢复默认
set +P
set +o physical
# 写入 ~/.bashrc 永久生效
echo 'set -P' >> ~/.bashrc
# zsh 等效
set -w
set -o chaselinks$CDPATH —— cd 的搜索路径
$CDPATH 是 cd 最被低估的特性,类似于 $PATH 对可执行文件的搜索机制:
# 设置 CDPATH
export CDPATH=".:/home/zolin/projects:/var/www"| 规则 | 说明 |
|---|---|
| 触发条件 | 参数不以 / 开头,且第一分量不是 . 或 .. |
| 分隔符 | 冒号 : |
| 空分量 | 表示当前目录(:: 或开头/结尾的 :) |
| 匹配后行为 | cd 会打印实际切换到的路径 |
$ CDPATH=.:/var
$ cd www
/var/www # 打印了实际路径(不易察觉的影响)
$ pwd
/var/www⚠️
CDPATH的打印行为可能干扰脚本输出。如果依赖cd的安静输出,考虑在脚本中取消CDPATH:unset CDPATH。
$HOME 与 $OLDPWD
| 变量 | 作用 | 设置时机 |
|---|---|---|
$HOME | cd 无参数时的默认目标;~ 展开的基础 | 用户登录时设置,通常不变 |
$OLDPWD | 上一个工作目录;cd - 的跳转目标 | 每次 cd 成功时自动更新 |
$ echo $OLDPWD # shell 刚启动时为空
$ cd /tmp
$ cd /var
$ echo $OLDPWD
/tmp
$ cd -
/tmp
$ echo $OLDPWD
/var # 注意:又变了bash 扩展选项(shopt)
bash 通过 shopt 提供了几个提升交互体验的 cd 相关选项:
| shopt 选项 | 作用 | 启用 |
|---|---|---|
autocd | 直接输入目录名即等同于 cd,省去键盘输入 | shopt -s autocd |
cdable_vars | cd 参数不是目录时,尝试将其作为变量名取值 | shopt -s cdable_vars |
cdspell | 自动修正目录名中的小拼写错误 | shopt -s cdspell |
direxpand | Tab 补全时将 $HOME 展开为绝对路径 | shopt -s direxpand |
# autocd 示例
$ shopt -s autocd
$ /tmp # 无需输入 cd,直接回车即切换
$ pwd
/tmp
# cdable_vars 示例
$ shopt -s cdable_vars
$ proj=/home/zolin/projects
$ cd proj # 将 proj 当作变量名,取其值
$ pwd
/home/zolin/projectsPOSIX 规定的 cd 内部算法
POSIX 用 10 步精确描述了 cd 的工作流程(curpath 为中间变量):
1. 无参数且 $HOME 为空 → 实现定义(bash 报错)
2. 无参数且 $HOME 非空 → curpath = $HOME
3. 参数以 / 开头 → curpath = 参数,跳到步骤 7
4. 第一分量为 . 或 .. → 跳到步骤 6
5. 遍历 $CDPATH 搜索,找到匹配目录则 curpath = 匹配路径
6. curpath = 参数(相对路径,后续转绝对)
7. -P 模式 → 跳到步骤 10
8. 基于 $PWD 构建完整绝对路径
9. 规范化(消解 . / ..,但不解析符号链接在 -L 下的含义)
10. 处理 PATH_MAX 超长
11. 执行 chdir() 系统调用这个算法揭示了为什么 cd 在符号链接下的行为如此微妙——步骤 8 在 -L 模式下"基于 $PWD 构建路径",而 $PWD 可能就是逻辑路径。
脚本中的 cd
核心大坑:子 shell 隔离
#!/bin/bash
# myscript.sh
cd /tmp
pwd # /tmp$ pwd
/home/zolin
$ ./myscript.sh
/tmp # 脚本内部输出
$ pwd
/home/zolin # 父 shell 不受影响!每个脚本在独立的子 shell 中运行,cd 只改变子 shell 的工作目录。脚本退出后子 shell 销毁,什么都留不下。
要在当前 shell 中生效,必须用 source(或 .):
$ source ./myscript.sh # 或:. ./myscript.sh
$ pwd
/tmp # 这次生效了安全模式:cd || exit
ShellCheck 规则 SC2164——如果 cd 失败,后续命令会在错误目录中执行,可能造成灾难:
# ❌ 危险:cd 失败后 rm 跑在错误目录
cd /nonexistent/path
rm -rf * # 在当前位置删东西!
# ✅ 安全
cd /nonexistent/path || exit 1
# ✅ 函数中
myfunc() {
cd "$1" || return 1
# ... 后续操作 ...
}
# ✅ 全局保护
set -e # 任何命令失败即退出
cd /some/path # cd 失败自动终止获取脚本所在目录
# 经典写法
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# 符号链接安全版(需 readlink -f 或 realpath)
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd -P)"
# 存储后跳回
HERE="$PWD"
cd /some/other/place || exit
# ... 工作 ...
cd "$HERE" || exit权限:进入目录需要什么
| 权限 | 能否 cd 进入 | 能否 ls 浏览 |
|---|---|---|
只有 r | ❌ | ❌(基本也不行) |
只有 x | ✅ | ❌(能看到文件,但列不了表) |
r + x | ✅ | ✅ |
| 都没有 | ❌ | ❌ |
进入目录只需要执行权限(
x),这也是为什么它被称为"搜索权限"——你需要"搜索"通过这个目录才能到达它内部。
常见报错
| 错误信息 | 原因 | 解决方法 |
|---|---|---|
cd: too many arguments | 目录名含空格未加引号 | cd "my dir" 或 cd my\ dir |
cd: no such file or directory | 路径不存在或相对路径依赖了错误的工作目录 | 确认路径拼写;用绝对路径 |
| cd: permission denied | 缺少目录执行权限 x | chmod +x dir 或 sudo | | cd: not a directory | 目标是文件而非目录 | 检查路径,用 ls -l 确认 | | cd: Too many levels of symbolic links | 符号链接循环或深度超过 40 层 | ls -l 检查链接链,修复循环 |
| cd: OLDPWD not set | shell 启动后尚未执行过 cd 就用了 cd - | 先正常 cd 一次 |
相关命令与变量
| 命令/变量 | 关系 |
|---|---|
$HOME | cd 无参数的默认目标 |
$OLDPWD | 上一个目录,cd - 的跳转目标 |
$PWD | 当前目录,cd 成功后自动更新 |
$CDPATH | cd 的相对路径搜索列表 |
$DIRSTACK(bash) | 目录栈数组(pushd/popd 操作的底层变量) |
pwd | 确认 cd 切换结果 |
pushd / popd / dirs | 目录栈操作 |
| chdir() | cd 的底层系统调用 | | chmod | 修改目录权限 |
常见误区
| ❌ 误区 | ✅ 正解 |
|---|---|
"脚本中的 cd 会改变我的工作目录" | 脚本在子 shell 运行,cd 效果不传递到父 shell——用 source 执行才行 |
"cd 有可执行文件 /bin/cd" | cd 是纯内建命令,没有外部二进制——否则它根本没法改变 shell 的工作目录 |
"cd .. 总是回到物理父目录" | 默认 -L 模式沿逻辑路径——通过符号链接进入时可能不是你预期的目录 |
"cd - 可以回溯多次" | bash 中只能在最近两个目录间切换;zsh 支持 cd -2/cd +3 等多级回溯 |
| "无执行权限也能进目录看文件" | 目录的执行权限 x 是进入的前提——没有 x,即使有读权限也进不去 |
zsh 差异
| 特性 | bash | zsh |
|---|---|---|
cd -N(回退 N 步) | ❌ | ✅ |
cd +N(目录栈索引跳转) | ❌ | ✅ |
autocd | shopt -s autocd | setopt auto_cd |
| 物理模式 | set -P | set -o chaselinks |
macOS 特别说明
- macOS 默认 shell 为 zsh,
cd为 zsh 内建 - macOS 文件系统(APFS/HFS+)默认不区分大小写,
cd /tmp和cd /TMP等效(Linux 严格区分) - Terminal 新窗口起始目录为
$HOME,可在"终端 → 设置 → 描述文件 → Shell → 启动时"中修改 - macOS 没有
/bin/cd(Linux 也没有——cd永远是内建)
来源
| 来源 | 链接 |
|---|---|
| POSIX cd 规范 | https://pubs.opengroup.org/onlinepubs/9699919799/utilities/cd.html |
| POSIX cd man page | https://www.man7.org/linux/man-pages/man1/cd.1p.html |
| bash 参考手册(cd 内建) | https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html |
| GNU bash man page | https://man7.org/linux/man-pages/man1/bash.1.html |
| ShellCheck SC2164 | https://www.shellcheck.net/wiki/SC2164 |
| The Bash Hackers Wiki: cd | https://flokoe.github.io/bash-hackers-wiki/commands/builtin/cd/ |
| 整理时间 | 2026-07-14 |
