文章目录
- 📖 介绍 📖
- 🏡 演示环境 🏡
- 📒 一个引号如何改变 PATH 的含义 📒
- 🧪 先看复现:同一个配置,两种 Shell 两种命运
- 🔍 引号到底动了什么手脚
- 💻 一条命令自查你的机器
- 💡 这类问题的通用教训
- ⚓ 相关链接 ⚓
📖 介绍 📖
有个细节,很多人用了十几年 Shell 都没留意过:往
.bashrc或.zshrc里加一条export PATH="$PATH:~/.local/bin/",顺手给路径套上引号——看起来严谨,实际上从这一刻起,塞进 PATH 的就不再是你家目录的路径,而是一个字面的~符号。这条配置在 bash 下照样"能用",在另一些 Shell 里却会指向一个完全意想不到的位置。这不是理论推演。最近国外一位安全研究员发文聊过这个坑,我把他的实验在自己机器上逐 shell 跑了一遍,几个 Shell 的表现各不相同:有的把
~在家目录里“顺手”展开,有的却把查找落到当前目录下一个名叫~的文件夹里,程序被原样执行。本文把这件事的来龙去脉讲清楚:引号为什么让
~变味、不同 Shell 各自的兜底逻辑差在哪、怎么用一条命令检查自己的机器、正确写法长什么样。方案不复杂,几分钟就能对照排查完,供大家参考。
🏡 演示环境 🏡
- 🐧 系统:Debian 12
- 🐚 Shell:bash 5.2、dash
- 🔁 复现:本地终端手动执行
📢注意:本文内容为个人学习笔记,仅供参考。附:读者须知
📒 一个引号如何改变 PATH 的含义 📒
🧪 先看复现:同一个配置,两种 Shell 两种命运
实验只需要三步,任何人都能在自己机器上跑一遍。先造一个字面的~目录,并往里面放一个可执行脚本:
mkdir-p'./~/.local/bin'printf'#!/bin/sh\necho PWNED-FROM-RELATIVE-PATH\n'>'./~/.local/bin/kek'chmod+x'./~/.local/bin/kek'注意mkdir的参数要加引号——这样才能保证创建出来的是一个名字就叫~的目录,而不是家目录下的.local(下文实验目录以/tmp/t2为例)。
然后把「带引号的波浪线路径」塞进 PATH,敲命令名运行。重点来了:换不同的 Shell,结果不一样。
在 dash(很多系统的/bin/sh默认指向它)里:
dash-c'cd /tmp/t2; PATH="~/.local/bin/"; kek'# 输出:PWNED-FROM-RELATIVE-PATH命令被执行了,但执行的不是家目录下的任何东西——dash 在当前目录下的./~/.local/bin/里找到了这个脚本(command -v kek会返回~/.local/bin//kek这样的纯相对路径)。
在 bash 里,同样的配置、同样的目录结构:
bash-c'cd /tmp/t2; PATH="~/.local/bin/"; kek'# 输出:bash: kek: command not foundbash 也没找到它——因为 bash 在命令查找阶段会把 PATH 条目开头的~展开成$HOME,它去找的是$HOME/.local/bin/kek。这个行为用command -v验证得很清楚(先在$HOME/.local/bin/放一个同名程序,注意与前面那个字面./~目录区分开):
mkdir-p"$HOME/.local/bin"printf'#!/bin/sh\necho PWNED-FROM-HOME\n'>"$HOME/.local/bin/kek"chmod+x"$HOME/.local/bin/kek"bash-c'PATH="~/.local/bin/"; command -v kek'# 输出:/home/node/.local/bin/kek注意这条和上一条的环境差异:同一个PATH="~/.local/bin/",dash 按字面相对路径找,bash 展开成家目录找。这条配置里的~到底代表什么,取决于你的 Shell——而大多数人是不会去分辨自己交互 Shell 和脚本 Shell 的。
最值得警惕的是 dash 那条:如果你的 PATH 里躺着一条没展开的波浪线路径,那么任何人(或任何脚本)只要在你工作目录里建出对应的./~结构,就能让你的 Shell 优先执行他放进去的程序。执行权来自你的一条配置错误,而它平时毫无症状。
⚠️注意:脚本里
#!/bin/sh的那一行,让这条配置风险加倍——你交互时用的是 bash(展开成家目录,看似没事),sh脚本跑的却是 dash(相对路径查找),同一套配置在两种场景下的行为完全不同。
🔍 引号到底动了什么手脚
两种写法命运不同的根源是 Shell 的展开规则。Bash 手册写得很明白:只有不带引号的波浪线前缀才会做 tilde expansion(波浪线展开,即把~替换成家目录路径);在变量赋值场景里,Bash 还只在=后和每个:后检查不带引号的波浪线。
# 写法 A:加引号 —— 赋值时波浪线不展开exportPATH="$PATH:~/.local/bin/"# 实际存进 PATH 的字符串就是字面的:~/.local/bin/# 写法 B:不加引号 —— 赋值时波浪线会展开exportPATH=$PATH:~/.local/bin/# ~ 被替换成了家目录,这个"能用"很多人的逻辑是「路径要加引号才安全」,这个习惯在普通场景没错,但撞上波浪线恰好相反:引号把展开关掉了,~变成了目录名的一部分。
那写法 B 是不是就该推荐?也不是。它依赖「赋值时整段不带引号」这个脆弱前提——万一哪天路径里混进空格,整个赋值直接散架。两种写法都不是答案,正确姿势只有一个:
# 推荐写法:用 $HOME,引号照加,两全其美exportPATH="$PATH:$HOME/.local/bin/"$HOME是变量展开,不受引号影响;引号继续保护路径里的空格。$HOME 在任何 Shell、任何阶段都是家目录,不用赌哪个 Shell 会替你兜底。这也是原作者文末给出的修法。
⚠️注意:真正拉开差距的是命令查找阶段:bash 会把 PATH 条目开头的
~展开成$HOME再去找可执行文件,dash 则一路按字面处理(所以同一条带引号的配置,在 bash 下查的是家目录,在 dash 下查的是当前目录下的./~)。赋值阶段反而是一致的——bash 和 dash 都会对=或:后不带引号的波浪线做展开,带引号的则都不展开。bash --posix模式又贴近 dash 行为(command -v kek返回/tmp/t2/~/.local/bin/kek这样的相对路径)。本机没装 zsh,zsh 行为请以你自己机器实测为准。别赌哪个 Shell 替你兜底,统一用$HOME最稳。
💻 一条命令自查你的机器
检查方式很简单,把 PATH 按冒号拆开逐条看有没有波浪线:
echo"$PATH"|tr':''\n'|grep'~'有输出,说明 PATH 里躺着没展开的波浪线条目,去.bashrc、.zshrc、.profile里把对应的~换成$HOME即可;没有任何输出,说明你的机器暂时干净。
顺带一提,我实际跑自查时本机 PATH 是干净的——但这个坑的特点是「加了坏配置当下毫无症状」:bash 用户照样能跑家目录里的命令,脚本场景才暴露;直到某天真出怪事再回头翻配置,往往已经忘了自己什么时候动过手。所以值得现在花十秒钟查一次,而且要在实际会用到那条 PATH 的 Shell 里查——在 bash 里干净,不代表sh脚本的 PATH 也干净。
💡 这类问题的通用教训
这次的事故根子上是三个知识点叠出来的:波浪线展开只发生在特定位置、引号会关掉它、不同 Shell 对同一条 PATH 条目的处理时机又不一样。单独看每一条都不冷门,叠在一起就成了「配置看起来没问题、行为完全不符合直觉」的经典案例。
能带走的东西可以归纳成三条习惯:写 PATH 追加一律用"$PATH:$HOME/..."而不是波浪线;从网上抄配置时别只看结果对不对,用echo "$PATH" | tr ':' '\n'看一眼每条路径实际长什么样;给关键目录里的可执行文件留个心眼——PATH 的匹配是从左到右的,排在前面的目录会赢。
安全研究员发现这个问题的方式也挺有意思:他是在试用一个叫 nono 的沙箱工具时,工具主动警告「沙箱可写的 PATH 条目里有~/.local/bin/」才顺藤摸瓜的。工具的防御性检查逮住了人眼漏掉的细节,这本身也值得借鉴——关键配置让程序替人盯一遍,比相信自己的眼睛靠谱。
📌注意事项:
- 每个人的 Shell 配置和环境不同,本文方案请按自己的实际情况调整,不能保证照搬有效。
- 修改
.bashrc/.zshrc前建议先备份一份,改完开新终端验证再关旧会话。- 本文实验基于 bash 5.2 与 dash 实测,zsh 与
bash --posix行为请以你本机实测为准。
⚠️说明:本文基于 bash 5.2、dash 在 2026 年 10 月的版本与文档整理,文中引用的工具与手册为整理时点的版本。开源项目与手册的内容后续可能调整或变化,请前往官方仓库与文档查看最新内容并自行核实,一切以官网当前版本为准;本文所述仅代表整理时点的实测情况,仅供参考。
⚓ 相关链接 ⚓
- XiaoqiangClub
- 定时任务总在奇怪的时间跑?Cron 表达式这 5 个坑,我逐个实测了一遍(同属「配置看着没问题、行为不符合直觉」类排查)
- The tilde in your PATH may not be your HOME(disconnect3d 博客原文)
- Bash 参考手册:Tilde Expansion
- Bash 参考手册:Command Search and Execution
- nono —— 沙箱工具(问题的发现场景)