☰
PATH 里那个「家目录」真的是家目录吗?一个引号埋下的执行隐患
2026/10/14 3:31:23 网站建设 项目流程

文章目录

  • 📖 介绍 📖
  • 🏡 演示环境 🏡
  • 📒 一个引号如何改变 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 found

bash 也没找到它——因为 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 —— 沙箱工具(问题的发现场景)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询