/ usr / bin / env关于shebang line pecularities的问题

时间:2022-09-12 23:24:51

Questions:

  • What does the kernel do if you stick a shell-script into the shebang line?
  • 如果你将shell脚本粘贴到shebang行中,内核会做什么?

  • How does the Kernel know which interpreter to launch?
  • 内核如何知道启动哪个解释器?

Explanation:

I recently wanted to write a wrapper around /usr/bin/env because my CGI Environment does not allow me to set the PATH variable, except globally (which of course sucks!).

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

So I thought, "OK. Let's set PREPENDPATH and set PATH in a wrapper around env.". The resulting script (here called env.1) looked like this:

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

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

which looks like it should work. I checked how they both react, after setting PREPENDPATH:

看起来它应该工作。在设置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

Look absolutely perfect! So far, so good. But look what happens to "Hello World!".

看起来绝对完美!到现在为止还挺好。但看看“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!"

I guess I am missing something pretty fundamental about UNIX.

我想我错过了一些关于UNIX的基本信息。

I'm pretty lost, even after looking at the source code of the original env. It sets the environment and launches the program (or so it seems to me...).

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

2 个解决方案

#1


6  

First of all, you should very seldom use $* and you should almost always use "$@" instead. There are a number of questions here on SO which explain the ins and outs of why.

首先,你应该很少使用$ *而你应该几乎总是使用“$ @”代替。这里有一些关于SO的问题,这些问题解释了原因的来龙去脉。

Second - the env command has two main uses. One is to print the current environment; the other is to completely control the environment of a command when it is run. The third use, which you are demonstrating, is to modify the environment, but frankly there's no need for that - the shells are quite capable of handling that for you.

第二 - env命令有两个主要用途。一是打印当前环境;另一种是在命令运行时完全控制命令的环境。你演示的第三个用途是修改环境,但坦率地说没有必要 - shell能够很好地为你处理。

Mode 1:

env

Mode 2:

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

This version cancels all inherited environment variables and runs command with precisely the environment set by the ENVVAR=value options.

此版本取消所有继承的环境变量,并使用ENVVAR = value选项设置的环境运行命令。

The third mode - amending the environment - is less important because you can do that fine with regular (civilized) shells. (That means "not C shell" - again, there are other questions on SO with answers that explain that.) For example, you could perfectly well do:

第三种模式 - 修改环境 - 不那么重要,因为你可以用常规(文明)炮弹做到这一点。 (这意味着“不是C shell” - 再次,在SO上还有其他问题,答案可以解释这一点。)例如,你可以做得非常好:

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

This insists that $PREPENDPATH is set to a non-empty string in the environment, and then prepends it to $PATH, and exports the new PATH setting. Then, using that new PATH, it executes the python program with the relevant arguments. The exec replaces the shell script with python. Note that this is quite different from:

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

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

Superficially, this is the same. However, this will execute the python found on the pre-existing PATH, albeit with the new value of PATH in the process's environment. So, in the example, you'd end up executing Python from /usr/bin and not the one from /home/pi/prepend/bin.

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

In your situation, I would probably not use env and would just use an appropriate variant of the script with the explicit export.

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

The env command is unusual because it does not recognize the double-dash to separate options from the rest of the command. This is in part because it does not take many options, and in part because it is not clear whether the ENVVAR=value options should come before or after the double dash.

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

I actually have a series of scripts for running (different versions of) a database server. These scripts really use env (and a bunch of home-grown programs) to control the environment of the server:

我实际上有一系列用于运行(不同版本)数据库服务器的脚本。这些脚本确实使用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

#2


4  

You should carefully read the wikipedia article about shebang.

你应该仔细阅读有关shebang的*文章。

When your system sees the magic number corresponding to the shebang, it does an execve on the given path after the shebang and gives the script itself as an argument.

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

Your script fails because the file you give (/usr/bin/env.1) is not an executable, but begins itself by a shebang....

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

Ideally, you could resolve it using... env on your script with this line as a shebang:

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

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

It won't work though on linux as it treats "/usr/bin/env.1 python" as a path (it doesn't split arguments)

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

So the only way I see is to write your env.1 in C

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

EDIT: seems like no one belives me ^^, so I've written a simple and dirty 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;
}

#1


6  

First of all, you should very seldom use $* and you should almost always use "$@" instead. There are a number of questions here on SO which explain the ins and outs of why.

首先,你应该很少使用$ *而你应该几乎总是使用“$ @”代替。这里有一些关于SO的问题,这些问题解释了原因的来龙去脉。

Second - the env command has two main uses. One is to print the current environment; the other is to completely control the environment of a command when it is run. The third use, which you are demonstrating, is to modify the environment, but frankly there's no need for that - the shells are quite capable of handling that for you.

第二 - env命令有两个主要用途。一是打印当前环境;另一种是在命令运行时完全控制命令的环境。你演示的第三个用途是修改环境,但坦率地说没有必要 - shell能够很好地为你处理。

Mode 1:

env

Mode 2:

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

This version cancels all inherited environment variables and runs command with precisely the environment set by the ENVVAR=value options.

此版本取消所有继承的环境变量,并使用ENVVAR = value选项设置的环境运行命令。

The third mode - amending the environment - is less important because you can do that fine with regular (civilized) shells. (That means "not C shell" - again, there are other questions on SO with answers that explain that.) For example, you could perfectly well do:

第三种模式 - 修改环境 - 不那么重要,因为你可以用常规(文明)炮弹做到这一点。 (这意味着“不是C shell” - 再次,在SO上还有其他问题,答案可以解释这一点。)例如,你可以做得非常好:

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

This insists that $PREPENDPATH is set to a non-empty string in the environment, and then prepends it to $PATH, and exports the new PATH setting. Then, using that new PATH, it executes the python program with the relevant arguments. The exec replaces the shell script with python. Note that this is quite different from:

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

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

Superficially, this is the same. However, this will execute the python found on the pre-existing PATH, albeit with the new value of PATH in the process's environment. So, in the example, you'd end up executing Python from /usr/bin and not the one from /home/pi/prepend/bin.

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

In your situation, I would probably not use env and would just use an appropriate variant of the script with the explicit export.

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

The env command is unusual because it does not recognize the double-dash to separate options from the rest of the command. This is in part because it does not take many options, and in part because it is not clear whether the ENVVAR=value options should come before or after the double dash.

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

I actually have a series of scripts for running (different versions of) a database server. These scripts really use env (and a bunch of home-grown programs) to control the environment of the server:

我实际上有一系列用于运行(不同版本)数据库服务器的脚本。这些脚本确实使用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

#2


4  

You should carefully read the wikipedia article about shebang.

你应该仔细阅读有关shebang的*文章。

When your system sees the magic number corresponding to the shebang, it does an execve on the given path after the shebang and gives the script itself as an argument.

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

Your script fails because the file you give (/usr/bin/env.1) is not an executable, but begins itself by a shebang....

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

Ideally, you could resolve it using... env on your script with this line as a shebang:

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

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

It won't work though on linux as it treats "/usr/bin/env.1 python" as a path (it doesn't split arguments)

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

So the only way I see is to write your env.1 in C

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

EDIT: seems like no one belives me ^^, so I've written a simple and dirty 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;
}