Linux 脚本用 env 传解释器选项时报 No such file or directory
Linux 将 shebang 中 env 后面的程序和选项作为一个参数传入,导致寻找含空格的程序名。核对 GNU env 8.30 的 -S 支持,并用真实脚本入口验证。
Linux 上直接执行脚本,提示 /usr/bin/env: ruby --verbose: No such file or directory,但手工运行 /usr/bin/env ruby --verbose ./script.rb 却正常。若首行写成 #!/usr/bin/env ruby --verbose,应检查 shebang 的参数边界:Linux 会把 ruby --verbose 作为一个参数传给 env,env 因而寻找名字包含空格的程序。
这里讨论 Linux 上通过 ./脚本名 启动、需要给解释器附加选项的脚本。GNU coreutils 8.30 起提供 env -S,可以拆分这个字符串;更早版本以及其他 env 实现不能直接假定支持同一选项。
手工命令能运行,不能证明首行正确
在终端输入下面的命令时,shell 已将程序名和选项拆成两个参数:
/usr/bin/env ruby --verbose ./script.rb
shebang 不经过这样的 shell 拆词。Linux 先取得 /usr/bin/env 这个解释器路径,再将余下的 ruby --verbose 作为一个可选参数,最后附上脚本路径。因此,给这一段再套一层普通引号,并不会让内核替你拆出多个参数。
这与 Ruby 是否安装是两项检查。如果错误里的程序名只有 ruby,仍要查实际执行环境的 PATH;如果名字包含 --verbose,参数被合并就是更直接的线索。错误中若带 \r,则另查首行的 CRLF 字节,不能用 -S 一概处理。
先核对首行所指的那个 env,而非 PATH 中另一个同名程序:
/usr/bin/env --version
/usr/bin/env --help
GNU 版本应能在帮助中找到 -S 或 --split-string。若 --version 本身不被支持,按该实现的帮助核对,不要据此判定它是某个 GNU 版本。
支持 -S 时,只调整解释器启动参数
对于开头的 Ruby 脚本,首行可以改为:
#!/usr/bin/env -S ruby --verbose
-S 由 env 解析,将后面的字符串拆成程序名及参数。它不是要求 Linux 改变 shebang 规则,也不是把整行送进 shell 执行。
下面是一份用于隔离检查的完整 Perl 脚本。测试环境需要已有 Perl,并确认 /usr/bin/env 支持 -S;将内容保存为尚不存在的 env-check.pl,使用 LF 换行:
#!/usr/bin/env -S perl -T -w
print "env argument check\n";
只为该测试文件所有者增加执行权限,再通过文件自身入口调用:
chmod u+x ./env-check.pl
./env-check.pl
预期打印 env argument check,且不再将 perl -T -w 整段当作程序名。只执行 perl ./env-check.pl 会绕过首行启动过程,不能用来验收这项修复。
需要检查拆分结果时,可对这个无业务动作的测试脚本使用调试选项:
/usr/bin/env -vS 'perl -T -w' ./env-check.pl
终端命令里需要引号来把 perl -T -w 保持为 -S 的一个输入参数;shebang 中已有系统提供的单参数边界,不要把终端整条命令连同这些外层引号机械复制进去。调试输出应把 perl、-T 和 -w 分别列出。
旧环境保留明确的调用入口
无法使用 -S 时,可以由任务入口直接传递解释器选项,例如继续使用前面的 /usr/bin/env ruby --verbose ./script.rb。这需要同步修改真正的调用方;只修改手工测试命令,cron 或服务仍直接运行旧脚本,故障不会消失。
另一种方式是使用已经确认存在的解释器绝对路径,并只附加一个选项,例如 #!/usr/bin/ruby --verbose。这里的 /usr/bin/ruby 仅在该主机确实使用这个路径时成立;不要为了修首行创建一个名字含空格的可执行文件,也不要随意替换系统解释器链接。
env 按 PATH 查找解释器,因此同一脚本在个人终端与服务账号下可能选中不同版本。验收要覆盖实际启动账号及其 PATH,保留原脚本和调用配置。若新环境不支持 -S,回退到已核实的显式解释器调用,而不是恢复那条仍把程序和选项合并的首行。
-S 支持自己的引号、转义及环境变量展开规则,但不等于完整 shell 语法。当前问题只需传递固定参数,不要顺手塞入管道、重定向或命令替换。修改范围保持在启动入口;原脚本可能写数据时,先使用无业务动作的检查脚本确认参数拆分,再安排原任务验收。


