刚开始学 Linux 的时候,pwd 是我最早背下来的三条命令之一,另外两条是 cd 和 ls。当时的理解特别朴素:pwd 就是显示当前目录,终端提示符里明明已经写了当前路径,再敲一遍好像有点多余。后来写 shell 脚本、处理软链接、排查磁盘挂载问题踩了几次坑,才意识到这个看起来最"没技术含量"的命令,实际用起来门道多得超出预期。这篇笔记不是要凑"十条冷门用法",而是想从实操角度把 pwd 的默认行为、参数差异、脚本里的坑、以及它和磁盘挂载、路径定位配合的场景完整梳理清楚,适合刚上手 Linux 的初学者,也适合写过一段时间脚本但偶尔还会被软链接和物理路径搞晕的朋友。
我会尽量把触发问题的现象、排查思路和最终解法都写出来,方便你直接实验和对照。毕竟命令行这东西,只有自己动手敲过一遍,踩过的坑才算真正变成经验。
1. 先说清楚:pwd 到底"管"磁盘管理的哪一段
1.1 命令本义与名字的误解
pwd 的全称是 Print Working Directory,直译过来就是"打印工作目录"。很多人会把 pwd 误以为和磁盘管理强相关,实际上它本身不直接管磁盘分区、不负责格式化、也不统计空间占用。它做的是另一件事:告诉你当前进程的工作目录在哪。
为什么要强调"当前进程的工作目录"?因为 Linux 里每个进程都有一份独立的当前工作目录,shell 只是其中一个进程。你在终端里执行 pwd,拿到的是 shell 的工作目录;如果在脚本里调用 pwd,拿到的是脚本运行时的工作目录;如果在 cron 任务里触发某个程序,那个程序的工作目录可能和你在终端里看到的完全不一样。理解这一点,很多脚本路径问题的根因就浮出水面了。
在磁盘管理的语境里,pwd 更像是一块"定位牌"。你要 df 看某个目录属于哪个挂载点、要 du 统计目录大小、要 mount 确认某个目录是不是 NAS 入口,第一步都得先确认自己确实站在预期的目录下。路径定位错了,后续所有磁盘操作都可能作用在错误的地方。
1.2 pwd 在路径定位和磁盘操作中的真正价值
举个实际例子。服务器上挂载了一块新盘到 /data01,你 cd /data01 进去后发现 df -h 显示的使用率和想象中不一样。这时候如果没确认当前路径,很容易怀疑是挂载失败了,但实际上可能是你还在宿主机根目录 / 下,根本没进到挂载点里面。
pwd 在这里的作用就是帮你确认"我到底在哪"。特别是在远程操作、多用户共用服务器、脚本自动化这些场景下,人的记忆是不可靠的,只有命令输出是可信的。
另一个高频场景是配合 du 做磁盘占用排查。很多人习惯直接 du -sh,但输出里的相对路径往往让人看不出来具体是哪个目录。更稳的做法是先 pwd 确认当前位置,再用 du -sh "$(pwd -P)/"这样的组合,把绝对路径打印到结果里,排查完才好写进报告或工单。
1.3 适合谁读,读完能解决什么问题
这篇内容的核心目标读者有三类:
- 刚接触 Linux、对目录概念还比较模糊的初学者。看完能彻底搞清楚 pwd 输出的是什么、为什么有时候显示的逻辑路径和实际物理路径不一致。
- 经常写 shell 脚本的开发者。我会把脚本里获取"自己所在目录"的标准姿势写清楚,避免最常见的 $0 和 pwd 混用问题。
- 做运维和服务器管理的人。pwd -P 配合 df、du、mount 排查磁盘挂载和空间占用问题,这个链路值得收藏。
读完之后,你至少能回答这几个问题:pwd -L 和 pwd -P 到底差在哪;为什么同一台机器上 pwd 和 /bin/pwd 结果可能不一样;脚本里 cd 到软链接目录后怎么拿到真实物理路径;当前目录被删了之后 pwd 报错怎么救。
2. 核心参数 -L 与 -P:两个字母的差别能玩出花
2.1 默认行为在不同环境里竟然不一样
pwd 的参数极其简单,只有两个:
- -L:Logical,逻辑路径。输出的是环境变量 PWD 里记录的路径,保留符号链接形式。
- -P:Physical,物理路径。会让内核重新解析当前目录的真实位置,把所有符号链接都解开,输出物理路径。
表面看只是一个字母的差别,但真正的坑在于:不同环境下 pwd 的默认参数不一样。这是我在工作里踩过最典型的"环境差异"问题。
Bash 的 pwd 内建命令默认使用逻辑路径,也就是 -L;但 GNU coreutils 提供的 /bin/pwd 独立程序默认却使用物理路径,也就是 -P。还有 dash、zsh 等不同的 shell,默认行为也各有差异。同一个命令,可能因为你换了执行方式、换了 shell 环境,输出结果就变了。
如果只记一句话:在 Bash 里默认 pwd 偏向逻辑路径,在脚本和系统工具场景里 /bin/pwd 偏向物理路径。需要精确控制时,永远不要省略参数,明确写成 pwd -P 或 pwd -L。
2.2 用实验验证逻辑路径和物理路径的区别
理论讲再多,不如亲手试一次。我在虚拟机里给你完整演示一遍:
# 1. 准备测试目录和软链接 mkdir -p /tmp/real_dir ln -s /tmp/real_dir /tmp/link_dir # 2. 通过软链接进入目录 cd /tmp/link_dir # 3. 默认输出(Bash 下通常为逻辑路径) pwd # 输出:/tmp/link_dir # 4. 查看物理路径 pwd -P # 输出:/tmp/real_dir # 5. 强制逻辑路径 pwd -L # 输出:/tmp/link_dir同样的目录,pwd 和 pwd -P 给出了两个完全不同的结果,但这两个路径其实指向同一个文件夹。出现差异的原因在于:内核维护的"当前目录"概念里有一个真实的索引节点入口,而 shell 还额外维护了一个 PWD 环境变量,记录的是你"通过什么路径走到这里的"。软链接就是这个差异的最大制造者。
在磁盘管理场景下,这个区别非常致命。比如你用 df -h /tmp/link_dir 和 df -h /tmp/real_dir,如果这两个路径横跨不同挂载点,会得到完全不同的容量信息。稍不留神就会把空间配额算错。
2.3 其他可选项:--help、--version 以及 /bin/pwd 的差异
除了 -L 和 -P,pwd 还支持 --help 和 --version,这两个参数在 GNU 版本下才有意义。我一般很少用,但偶尔排查环境时会靠它们确认系统里 pwd 程序的来源:
pwd --help /bin/pwd --version这里要专门区分一件事:shell 内建 pwd 和独立程序 /bin/pwd 不是同一个东西。bash 的内建 pwd 优先于外部命令,所以你在终端敲 pwd,实际执行的是 bash 内置的逻辑。如果你强制写 /bin/pwd,调用的是 GNU coreutils 的独立实现,默认参数是 -P。用 type -a pwd 可以看到完整情况:
type -a pwd # pwd is a shell builtin # pwd is /bin/pwd这就能解释一个很怪的现象:有人在自己的终端里执行 pwd 显示 /home/user/project,到脚本里执行 pwd 却显示 /home/user/project/../project。原因通常是脚本环境里有人设置了奇怪的别名、函数覆盖,或者执行路径里带了 .. 而 bash 的 PWD 处理逻辑和 /bin/pwd 不一致。遇到这种情况不要慌,先 type -a pwd 看真相。
3. 高频实操场景:软链接、脚本路径、CI/CD 定位
3.1 场景一:软链接目录下的"真假路径"
日常运维里,软链接到处都是。/etc 本身在不少系统里就是指向 /private/etc 的软链接,/var、/tmp 这类目录在 macOS 上同样是链接到 /private 下的真实路径。你进入这些目录后执行 pwd,看到的是带链接的假路径;执行 pwd -P,才看得到真实物理路径。
这个差异对普通查看目录没影响,但在脚本里会产生连锁反应。假设你把项目部署在 /opt/app 下,然后又创建了一个软链接 /data/app 指向它。你想用脚本扫描 /data/app 目录下的所有文件,如果脚本内部用 pwd 拿到的是 /data/app,一切正常;但如果某个模块内部做了 cd 到真实路径,再 pwd 就会得到 /opt/app。两个路径可能触发不同的权限策略、配额限制甚至自动挂载行为。
我的习惯是:脚本里凡是涉及绝对路径的变量,一律在入口统一用 pwd -P 解析一次,后续逻辑全部基于物理路径操作。这样可以避免软链接层数叠加导致的路径混乱,尤其是链接套链接的情况下。
dir_before=$(pwd) dir_physical=$(pwd -P) echo "逻辑路径: $dir_before" echo "物理路径: $dir_physical"3.2 场景二:脚本中拿到真正的脚本目录
这是一道经典的 shell 脚本面试题,也是无数人栽过跟头的地方。很多新手在脚本里直接写:
#!/bin/bash script_dir=$(pwd)然后发现无论从哪个目录调用这个脚本,script_dir 拿到的都是"当前执行命令时所在的目录",而不是"脚本文件存放的目录"。项目明明在 /home/user/deploy,脚本却在 /home/user 下面被调用,最后拼出来的路径全部错位。
pwd 的含义是"当前进程工作目录",它和脚本文件所在位置没有必然关系。正确的做法是用 BASH_SOURCE 变量,再结合 cd 和 pwd -P 做解析:
#!/bin/bash script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd -P) echo "脚本真实目录: $script_dir"解释一下这里为什么必须加 pwd -P:如果脚本是通过软链接被调用的,BASH_SOURCE[0] 拿到的是软链接路径,dirname 得到的目录也是软链接所在目录。加上 pwd -P 之后,cd 进入目标目录再打印物理路径,就能把软链接层解开,拿到真实物理位置。这是我在生产脚本里已经固定使用的模板,强烈建议直接抄走。
如果你的脚本可能被软链接多层嵌套调用,更保险的写法是加一个循环,把链接层全部解开:
#!/bin/bash SOURCE="${BASH_SOURCE[0]}" while [ -h "$SOURCE" ]; do DIR="$(cd -P "$(dirname "$SOURCE")" >/dev/null 2>&1 && pwd -P)" SOURCE="$(readlink "$SOURCE")" [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" done script_dir="$(cd -P "$(dirname "$SOURCE")" >/dev/null 2>&1 && pwd -P)"这个结构看起来啰嗦,但遇到链接套链接、相对路径链接、路径带空格的情况都能正确解析。投入的这几行代码成本,绝对值回票价。
3.3 场景三:搭配 df/du 判断当前目录所在挂载点
回到磁盘管理这条主线。服务器挂载多个磁盘时,最常干的一件事就是确认"当前目录在哪个挂载点下"。直接用 df -h . 也能看,但如果你想在脚本里自动拿到挂载点名字,写成这样更稳:
current_dir="$(pwd -P)" mount_point="$(df -P "$current_dir" | awk 'NR==2 {print $NF}')" echo "当前目录: $current_dir" echo "所在挂载点: $mount_point"这里用 pwd -P 而不是裸 pwd 的原因,还是绕不开软链接。如果当前目录是通过软链接进去的,df 默认会按照路径参数检查,而 df 内部对路径的解析规则和 shell 的 PWD 并不完全一致。先统一成物理路径再传给 df,结果才稳定可预期。
同理,做 du 统计磁盘占用时,最好也把目标转成绝对物理路径:
du -sh "$(pwd -P)"这样即使你在层层软链接里,统计结果对应的也是真实物理位置,不会出现 A 路径统计完了 B 路径还要再统计一遍的重复计算问题。
4. 避坑专题:三个 pwd 翻车案例排查全过程
4.1 坑位一:脚本里执行 pwd 拿到的不是脚本位置
这个坑我在 3.2 已经提到过,这里展开讲一次完整的排查链路,方便你以后遇到类似问题能快速定位。
现象:写了一个部署脚本,放在 /home/user/deploy/deploy.sh,里面用 pwd 判定项目目录,然后去 pwd/data/ 下面找配置文件。直接在 deploy 目录里执行 ./deploy.sh 一切正常,但换了 /home/user 目录执行 /home/user/deploy/deploy.sh,脚本立刻报错找不到配置文件。
排查步骤:
- 先看报错信息。如果是"文件或目录不存在",大概率是路径拼错了。
- 在脚本里临时加一行 echo "pwd is $(pwd)",分别在两个位置执行,看输出差异。结果一个是 /home/user/deploy,一个是 /home/user,问题坐实。
- 再用 echo "$0" 和 echo "${BASH_SOURCE[0]}" 对比,发现 $0 输出的是调用时传入的脚本路径 /home/user/deploy/deploy.sh,但 pwd 拿到的还是调用者所在目录。
- 最终修改:放弃 pwd,改用 dirname + BASH_SOURCE + pwd -P 的组合。
这里的关键教训是:pwd 永远只回答"我在哪",不回答"脚本在哪"这个问题。脚本定位必须依赖脚本自身的路径体,也就是 $0 或 BASH_SOURCE,不要被 pwd 的表面意义带偏。
4.2 坑位二:alias/函数覆盖导致 pwd 输出失效
有一种隐蔽的情况:你在 .bashrc 或 .bash_profile 里写了别名或函数,导致 pwd 输出被篡改。比如有些人为了偷懒,会设置:
alias pwd='echo "当前路径: $(pwd -P)"'听着很方便,但实际上这个别名是递归的,更合理的写法应该考虑别名展开本身。最麻烦的不是这种格式问题,而是如果 alias 没有处理好,pwd 被替换成了其他命令,那脚本里所有基于 pwd 的判断都会失真。
我帮同事排查过一个案例:他写了一个备份脚本,本地执行正常,通过 SSH 远程执行时路径全部错乱。后来发现远程服务器的 .bashrc 里有这么一行:
alias pwd='echo $OLDPWD'大概是他之前调试时加的,忘了删。远程被 SSH 进去时,非交互式 shell 也会读取 .bashrc 的某些配置,alias 生效后,系统里的 pwd 输出变成了上一次的目录,所有路径判断全部错位。
排查思路很简单:遇到 pwd 输出可疑时,先敲 type -a pwd 看它是内建、外部程序,还是被 alias 或函数包裹了。如果是 alias,用 \pwd 或 command pwd 绕过别名,直接调用真实命令:
\pwd command pwd /bin/pwd这也是我推荐在脚本里使用 /bin/pwd 或命令前缀的原因之一:脚本应该尽量不受交互式 shell 的别名污染。测试完记得清理对应的别名配置。
4.3 坑位三:当前目录被删除后 pwd 直接报错
还有一个比较少见但一旦遇到就有点懵的情况:你进入一个目录,然后用另一个终端把它删掉了,再回头在当前终端执行 pwd,会看到报错:
pwd: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory然后 bash 会尝试基于 PWD 环境变量把路径打出来,但报错信息已经说明当前工作目录已经失效。出现这个现象的原因:内核里的当前目录索引节点已经不在了,getcwd 系统调用失败,而 shell 的 PWD 变量可能还保留着旧路径。
这时候千万别继续执行那些依赖当前路径的操作,比如删除相对路径文件、写入日志等,很可能把文件写到意外的地方或者直接失败。正确做法是立刻 cd 到一个确保存在的目录,比如:
cd /tmp pwd如果 cd 也报错,那就连当前目录的父级都可能被删了,可以尝试 cd ~ 或者 cd /。这种场景解释了为什么在某些自动化任务里推荐先进入固定目录再执行逻辑,比如 crontab 脚本开头统一 cd 到脚本目录或 /tmp,避免工作目录被外部清理程序干掉之后引发连锁错误。
5. 联动机制:cd、环境变量 PWD 与 set -o physical
5.1 CD 命令如何决定 pwd 的输出
pwd 的输出不是凭空产生的,它严重依赖 cd 命令的记录。Bash 里维护了一个 PWD 环境变量,每次 cd 成功都会更新它。而 cd 本身也支持 -L 和 -P 参数,默认行为受 set -o physical 选项控制。
默认情况下,cd -L 采用逻辑方式,只更新 PWD 变量,不关心路径中是否有符号链接。cd -P 则会调用系统调用 chdir,让内核真正切换,并把物理路径存到 PWD 中。如果你先 cd -P 进入一个软链接目录,即使后面不带参数执行 pwd,输出也大概率是物理路径。
一个值得记住的组合:如果你希望整个会话内所有 pwd 和 cd 都默认解析物理路径,可以执行:
set -o physical执行之后,cd 进入软链接目录的行为会变化,pwd 的默认输出也偏向物理路径。再执行 set +o physical 可以恢复。这个选项在需要严格物理路径的脚本开头特别有用,全局影响完事再关掉也来得及。
5.2 $PWD 环境变量和 pwd 命令的输出有什么不同
很多人以为 echo $PWD 和 pwd 完全等价,实际上大多数时候等价,但环境变量可以被手动修改,这就留下了隐患。
export PWD=/home/user/fake pwd执行完上面两行,pwd 的输出取决于 shell 的具体实现。有些 bash 版本会尝试纠正这个不一致,重新读取真实目录;有些则直接信任 PWD 环境变量。至少有一点可以确定:手工修改 PWD 是极不安全的操作,会导致脚本里的路径判断混乱,甚至影响命令提示符、vim 的会话恢复等功能。
所以在脚本里尽量不要依赖 $PWD 变量,而是直接调 pwd -P 获取可靠结果。两者混用的时候,有一个隐蔽差异:$PWD 不包含最后一个斜杠,大部分时候无影响,但在做字符串拼接时容易产生双斜杠,比如 $PWD//data/file.txt,这类拼出来的路径虽然大多数工具能容忍,但最好还是用 $(pwd -P)/data/file.txt 这种更规整的写法。
5.3 利用 set -o physical 统一全会话行为
我自己管理服务器时有个偏好:在系统级的交互式 shell 配置里不启用 set -o physical,但在写关键业务脚本时,第一行后面紧跟 set -o physical,让整个脚本都工作在物理路径模式下。这样无论调用者处于什么乱七八糟的软链接环境里,脚本内部都有一套稳定的路径视图。
脚本开头模板大概长这样:
#!/bin/bash set -o physical set -euo pipefail # 获取脚本目录(此时 pwd 已经是物理路径优先) script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" echo "脚本目录: $script_dir" cd "$script_dir"有人担心 set -o physical 会影响 cd 的便利性,比如想快速进入一个软链接目录并保持链接路径显示。这种情况建议显式使用 cd -L,不要为了少数例外牺牲全局一致性。
6. 把这套功夫用到日常:我的几条经验沉淀
6.1 写脚本时约定俗成的路径获取模板
这几年下来,我自己形成了几个路径获取的"肌肉记忆"。最常用的三条:
- 想知道当前工作目录,且在意软链接:用 pwd -P。
- 想在脚本里拿脚本文件所在的真实目录:用 cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P。
- 想确认当前目录在哪个挂载点下:用 df -P "$(pwd -P)" | awk 'NR==2 {print $NF}'。
这三条几乎覆盖了我生产环境里大多数需要绝对路径的场景。遇到特殊情况,比如要处理软链接多层嵌套的脚本,就升级到 3.2 里那个 while 循环版本。
另外还有一个小细节:如果脚本既可能在 Linux 上跑,又可能在 macOS 或者 BSD 系统上跑,尽量避开非标准的 readlink -f,优先用 cd 加 pwd -P 的组合。因为 macOS 自带的 readlink 和 GNU 版本行为不一样,-f 参数支持也不统一,跨系统时容易踩雷。
6.2 排查"路径不对"类问题时的检查顺序
路径相关的 bug 排查起来最费神,我总结了一套固定顺序,遇到问题直接按这个来,效率能翻倍:
- 先确认当前是否站在预期目录:pwd、pwd -P、echo $PWD 三连击,对比输出差异。
- 检查 pwd 是不是被 alias/函数覆盖:type -a pwd,必要时 \pwd 绕过。
- 查看当前 shell 是否启用了 physical 模式:set -o | grep physical。
- 验证脚本里路径变量来源:是 BASH_SOURCE、$0,还是 pwd,分清它们各自的语义。
- 最后再用 ls -ld 查看目标路径的真实链接状态,排除软链接干扰。
这套顺序解决过好几次莫名其妙的问题,尤其是第 2 步,很多人想不到。别嫌麻烦,路径类问题最忌猜,每一步都有输出对照,根因很快就浮出来。
6.3 最后再分享一个减少误判的小习惯
最后分享一个我坚持了很久的习惯:在命令行里敲关键操作之前,先把 pwd -P 打到屏幕上,确认位置无误再执行。比如要清理某个目录下的临时文件,先 pwd -P,再看磁盘 df -h .,然后再动手。这个习惯在操作大量挂载目录的服务器上尤其重要,能为误删、误格式化这类事故加一道保险。
pwd 这四个字母看着简单,但把它放到软链接、挂载点、脚本语境里,每个细节都能展开不少门道。这篇笔记里写的都是我在实际机器上跑过、验证过的东西,你可以直接照着实验一遍,遇到和自己环境不符的结果,先看看是不是 shell 版本、操作系统差异导致的,再用 type -a 和内建参数逐一排查。命令行这东西,往往就是多敲一次、多对比一次,理解就上了一个台阶。