DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

Bash 路径变量含 ~/ 时,ls 报 No such file or directory

双引号里的波浪号保持字面值,变量展开不会重新解释路径语法。用 HOME 构造主目录路径,并保留参数引用和运行身份边界。

终端里 ls ~/reports 能找到目录,脚本把同一路径存进变量后却报 No such file or directory:

report_dir="~/reports"
ls -- "$report_dir"

这里变量保存的是字面的 ~/reports,并没有保存用户主目录下的绝对路径。Bash 不在双引号内展开波浪号;之后展开变量,也不会把变量内容重新当成一段新的 shell 路径语法。

本文针对 Bash 脚本中代表当前用户主目录的 ~。不把这一写法推广到其他用户的 ~username、应用配置文件或程序自己实现的路径解析。

先看变量内容,再看目录是否存在

下面的检查只显示 Bash 收到的值,不创建或修改目录:

printf 'report_dir=<%s>\n' "$report_dir"
printf 'HOME=<%s>\n' "${HOME-}"

如果第一行仍含 ~,错误发生在路径构造阶段。对 ls 来说,这个相对路径通常意味着当前目录下名为 ~ 的目录,而不是自动请求 shell 再次展开主目录。

不要先用 mkdir、放宽权限或换成 sudo 来试错。那可能在错误的位置创建目录,或让脚本使用另一套用户环境,却没有修复传给程序的路径。

用 HOME 构造路径,同时保留引用

对于脚本当前环境的主目录,可以写成:

#!/usr/bin/env bash

if [[ -z ${HOME-} ]]; then
    printf '%s\n' 'HOME is not set to a non-empty value' >&2
    exit 1
fi

report_dir="$HOME/reports"
if [[ ! -d "$report_dir" ]]; then
    printf 'directory not found: %s\n' "$report_dir" >&2
    exit 1
fi

ls -- "$report_dir"

$HOME 是参数展开,能在双引号中发生;双引号又能让包含空格的目录保持为一个参数。示例中的 reports 是目录名占位,需要换成实际的相对目录。-d 仅确认检查时路径指向目录,不保证后续读取一定成功;最终命令仍可能因权限或路径并发变化报错。

非空检查只避免意外把空 HOME 拼成 /reports,不负责证明 HOME 是业务期待的用户目录。服务、计划任务或切换用户后的环境可能不同,应核对任务的运行身份与明确配置,不要在系统级脚本里默认它等于登录终端的主目录。

波浪号留在引号外也可以,但变量展开仍须引用

简单赋值可以让 Bash 直接识别未引用的波浪号:

report_dir=~/reports
ls -- "$report_dir"

路径后半段包含空格时,可以只引用后半段:

report_dir=~/"Project Reports"
ls -- "$report_dir"

关键是赋值中用于主目录展开的 ~ 没被引号包住,不是“把所有引号删掉”。在使用变量时继续写 "$report_dir";写成 ls $report_dir 会重新引入字段拆分和通配符展开。

已经写成 report_dir="~/reports" 后,只把使用处改成 ls $report_dir,不会让波浪号自动恢复主目录含义。也不应对来自配置、参数或文件的路径使用 eval:需求是传递一个路径,不是执行其中可能出现的 shell 代码。

外部配置里的路径要有明确约定

若路径从 JSON、环境变量或配置文件读取,最简单的约定是保存明确的绝对路径,或保存相对于一个已知根目录的子路径。读取普通字符串不会自动应用 Bash 源码中的所有展开规则;即使字符串写成 $HOME/reports,普通变量展开也不会递归解析其中的 $HOME。

确实需要支持 ~/ 的程序,应在自己的路径解析层明确只处理哪些形式、使用谁的主目录,以及如何处理无效值。不要把当前用户的 $HOME 用来替换任意 ~username,也不要把所有出现的 ~ 都替换掉,文件名中间的波浪号可以是普通字符。

修复后先打印构造出的路径,核对它位于预期用户目录,再用含空格的测试目录验证参数没有被拆开。保留原脚本和配置;这次修改不会修复此前在错误目录里创建的文件,历史输出应单独确认后处理。回退时恢复相应路径设置,不批量移动或删除名称相似的目录。