Bash 目录栈:popd 删除一个条目,为什么有时并不切换目录

10-01 3阅读

连续进入几个目录后,popd 好像是“返回上一个目录”。但给它加上索引,当前位置可能完全不动;使用 dirs -c 清空目录栈后,当前路径也依旧存在。问题在于目录栈不是浏览器历史,它的首项始终与当前目录有关,其他条目则是保存的路径。

操作目录栈时应分别观察两件事:条目排列怎样改变,当前工作目录有没有变化。只看命令输出中的一长串路径,很容易把移除中间条目当成切换目录,或者把旋转误认成复制了一份目录。

用三个目录逐步检查状态

将代码保存为 demo.sh,以 bash demo.sh 启动独立 shell。本文在 Bash 5.2.37 验证。不要用 source 运行本实验,因为那会让目录变化发生在当前交互 shell。实验只在自己的临时目录内创建 a、b、c 三个空目录。

Bash 目录栈:popd 删除一个条目,为什么有时并不切换目录

AI概念示意图:表现本文的抽象关系,并非软件截图或真实运行结果。

set -euo pipefail
work=$(mktemp -d)
trap 'rm -rf -- "$work"' EXIT
mkdir "$work/a" "$work/b" "$work/c"
cd "$work/a"
dirs -c
pushd "$work/b" >/dev/null
pushd "$work/c" >/dev/null
[[ "${DIRSTACK[*]}" == "$work/c $work/b $work/a" ]]
popd +1 >/dev/null
[[ "$PWD" == "$work/c" ]]
[[ "${DIRSTACK[*]}" == "$work/c $work/a" ]]
pushd +1 >/dev/null
[[ "$PWD" == "$work/a" ]]
[[ "${DIRSTACK[*]}" == "$work/a $work/c" ]]
pushd -n "$work/b" >/dev/null
[[ "$PWD" == "$work/a" ]]
[[ "${DIRSTACK[*]}" == "$work/a $work/b $work/c" ]]
popd >/dev/null
[[ "$PWD" == "$work/b" ]]
[[ "${#DIRSTACK[@]}" == 2 ]]
dirs -c
[[ "$PWD" == "$work/b" ]]
[[ "${#DIRSTACK[@]}" == 1 ]]
printf 'current=%s saved=%s\n' "${PWD##*/}" "${#DIRSTACK[@]}"
printf 'directory-stack checks passed\n'

索引指向条目,不是要回退几次

前两次 pushd 完成后,栈顺序为 c、b、a。popd +1 删除左起编号一的 b,当前目录仍是 c。这里的编号从零开始,删除一个非首项不会让 shell 自动进入那个被删掉的路径。

接下来的 pushd +1 把 a 旋转到首位,当前目录随之变成 a,c 则留在栈里。它没有删除 a,也没有为 a 新建目录。实验把顺序和 PWD 都断言了一遍,避免把“列表包含哪些路径”误当成完整状态。

带 -n 的 pushd 只调整保存条目而不切换目录,因此加入 b 后,当前目录仍为 a,列表变成 a、b、c。随后不带参数的 popd 删除当前首项并进入新的首项 b,才表现出日常熟悉的返回动作。

清空记录不会把当前位置清空

最后执行 dirs -c 后,DIRSTACK 仍有一个元素,内容就是当前目录 b。输出为 current=b saved=1。这个一不是清理失败,而是当前工作目录仍需要存在;被清掉的是额外保存的目录记录。

正负索引还有方向差别:加号从左数,减号从右数,二者都从零开始。路径一多时,先用 dirs -v 看编号,再决定操作哪个条目,不要把 -1 直觉理解为数组语言中的最后一项。

脚本里每次切换都应检查返回状态,因为保存过的目录可能已被移走或没有访问权限。目录栈提供的是路径导航便利,并不替目录保留生命周期。本文使用新建空目录,目的是把权限和符号链接因素暂时移开。

如果只是到某个目录运行一条命令,独立子 shell 中完成切换通常更容易约束影响范围。需要交互式往返多个位置时,目录栈才更有优势。无论选择哪种方式,先明确是否希望调用者的当前位置发生变化,再选合适的执行边界。

示例为了让断言便于阅读,用空格连接目录栈元素;这些临时路径没有空格,因此可以直接比较。实际脚本若接收任意路径,应逐项比较数组,不要通过拆分这段展示字符串还原路径。目录名本身可以包含空格,展示格式并不是可靠的数据交换格式。

正常 pushd 和 popd 成功后会打印目录栈,所以代码把它们的标准输出送到空设备,让最终结果更容易核对。这只是压住成功信息,不影响返回状态。错误信息仍能出现,脚本也会停下,不会在切换失败后继续把相对路径当成正确目录下的文件。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。