我在试用 nono 代理沙箱工具时,它弹出一条警告:沙箱可写入的 PATH 条目为 ~/.local/bin/,这看起来有些可疑。换句话说,在 ~/.bashrc 或 ~/.zshrc 里写 export PATH="$PATH:~/.local/bin/",并不会把 ~ 展开成家目录路径或 $HOME,因为波浪号到家目录的展开只发生在未加引号的输入中,bash 文档也这样说明。结果加到 PATH 里的不是 /home/
正文
我在试用 nono 这个智能体沙盒工具时,它弹出了一条警告:PATH entries the sandbox can write to: ~/.local/bin/,看起来很可疑。

换句话说,在 ~/.bashrc 或 ~/.zshrc 里这样写:export PATH="$PATH:~/.local/bin/",并不会把 ~ 展开成家目录路径(或 $HOME),因为波浪号到家目录的展开只发生在未加引号的输入中,正如 bash 文档 所说:
如果一个词以未加引号的波浪号(‘~’)开头,那么直到第一个未加引号的斜杠(……)之前的全部字符都被视为波浪号前缀。(……)
Bash 会检查每个变量赋值中紧跟在 ‘:’ 或第一个 ‘=’ 之后的未加引号波浪号前缀,并在这些情况下执行波浪号展开。(……)
所以我们最终加到 PATH 里的不是 /home/<user>/.local/bin/,而是 ./~/.local/bin/。
要修复这个问题,可以这样写:export PATH="$PATH:$HOME/.local/bin/"。
注意,不加引号的版本 export PATH=$PATH:~/.local/bin 在 Bash 和 Zsh 里确实能正常工作,因为在变量赋值中,= 之后以及每个 : 之后也会做波浪号展开。但依赖这种行为很脆弱,比如遇到空格就会破坏变量赋值。
这个问题也可以从下面这段操作看出来:
$ ls -la
total 0drwxr-xr-x@ 2 dc staff 64 Oct 2 13:37 .drwxr-x---+ 105 dc staff 3360 Oct 2 13:37 ..
$ mkdir -p ./~/.local/bin/$ printf '#include <stdio.h>\nint main() { puts("hello"); }'>a.c; gcc a.c -o ./~/.local/bin/kek$ PATH="~/.local/bin/" kekhello
$ tree -f.├── ./~│ └── ./~/.local│ └── ./~/.local/bin│ └── ./~/.local/bin/kek└── ./a.c
4 directories, 2 files

可以看到,kek 这个二进制文件是从 ./~/.local/bin/ 找到并执行的——自始至终都没有涉及家目录。
检查你的 PATH
你可以用下面的命令快速检查自己有没有这个问题:echo "$PATH" | grep -- '~'。
或者,如果想让每个条目单独占一行,可以执行 echo "$PATH" | tr ':' '\n' | grep '~',输出为 ~/.local/bin/。
如果它打印出了任何内容,就去修改你的 .bashrc、.zshrc 或 .profile,把里面的 ~ 替换成 $HOME 就好啦。
评论
讨论以“又一个 Bash 踩坑”的调侃开场,随后主线分歧在于:这到底是使用者的引号/展开知识缺口,还是 Shell、终端与脚本语言耦合导致的设计复杂性。较多人把原因归到波浪号在引号内不会展开、脚本里应改用 $HOME 等老问题;少数声音则批评可移植性和整体设计,深挖 HOME 与系统用户数据库不一致的隐患,并讨论终端是否应与 shell 解耦。
认为波浪号被放进引号就不会展开,是文档没读透的老坑
- 最多人支持这一解释:波浪号展开不是变量替换,双引号会原样保留“~”,使 PATH 里出现字面量家目录;正确做法是不给波浪号加引号,或意识到自己反射性给所有“字符串”加引号反而出错。还有人质疑,既然 ~/.local/bin 没生效、命令跑不起来,本该早发现。(较多人) > “这就是典型的没读文档:展开不是变量,被放进引号里就不会展开。”(@arkt8)
认为脚本里应改用 $HOME,并注意 PATH 顺序与 Shell 可移植性
- 不少人主张:交互式输入用波浪号只是图方便,脚本里一律用 $HOME;文章示例把新目录追加在 $PATH 之后也不够好,更应把 $HOME/.local/bin/ 放在 $PATH 之前。还有人提醒,很多系统的 /bin/sh 其实是 dash,若要用 Bash 特性就该显式写 #!/bin/bash,否则不可移植。(较多人) > “文章里的例子不好,更好的写法是把 $HOME/.local/bin/ 放在 $PATH 之前,而不是追加在末尾。”(@gjvc)
认为还应讲清 ~用户名 与 HOME/getpwent 的分叉
- 少数人指出文章不够完整:除单独的 ~ 外,~root、~pulse、~sddm 这类按用户名展开的形式也常见;更反直觉的是,Bash 和 Zsh 解析单独的 ~ 时看 HOME 环境变量,而 ~用户名 查的是 getpwent 用户数据库,两者可能不同。若临时把 HOME 指到测试目录,~/qux 会变,~foobar/qux 却仍回原家目录,各种语言运行时还各自决定信哪一个,容易引发混乱。(少数人) > “~ 默认按 HOME 展开,~用户名 却查用户数据库,不同运行时还各自选择信哪一个。”(@quotemstr)
认为终端/CLI 应与 Shell 脚本语言解耦
- 少数人把根源指向设计:PATH 不该绑定某个 shell,发行版应提供脱离脚本语言的终端程序,统一处理目录切换、启动 python/git/cargo 等应用、书签与补全;写脚本继续使用 Bash/Fish/Zsh,日常启动程序和文件操作则用普通终端,从根上减少扩展和微妙行为。(少数人) > “希望发行版提供与脚本语言解耦的终端,并有一个不绑定具体 shell 的通用 Path 环境变量。”(@the__alchemist)
主张只允许路径使用有限安全字符
- 少数人提出更激进的规避办法:如果路径只允许字母、数字和 . _ - 等有限字符(连字符都可疑),编码、展开、参数分隔等一大类问题都会消失,而且没见到必须支持其他字符的强理由。(少数人) > “若路径只允许字母数字和少数安全字符,编码、展开、参数分隔等麻烦都会消失。”(@mr_mitm)
有价值的补充
- 排查环境变量来源时,若 bashrc 有十几层嵌套导入,可开启 bash 的导入追踪来逐层定位;这源自 Stack Exchange 上的做法。(@alexpotato)
- 有真实教训:Cursor 早期曾在某人的仓库根目录直接创建名为“~”的目录,清理起来非常吓人,说明波浪号被当成字面目录名会带来实际风险。(@verygoodman)
- 代表社区自嘲情绪的一句玩笑:有人后悔没先读文档,就把“~/”纹在了手腕上。(@duncangh)