/usr/bin/env: ruby -ws: No such file or directory

January 31, 2008 – 3:52 pm

起因

我试图用 sow 工具来创建一个 gem ,但是当我执行 sow 的时候却得到了一个错误提示:

$ sow rmmseg
/usr/bin/env: ruby -ws: No such file or directory

经过

这是为什么呢?我首先找到了 sow 脚本的位置,发现它第一行的 Sha-Bang 如下:

#!/usr/bin/env ruby -ws

我立即尝试在 shell 下输入 /usr/bin/env ruby -ws ,发现一切正常,再创建了一个具有同样 Sha-Bang 的脚本,直接执行,却又同样的错误。这是为什么呢?

直接在 shell 下输入命令,命令的解析是由 shell 来完成的,它会把 ruby-ws 作为两个参数传递给 /usr/bin/env ,而直接执行脚本文件时,内核通过 #! 魔数发现这是一个脚本文件,然后它会解析 Sha-Bang 的内容,进而运行程序。内核不会像 shell 那样做复杂的解析,甚至是及其简单的,下面是 2.6.23.9 内核的 fs/binfmt_script.c 文件里的一段代码,就是用于解析 Sha-Bang 的:

27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
 * This section does the #! interpretation.
 * Sorta complicated, but hopefully it will work.  -TYT
 */
 
bprm->sh_bang++;
allow_write_access(bprm->file);
fput(bprm->file);
bprm->file = NULL;
 
bprm->buf[BINPRM_BUF_SIZE - 1] = '\0';
if ((cp = strchr(bprm->buf, '\n')) == NULL)
        cp = bprm->buf+BINPRM_BUF_SIZE-1;
*cp = '\0';
while (cp > bprm->buf) {
        cp--;
        if ((*cp == ' ') || (*cp == '\t'))
                *cp = '\0';
        else
                break;
}
for (cp = bprm->buf+2; (*cp == ' ') || (*cp == '\t'); cp++);
if (*cp == '\0')
        return -ENOEXEC; /* No interpreter name found */
i_name = cp;
i_arg = NULL;
for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++)
        /* nothing */ ;
while ((*cp == ' ') || (*cp == '\t'))
        *cp++ = '\0';
if (*cp)
        i_arg = cp;
strcpy (interp, i_name);

可以看到确实只是简单地解析了一个参数。就是说,最多只能有一个可选的参数,因此 ruby -ws 被当作一个参数传递给了 /usr/bin/envenv 试图在 PATH 中寻找 ruby -ws 这个文件,当然这是不存在的,所以就出错了。

为什么会有这样的行为呢?是因为偷懒吗?我想不是,搜索了一下,发现有人说是由于各种标准的原因,有人说是大部分 Unix 程序都是这样的行为,我也没有找到相关的标准,也不想去深究了,总之就是万恶的“历史原因”吧!

结果

要解决这个问题,我面临三个选择:

  1. 修改内核源代码。这不太现实,一来修改一下代码还要自己编译内核,费时费力,而且既然内核故意要这样写,多是有它自己的用意的。据说 FreeBSD 的内核原来是可以正确解析多个参数的,后来由于标准原因也改成了这种笨拙的实现了。
  2. 修改 /usr/bin/env ,让它重新解析一遍参数,再按照空格分开。这立即被否决了,这样修改以后说不定许多原来能正常工作的脚本会产生不可预料的行为。
  3. 修改 sow 脚本。这也不是太好, sow 脚本是由包管理器自动安装更新的,修改以后下次还会被覆盖掉,但是相比之下这似乎是最好的解决办法了,或许我可以给作者发一封邮件,让他考虑一下在后续版本里修复这个问题。

唉!这个应该怪谁呢? :(

Update: 根据 binaryxie 提供的链接,看到那个表格中“each arg in its own argv[x]”一栏可以看到只有很少的几个系统实现了完全解析。

  1. 2 Responses to “/usr/bin/env: ruby -ws: No such file or directory”

  2. ……, 还真有这个问题,平时还真没注意过. 不过这么高危的写法,怎么也不应该出现在一个比较通用的项目里面吧……

    By is on Jul 13, 2008

  3. @is,
    比如 hoe 就是一个,在 Ruby world 里还挺常用啊,不过也许许多开发人员是 Mac 环境的缘故吧。Mac 的环境并不是和 Linux 完全一样呢。

    By pluskid on Jul 13, 2008

Post a Comment