вопросы по /usr/bin / env, касающиеся особенностей линии shebang
-
20-08-2019 - |
Вопрос
Вопросы:
- Что делает ядро, если вы вставляете shell-скрипт в строку shebang?
- Как ядро узнает, какой интерпретатор запускать?
Объяснение:
Недавно я хотел написать оболочку вокруг /usr/bin/env поскольку моя среда CGI не позволяет мне устанавливать ПУТЬ переменная, за исключением глобальной (что, конечно, отстой!).
Поэтому я подумал: "Хорошо.Давайте установим PREPENDPATH и установим ПУТЬ в обертке вокруг 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
Смотреть абсолютно Идеальный!Пока все идет так хорошо.Но посмотрите, что происходит с "Привет, мир!".
# 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.Он устанавливает среду и запускает программу (по крайней мере, мне так кажется ...).
Решение
Прежде всего, вам следует очень редко использовать $*
и вы почти всегда должны использовать "$@"
вместо этого.Здесь, на 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
, и экспортирует новый параметр ПУТИ.Затем, используя этот новый ПУТЬ, он выполняет python
запрограммируйте с соответствующими аргументами.Тот Самый exec
заменяет сценарий командной строки на python
.Обратите внимание, что это сильно отличается от:
#!/bin/bash
PATH=${PREPENDPATH:?}:$PATH exec python "$@"
На первый взгляд, это одно и то же.Однако это приведет к выполнению python
найден на ранее существовавшем ПУТИ, хотя и с новым значением 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
Другие советы
Вам следует внимательно прочитать статью в Википедии о дело.
Когда ваша система видит магическое число, соответствующее шаблону, она выполняет execve
по заданному пути после shebang и выдает сам скрипт в качестве аргумента.
Ваш скрипт завершается с ошибкой, потому что файл, который вы предоставляете (/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;
}