DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

sudo source 报 command not found,环境变量没有传给后续命令

source 是 Bash 内建命令,sudo 不会自动调用 Shell 解释它。按变量用于当前终端还是提权程序,选择直接加载或在同一次 Bash 调用中加载并启动程序。

在 Bash 中直接加载文件可以执行,加上 sudo 后却报错:

sudo source /etc/environment
sudo: source: command not found

即使普通用户和 root 的默认 Shell 都是 Bash,也可能出现这个结果。source 是 Bash 内建命令;普通形式的 sudo 需要找到可执行程序,不会因为用户的默认 Shell 是 Bash,就自动把后面的内容交给 Bash 解释。

还有一个容易混在一起的问题:显式启动 Bash 后,加载文件的命令可能不再报错,但随后单独运行的 sudo 命令仍没有所需变量。这涉及环境变量在哪个进程中生效,不能靠给 source 增加权限解决。

下文适用于使用 Bash、通过 sudo 运行命令的环境。其他 Shell 对 source 的支持可能不同;示例显式选择 Bash,不依赖 sudo 的默认 Shell 选择。sudo 是否允许启动 Bash、保留哪些变量,还受本机授权策略约束。

先确认 source 是内建命令

在发生问题的 Bash 终端中检查:

type source
type -t source

常规结果会说明它是内建命令,第二条输出 builtin。如果显示 alias 或 function,先检查同名别名或函数;如果提示找不到,确认当前解释器确实是 Bash。不要仅凭 echo "$SHELL" 就认定当前正在运行的解释器。

关键在于由谁解释命令。执行 source 文件 时,当前 Bash 自己读取文件;执行 sudo source 文件 时,sudo 尝试找到名为 source 的程序。给文件加执行权限、修改 root 的登录 Shell,都没有改变这一点。Bash 的 source 只要求文件可读,并不要求文件本身可执行。

只需要当前终端的变量时,直接加载可信脚本

如果配置供当前用户运行的程序使用,且当前用户能读取它,就在当前 Bash 中加载。假设已有可信文件 /home/example/myapp/session-env.sh,内容为:

export APP_MODE=production

下面的用户名、目录和程序名均是示例,请替换成实际路径:

source /home/example/myapp/session-env.sh &&
  printf 'APP_MODE=%s\n' "$APP_MODE"

随后从这个终端启动的普通子进程可以继承已导出的变量。export 很重要:仅设置 Shell 变量,并不保证新启动的外部程序能从环境中读取它。

不要把加载步骤改成单独执行 bash session-env.sh,再期待当前终端跟着改变。那会启动另一个 Bash;它的环境变化不会写回父 Shell。已经运行的程序也不会因为当前终端重新加载文件而自动获得新值。

这里的配置文件是 Shell 脚本。source 会执行其中的命令,并非只读取键值;加载前应检查文件内容和可信来源,不能直接加载下载来的未知文件。

需要提权的程序,在同一个 Bash 中加载并启动

若目标程序确实必须以 root 身份运行,且 sudo 策略允许这项操作,可以把加载和启动放在同一次 Bash 调用中。以下示例假设管理员已准备好 /etc/myapp/task-env.sh,里面只包含经过审核的 Shell 配置,例如上面的 export APP_MODE=production:

sudo bash -c 'source /etc/myapp/task-env.sh && exec /usr/local/sbin/my-task'

这里 /usr/local/sbin/my-task 是待运行程序的占位路径,不能原样照抄。sudo 启动 Bash,Bash 执行 source,接着用同一个执行环境启动目标程序。&& 让加载命令返回失败时不再启动程序;它不是对脚本中每一行是否成功的全面检查,配置脚本仍应保持简单。

这条命令会以提权身份执行配置文件里的全部 Shell 代码。文件、被加载的其他脚本、目标程序及其父目录都必须由可信管理员控制,不能让不可信用户修改或替换。尤其不要把普通用户可写的项目文件直接代入 root 的加载路径。

外层用单引号,是为了把命令文本交给新启动的 Bash。若把含变量的命令随意换成双引号,变量可能先被当前普通用户的 Shell 展开,造成“检查的是另一个环境”的误判。

在正式启动程序前,可以只核对示例中的非敏感变量:

sudo bash -c 'source /etc/myapp/task-env.sh && printf "APP_MODE=%s\n" "$APP_MODE"'

这仍会执行配置脚本,所以可信性检查必须先完成。不要用整份环境输出核验密钥或令牌;诊断日志也应避免记录敏感值。

如果 sudo 明确拒绝启动 Bash,就停止这条路径,按已有的命令授权或管理员维护的启动方式处理。不要为了加载一个变量就扩大为任意 root Shell 权限。

一次 sudo 加载,不会设置后续 sudo 命令

下面两个调用是分开的,不能把第一条当成第二条的环境初始化:

sudo bash -c 'source /etc/myapp/task-env.sh'
sudo /usr/local/sbin/my-task

第一条里的 Bash 加载后立即退出。第二条由当前终端重新启动,不会继承已经退出的那次 Bash 的环境。因此,加载不再报错,变量也可能仍未生效。需要这份配置的程序,应放回同一条 bash -c 命令中启动。

当前用户先 source 再执行 sudo,也不能保证变量被保留。sudoers 通常启用 env_reset,按策略构造命令环境。不要把 sudo -E 当作固定补丁:请求保留环境仍受策略限制,而且保留整个用户环境可能把无关变量带入提权程序。

/etc/environment 不等同于 Bash 配置脚本

报错里的 /etc/environment 还涉及另一层边界。在 Linux PAM 的 pam_env 用法中,它是简单的 KEY=VAL 环境文件,不是任意 Shell 程序。某些简单内容同时能被 Bash 解析,并不意味着可以把所有 Bash 语法放进去。

sudo 是否读取或合并其中的变量,与系统平台、PAM 配置以及 sudoers 的环境策略有关。不能认为运行一次 sudo source /etc/environment,就能让所有后续 sudo 命令或服务重新加载全局环境。

如果只是一项任务需要配置,优先使用任务专用、格式明确的配置文件和启动方式。不要为了消除这个报错去批量改动 /etc/environment,也不要给它放宽写权限。若已修改全局文件,应按修改前备份恢复,再按对应登录会话或服务的加载规则核验,避免影响其他程序。

以目标程序读到变量为准

修复后的判断要对应实际用途:普通用户场景,在同一终端确认指定变量,再从该终端启动程序;提权场景,在同一次提权 Bash 中确认变量,并让目标程序在这次调用中运行。目标程序还可能自行清理环境或忽略该配置,打印结果正确不等于应用行为必然正确。

采用一次性 Bash 调用时,环境设置随进程退出结束,不会永久修改父终端。若在当前终端直接加载了脚本,需要恢复变量原来的值和导出状态;只有确认该变量原先不存在时,才用 unset APP_MODE 删除它。结束 Shell 也不会撤销脚本或目标程序已经写入的文件、数据或其他外部副作用。