/ usr / bin / env вопросы, касающиеся особенностей линий Шебанга - PullRequest
6 голосов
/ 09 декабря 2008

Вопросы

  • Что делает ядро, если вы вставляете 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 . Он устанавливает среду и запускает программу (или мне так кажется ...).

Ответы [ 2 ]

6 голосов
/ 11 декабря 2008

Прежде всего, вы должны очень редко использовать $*, и вы почти всегда должны использовать "$@" вместо этого. Здесь есть несколько вопросов по SO, которые объясняют, почему и почему.

Второе - команда env имеет два основных применения. Одним из них является печать текущей среды; другой - полностью контролировать среду команды при ее запуске. Третье применение, которое вы демонстрируете, - это изменение среды, но, честно говоря, в этом нет необходимости - оболочки вполне способны справиться с этим за вас.

Режим 1:

env

Режим 2:

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

Эта версия отменяет все унаследованные переменные среды и запускает command точно с окружением, установленным параметрами ENVVAR = value.

Третий режим - изменение среды - менее важен, потому что вы можете сделать это нормально с обычными (цивилизованными) оболочками. (Это означает «не оболочка C» - опять же, есть и другие вопросы о SO, ответы на которые объясняют это.) Например, вы вполне могли бы сделать:

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

Это настаивает на том, что $PREPENDPATH задается непустой строкой в ​​среде, а затем добавляется к $PATH и экспортируется новый параметр PATH. Затем, используя этот новый PATH, он запускает программу python с соответствующими аргументами. exec заменяет сценарий оболочки на python. Обратите внимание, что это сильно отличается от:

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

Внешне это то же самое. Однако при этом будет выполнено python, найденное в ранее существовавшем PATH, хотя и с новым значением PATH в среде процесса. Итак, в этом примере вы в конечном итоге выполняете Python из /usr/bin, а не из /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
4 голосов
/ 09 декабря 2008

Вы должны внимательно прочитать статью в Википедии о Шебанг .

Когда ваша система видит магическое число, соответствующее шебангу, она делает execve на заданном пути после шебанга и дает сам сценарий в качестве аргумента.

Ваш скрипт завершается ошибкой, потому что файл, который вы даете (/usr/bin/env.1), не является исполняемым файлом , но начинается с шебанга.

В идеале вы могли бы разрешить это, используя ... env в вашем скрипте с этой строкой как шебанг:

#!/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;
}
...