7

问题

  • 如果将 shell 脚本插入 shebang 行,内核会做什么?
  • 内核如何知道要启动哪个解释器?

说明

我最近想围绕/usr/bin/env编写一个包装器,因为我的 CGI 环境不允许我设置PATH变量,但全局除外(这当然很糟糕!)。

所以我想,“好的。让我们设置 PREPENDPATH 并将PATH设置在 env 的包装器中。”。生成的脚本(此处称为env.1)如下所示:

#!/bin/bash
/usr/bin/env PATH=$PREPENDPATH:$PATH $*

看起来应该可以。在设置 PREPENDPATH 后,我检查了它们的反应:

$ which /usr/bin/env python
/usr/bin/env
/usr/bin/python

$ which /usr/bin/env.1 python
/usr/bin/env
/home/pi/prepend/bin/python

看起来绝对完美!到现在为止还挺好。但是看看“Hello World!”会发生什么。

# Shebang is #!/usr/bin/env python
$ test-env.py
Hello World!

# Shebang is #!/usr/bin/env.1 python
$ test-env.1.py
Warning: unknown mime-type for "Hello World!" -- using "application/*"
Error: no such file "Hello World!"

我想我错过了一些关于 UNIX 的基本知识。

即使查看了原始env的源代码,我也很迷茫。它设置环境并启动程序(或者在我看来......)。

4

2 回答 2

6

首先,您应该很少使用$*并且几乎总是应该使用"$@"。这里有很多关于 SO 的问题,可以解释原因的来龙去脉。

其次 - 该env命令有两个主要用途。一是打印当前环境;另一种是完全控制命令运行时的环境。您正在演示的第三个用途是修改环境,但坦率地说,没有必要这样做——shell 完全有能力为您处理。

模式一:

env

模式二:

env -i HOME=$HOME PATH=$PREPENDPATH:$PATH ... command args

这个版本取消了所有继承的环境变量,并command在 ENVVAR=value 选项设置的环境下运行。

第三种模式 - 修改环境 - 不太重要,因为您可以使用常规(文明)外壳很好地做到这一点。(这意味着“不是 C shell”——同样,还有其他关于 SO 的问题的答案可以解释这一点。)例如,你可以很好地做到:

#!/bin/bash
export PATH=${PREPENDPATH:?}:$PATH
exec python "$@"

这坚持$PREPENDPATH设置为环境中的非空字符串,然后将其添加到$PATH,并导出新的 PATH 设置。然后,使用新的 PATH,它python使用相关参数执行程序。将execshell 脚本替换为python. 请注意,这与以下内容完全不同:

#!/bin/bash
PATH=${PREPENDPATH:?}:$PATH exec python "$@"

从表面上看,这是一样的。但是,这将python在预先存在的 PATH 上执行找到的,尽管在进程的环境中使用 PATH 的新值。因此,在示例中,您最终将执行 Python from/usr/bin而不是 from /home/pi/prepend/bin

在您的情况下,我可能不会使用env并且只会使用具有显式导出的适当脚本变体。

env命令不寻常,因为它无法识别用于将选项与命令的其余部分分开的双破折号。这部分是因为它不需要很多选项,部分是因为不清楚 ENVVAR=value 选项应该出现在双破折号之前还是之后。

我实际上有一系列用于运行(不同版本)数据库服务器的脚本。这些脚本真正使用env(和一堆自制程序)来控制服务器的环境:

#!/bin/ksh
#
# @(#)$Id: boot.black_19.sh,v 1.3 2008/06/25 15:44:44 jleffler Exp $
#
# Boot server black_19 - IDS 11.50.FC1

IXD=/usr/informix/11.50.FC1
IXS=black_19
cd $IXD || exit 1

IXF=$IXD/do.not.start.$IXS
if [ -f $IXF ]
then
    echo "$0: will not start server $IXS because file $IXF exists" 1>&2
    exit 1
fi

ONINIT=$IXD/bin/oninit.$IXS
if [ ! -f $ONINIT ]
then ONINIT=$IXD/bin/oninit
fi

tmpdir=$IXD/tmp
DAEMONIZE=/work1/jleffler/bin/daemonize
stdout=$tmpdir/$IXS.stdout
stderr=$tmpdir/$IXS.stderr

if [ ! -d $tmpdir ]
then asroot -u informix -g informix -C -- mkdir -p $tmpdir
fi

# Specialized programs carried to extremes:
#   * asroot sets UID and GID values and then executes
#   * env, which sets the environment precisely and then executes
#   * daemonize, which makes the process into a daemon and then executes
#   * oninit, which is what we really wanted to run in the first place!
# NB: daemonize defaults stdin to /dev/null and could set umask but
#     oninit dinks with it all the time so there is no real point.
# NB: daemonize should not be necessary, but oninit doesn't close its
#     controlling terminal and therefore causes cron-jobs that restart
#     it to hang, and interactive shells that started it to hang, and
#     tracing programs.
# ??? Anyone want to integrate truss into this sequence?

asroot -u informix -g informix -C -a dbaao -a dbsso -- \
    env -i HOME=$IXD \
        INFORMIXDIR=$IXD \
        INFORMIXSERVER=$IXS \
        INFORMIXCONCSMCFG=$IXD/etc/concsm.$IXS \
        IFX_LISTEN_TIMEOUT=3 \
        ONCONFIG=onconfig.$IXS \
        PATH=/usr/bin:$IXD/bin \
        SHELL=/usr/bin/ksh \
        TZ=UTC0 \
    $DAEMONIZE -act -d $IXD -o $stdout -e $stderr -- \
    $ONINIT "$@"

case "$*" in
(*v*) track-oninit-v $stdout;;
esac
于 2008-12-11T06:01:53.590 回答
4

您应该仔细阅读有关shebang的维基百科文章。

当您的系统看到对应于 shebang 的幻数时,它会在execveshebang 之后的给定路径上执行一个操作,并将脚本本身作为参数。

您的脚本失败,因为您提供的文件 ( /usr/bin/env.1) 不是可执行文件,而是以 shebang....

理想情况下,您可以env在脚本上使用 ... 解决它,并将此行作为 shebang:

#!/usr/bin/env /usr/bin/env.1 python

它在 linux 上不起作用,因为它将 " /usr/bin/env.1 python" 视为路径(它不会拆分参数)

所以我看到的唯一方法是env.1用 C 写你的

编辑:似乎没有人相信我^^,所以我写了一个简单而肮脏的env.1.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>


const  char* prependpath = "/your/prepend/path/here:";

int main(int argc, char** argv){
  int args_len = argc + 1;
  char* args[args_len];
  const char* env = "/usr/bin/env";
  int i;

  /* arguments: the same */
  args[0] = env;
  for(i=1; i<argc; i++)
    args[i] = argv[i];
  args[argc] = NULL;

  /* environment */
  char* p = getenv("PATH");
  char* newpath = (char*) malloc(strlen(p)
                 + strlen(prependpath));
  sprintf(newpath, "%s%s", prependpath, p);
  setenv("PATH", newpath, 1);

  execv(env, args);
  return 0;
}
于 2008-12-09T14:07:48.353 回答