pwd
pwd(print working directory)看似简单到只有两个选项,但恰恰是这层"简单"让很多人从未深究过它真正的知识纵深——shell 内建与外部二进制的默认行为完全相反,符号链接下的路径分歧更是脚本 bug 的温床。
速查卡片
| 需求 | 命令 |
|---|---|
| 显示当前目录 | pwd |
| 显示物理路径(解析符号链接) | pwd -P |
| 显示逻辑路径(保留符号链接) | pwd -L |
| 使用外部命令(非 shell 内建) | /bin/pwd 或 command pwd |
| 查看 PWD 环境变量 | echo $PWD |
| 查看上一个目录 | echo $OLDPWD |
| 获取脚本所在目录 | DIR="$(cd "$(dirname "$0")" && pwd)" |
| 验证在符号链接目录中 | pwd && pwd -P(两输出不同则是) |
概述
pwd 是 Linux 最基本的命令之一,用途单一:输出当前工作目录的绝对路径。但它的实现有两种形态:
- shell 内建命令:bash/zsh/dash 等 shell 内置的实现,直接读取
$PWD环境变量,极快 - 外部二进制
/bin/pwd:GNU coreutils 提供的独立程序,通过getcwd()系统调用从文件系统逐级追溯
两者在默认行为上存在关键差异:shell 内建默认 -L(逻辑路径,保留符号链接),GNU /bin/pwd 默认 -P(物理路径,解析符号链接)。这个差异是 pwd 文档的核心价值所在。
命令签名
# POSIX 标准签名
pwd [-L | -P]
# GNU coreutils 签名
pwd [OPTION]...选项
| 选项 | 长选项 | 说明 |
|---|---|---|
-L | --logical | 从 $PWD 环境变量取值输出,保留路径中的符号链接分量(shell 内建默认) |
-P | --physical | 解析全部符号链接,输出真实文件系统路径(/bin/pwd 默认) |
--help | 显示帮助信息(仅外部命令) | |
--version | 输出版本信息(仅外部命令) |
默认行为差异:POSIX 规范规定不指定选项时等效
-L;GNU coreutils man page 写明 "If no option is specified, -P is assumed"。这不是 bug——/bin/pwd的 man page 同时备注:"Your shell may have its own version of pwd, which usually supersedes"。日常交互中你用的pwd大概率是 shell 内建,默认-L。
同时指定 -L 和 -P
POSIX 规定:最后一个选项生效。pwd -L -P 等价于 pwd -P。
shell 内建 vs 外部二进制
这是整篇文档最深的知识点。两套实现不是"哪个更好"的问题,而是行为不同,选错会出 bug。
| 维度 | shell 内建 pwd | /bin/pwd(GNU coreutils) |
|---|---|---|
| 默认行为 | -L(逻辑路径) | -P(物理路径) |
| 实现方式 | 读取 $PWD 环境变量 | 调用 getcwd() 系统调用 |
| 性能 | 极快,零文件系统 I/O | 逐级遍历目录树,受磁盘 I/O 影响 |
| 权限依赖 | 无需父目录读权限 | 需要对路径上每一级目录的读权限 |
| 目录被删除时 | 仍输出旧 $PWD 值 | 输出带 (unreachable) 的相对路径或报错 |
| 长选项 | 无(bash 只支持 -L/-P) | 支持 --logical --physical |
如何精确选择
type pwd # 查看当前 pwd 是哪种实现
pwd # shell 内建,默认 -L
builtin pwd # 强制使用内建
command pwd # 强制使用外部命令(POSIX 方式)
\pwd # 反斜杠绕过别名,同样走外部命令
/bin/pwd # 直接调用二进制
env pwd # 在干净环境下运行(不继承 shell 别名/函数)基本用法
示例一:最简用法
$ pwd
/home/zolin/projects无选项时输出当前工作目录的绝对路径。由于 shell 内建默认 -L,如果通过符号链接进入目录,输出的是你走过去的那条路而不是真实路径。
示例二:典型场景——确认你在哪里
$ cd /etc/nginx
$ pwd
/etc/nginx
$ ls
nginx.conf sites-available sites-enabled进入目录后 pwd 确认位置,是日常操作的标准习惯。
示例三:进阶场景——符号链接下的路径分歧
# 准备环境
$ mkdir -p /tmp/real/project
$ ln -s /tmp/real /tmp/link
# 通过符号链接进入
$ cd /tmp/link/project
$ pwd # shell 内建,默认 -L
/tmp/link/project
$ pwd -P # 强制物理路径
/tmp/real/project
$ /bin/pwd # 外部命令,默认 -P
/tmp/real/project
$ echo $PWD # 环境变量,永远是逻辑路径
/tmp/link/project四种方式,三种结果。 如果你的脚本依赖路径判断(如检查是否在某个特定目录下),用错了就会静默出错。
示例四:冷门场景——目录被删除后
$ mkdir /tmp/testdir && cd /tmp/testdir
$ rmdir /tmp/testdir # 删除当前目录
$ pwd # shell 内建:仍输出旧值
/tmp/testdir
$ /bin/pwd # 外部命令(Linux ≥ 2.6.36)
(unreachable)/tmp/testdir # 带前缀标记,且是相对路径自 Linux 2.6.36 起,
getcwd()在目录被删除时返回带(unreachable)前缀的相对路径——但这违反了 POSIX 对绝对路径的要求。
$PWD 环境变量
$PWD 是 shell 维护的核心变量,记录当前的逻辑工作目录。
| 特性 | 说明 |
|---|---|
| 初始化 | shell 启动时设置,随每次 cd 更新 |
| 内容 | 绝对路径,可能包含符号链接分量 |
| 有效性 | POSIX:若 $PWD 不包含 . 或 .. 分量,pwd -L 直接用它 |
| 超过 PATH_MAX | POSIX:实现可选择回退到 -P |
| 用户修改后果 | POSIX:应用自行设置或取消 $PWD 后,pwd 行为未定义 |
| 继承 | 子进程继承当前 shell 的 $PWD |
$PWD "变味"场景
如果某级父目录被重命名,$PWD 不会自动更新:
$ cd /tmp/foo/bar
$ mv /tmp/foo /tmp/foo2 # 重命名父目录
$ pwd # shell 内建:仍输出旧路径
/tmp/foo/bar
$ /bin/pwd # 外部命令:失败,因为路径已不存在
/bin/pwd: couldn't find directory entry in '..' with matching i-node$OLDPWD
$ cd /tmp
$ cd /etc
$ echo $OLDPWD
/tmp
$ cd - # 等价于 cd "$OLDPWD"
/tmpcd - 在最近两个目录间切换,适合频繁在两个工作区间跳转的场景。
符号链接深入
符号链接是理解 pwd 全部价值的场景。这里集中展开。
cd .. 的行为差异
在符号链接目录中,cd .. 的行为取决于 shell 的物理/逻辑模式:
$ mkdir -p /tmp/a/b
$ ln -s /tmp/a /tmp/link
$ cd /tmp/link/b
# 逻辑模式(默认)
$ cd .. && pwd
/tmp/link # 沿符号链接路径返回
# 物理模式
$ cd -P .. && pwd
/tmp/a # 沿真实文件系统返回这就是很多人的困惑来源——"明明在 /tmp/link/b,cd .. 之后怎么回到了 /tmp/link 而不是 /tmp/a?"
全局切换默认行为
| shell | 命令 | 效果 |
|---|---|---|
| bash | set -P 或 set -o physical | cd 和 pwd 默认使用物理路径 |
| bash | set +P 或 set +o physical | 恢复默认逻辑路径 |
| zsh | set -w 或 set -o chaselinks | 同上 |
| 永久生效 | 写入 ~/.bashrc / ~/.zshrc | — |
内核视角:/proc/self/cwd
Linux 内核只维护已解析的真实路径,不关心符号链接:
$ cd /tmp/link/b
$ ls -l /proc/self/cwd
lrwxrwxrwx 1 root root 0 Jul 14 12:00 /proc/self/cwd -> /tmp/a/b无论你是通过哪个符号链接过来的,内核只看你"真正"在哪个目录。
目录栈操作(pushd / popd / dirs)
bash 和 zsh 提供了基于栈的目录导航,与 pwd 配合使用:
$ cd /tmp
$ pushd /etc # 将 /etc 压栈并切换
/etc /tmp
$ pushd /var # 将 /var 压栈并切换
/var /etc /tmp
$ dirs # 查看栈内容
/var /etc /tmp
$ dirs -v # 带编号查看
0 /var
1 /etc
2 /tmp
$ popd # 弹出栈顶,切换到下一个
/etc /tmp
$ pwd
/etc$DIRSTACK 是 bash 暴露的目录栈数组变量,可在脚本中使用。
脚本中的 pwd
获取脚本所在目录
这是 pwd 在脚本中最重要的用途:
#!/bin/bash
# 经典写法:获取脚本所在目录的绝对路径
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "脚本位于:$SCRIPT_DIR"逐步拆解:
$0:脚本的调用路径(可能是相对路径,如./script.sh)dirname "$0":提取目录部分cd到该目录pwd:输出绝对路径
$0的陷阱:如果脚本被 source 引入(. script.sh或source script.sh),$0可能是调用者的名称而不是脚本自身。此时应用${BASH_SOURCE[0]}替代。
符号链接安全的版本
如果脚本本身是符号链接:
# GNU 环境(Linux)
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
# 兼容 macOS 的写法(macOS 没有 readlink -f)
if command -v realpath &>/dev/null; then
SCRIPT_DIR="$(cd "$(dirname "$(realpath "$0")")" && pwd)"
else
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
fi存储后跳回
HERE="$PWD"
cd /some/other/place
# ... 工作 ...
cd "$HERE" # 回到原目录比 pushd/popd 更可移植(兼容所有 POSIX shell)。
相关命令对比
| 命令 | 功能 | 输出示例(在符号链接目录中) |
|---|---|---|
pwd | 逻辑路径(shell 内建默认) | /tmp/link/project |
pwd -P | 物理路径 | /tmp/real/project |
/bin/pwd | 物理路径(外部命令默认) | /tmp/real/project |
echo $PWD | PWD 变量值 | /tmp/link/project |
realpath . | 解析当前目录真实路径 | /tmp/real/project |
readlink -f . | GNU 版解析真实路径 | /tmp/real/project |
退出码
| 值 | 含义 |
|---|---|
0 | 成功 |
>0 | 出错(无父目录读权限 + 使用 /bin/pwd 等) |
POSIX 特别规定:出错时不得向 stdout 写入任何内容。这一要求的背景是历史上大量脚本不检查 pwd 的退出码,如果输出了不完整的路径可能造成灾难性后果。
注意事项
/bin/pwd依赖父目录读权限:如果路径上任何一级目录不可读,外部命令会失败,shell 内建不受影响$PWD可能已被污染:如果脚本或程序修改了$PWD,pwd -L会输出错误值——POSIX 将其标注为"未定义行为"- 长路径风险:POSIX 指出,路径超过
PATH_MAX时pwd输出的结果可能导致cd失败,需拆分为多段 - 不要混淆
pwd和echo $PWD:前者经过(至少内建层面)一定的校验,后者是裸读变量
常见误区
| ❌ 误区 | ✅ 正解 |
|---|---|
"pwd 就是 /bin/pwd" | 日常交互中用的是 shell 内建,默认行为不同(-L vs -P) |
"cd .. 总是回到物理父目录" | 逻辑模式下沿符号链接路径返回,可能不是你预期的目录 |
"$PWD 一定是真实路径" | 通过符号链接进入时包含链接分量,用 pwd -P 求真值 |
"pwd 永远不会失败" | /bin/pwd 在父目录无读权限时会报错退出 |
"$(dirname "$0") 就能拿到脚本目录" | $0 可能是相对路径或符号链接,需 cd + pwd 转绝对 |
"所有 shell 的 pwd 行为一致" | bash/zsh/dash 对 -L/-P 默认处理一致,但 /bin/pwd(GNU)默认 -P |
版本信息
| 事件 | 版本/日期 |
|---|---|
| 首次纳入 POSIX | Issue 2 |
新增 -P / -L 选项 | POSIX Issue 6(IEEE P1003.2b) |
与 getcwd() 行为变化同步 | POSIX Issue 7(Austin Group Interpretation #140) |
getcwd() 增加 (unreachable) 前缀 | Linux 2.6.36 |
| GNU coreutils 当前版本 | 9.11(2026-04) |
macOS 特别说明
- macOS 默认 shell 为 zsh
- zsh 内建
pwd默认-L,支持-P、-L,行为与 bash 一致 - macOS 的
/bin/pwd来自 BSD(非 GNU),选项支持-L/-P,但不支持长选项--logical/--physical - zsh 的
$PWD变量标记为只读,不可直接赋值修改 - macOS 没有
readlink -f(BSD 版本不支持-f选项),可用realpath替代
来源
| 来源 | 链接 |
|---|---|
| GNU coreutils man page | https://man7.org/linux/man-pages/man1/pwd.1.html |
| POSIX 规范 | https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pwd.html |
| bash 参考手册(pwd 内建) | https://ftp.gnu.org/old-gnu/Manuals/bash/html_chapter/bashref_4.html |
| getcwd(3) Linux man page | https://man7.org/linux/man-pages/man3/getcwd.3.html |
| GNU coreutils 官方文档 | https://www.gnu.org/software/coreutils/manual/html_node/pwd-invocation.html |
| Stack Overflow: shell builtin vs /bin/pwd | https://stackoverflow.com/questions/3134479/ |
| 整理时间 | 2026-07-14 |
