Почему лучше использовать «#! /Usr /bin /env NAME» вместо «#! /Path /to /NAME» в качестве моего shebang?
Я заметил, что некоторые скрипты, которые я приобрел у других, имеют shebang #!/path/to/NAME
, тогда как другие (используя тот же инструмент, NAME) имеют shebang #!/usr/bin/env NAME
.
Оба, похоже, работают правильно. В учебниках (например, на Python), похоже, есть предположение, что последний shebang лучше. Но я не совсем понимаю, почему это так.
Я понимаю, что, чтобы использовать последний shebang, NAME должен находиться в PATH, тогда как первый shebang не имеет этого ограничения.
Кроме того, мне кажется, что первым будет лучший shebang, поскольку он точно определяет, где находится NAME. Итак, в этом случае, если существует несколько версий NAME (например, /usr /bin /NAME, /usr /local /bin /NAME), первый случай указывает, что использовать.
Мой вопрос: почему первый шебанг предпочитает второй?
9 ответов
Это не обязательно лучше.
Преимущество #!/usr/bin/env python
заключается в том, что он будет использовать любой исполняемый файл python
, который появляется первым в пользовательском $PATH
.
Недостаток #!/usr/bin/env python
заключается в том, что он будет использовать любой исполняемый файл python
, который появляется первым в пользовательском $ PATH.
Это означает, что сценарий может вести себя по-разному в зависимости от того, кто его запускает. Для одного пользователя он может использовать $PATH
, который был установлен с ОС. Для другого, он может использовать экспериментальный /usr/bin/python
, который работает не совсем корректно.
И если /home/phred/bin/python
установлен только в python
, пользователь, у которого нет /usr/local/bin
в /usr/local/bin
даже не сможет запустить скрипт. (Вероятно, это не слишком вероятно для современных систем, но это может легко произойти для более неясного интерпретатора.)
Указывая $PATH
, вы точно определяете, какой интерпретатор будет использоваться для запуска скрипта в конкретной системе .
Другая потенциальная проблема заключается в том, что трюк #!/usr/bin/python
не позволяет передавать аргументы в intrepreter (кроме имени скрипта, который передается неявно). Этот обычно не является проблемой, но может быть. Многие скрипты Perl написаны с помощью #!/usr/bin/env
, но #!/usr/bin/perl -w
- рекомендуемая замена в эти дни. Сценарии Csh должны использовать use warnings;
- но скрипты csh не рекомендуется в первую очередь. Но могут быть и другие примеры.
У меня есть несколько скриптов Perl в личной системе управления версиями, которую я устанавливаю, когда настраиваю учетную запись в новой системе. Я использую сценарий установщика, который изменяет строку #!/bin/csh -f
каждого скрипта, поскольку он устанавливает ее в моем #!
. (В последнее время мне не приходилось использовать ничего, кроме $HOME/bin
, оно возвращается к временам, когда Perl часто не устанавливался по умолчанию.)
Малая точка: трюк #!/usr/bin/perl
, возможно, является злоупотреблением командой #!/usr/bin/env
, которая изначально была предназначена (как следует из названия) для вызова команды с измененной средой. Более того, некоторые старые системы (включая SunOS 4, если я правильно помню) не имели команды env
в env
. Ни одна из них, скорее всего, не будет представлять серьезной проблемы. /usr/bin
работает так, многие скрипты используют трюк env
, а поставщики ОС вряд ли что-либо сделают, чтобы сломать его , Это может быть проблема может , если вы хотите, чтобы ваш сценарий запускался в действительно старой системе, но тогда вам, вероятно, потребуется изменить его.
Другая возможная проблема (спасибо Sopalajo de Arrierez за указание на комментарии) заключается в том, что задания cron работают с ограниченной средой. В частности, #!/usr/bin/env
обычно имеет значение $PATH
. Поэтому, если каталог, содержащий интерпретатор, не входит в один из этих каталогов, даже если он находится в вашем стандартном /usr/bin:/bin
в пользовательской оболочке, тогда $PATH
трюк не будет работать. Вы можете указать точный путь или добавить строку в свой crontab для установки /usr/bin/env
($PATH
).
Поскольку /usr /bin /env может интерпретировать ваш $PATH
, что делает скрипты более переносимыми.
#!/usr/local/bin/python
Будет запускаться только ваш скрипт, если python установлен в /usr /local /bin.
#!/usr/bin/env python
Будет интерпретировать ваш $PATH
и найти python в любом каталоге вашего $PATH
.
Итак, ваш скрипт более портативен и будет работать без изменений в системах, где python устанавливается как /usr/bin/python
, или /usr/local/bin/python
или даже настраиваемые каталоги (которые были добавлены в $PATH
), например /opt/local/bin/python
.
Переносимость - единственная причина, по которой использование env
предпочтительнее жестко закодированных путей.
Указание абсолютного пути более точно для данной системы. Недостатком является то, что он слишком точен. Предположим, вы понимаете, что системная установка Perl слишком стар для ваших скриптов, и вы хотите использовать свои собственные: вместо этого вы должны отредактировать сценарии и изменить #!/usr/bin/perl
на #! /дома /MyName /бен /Perl. Хуже того, если у вас есть Perl в #!/home/myname/bin/perl
на некоторых машинах, /usr/bin
на других, а /usr/local/bin
на других машинах, тогда вам нужно будет сохранить три отдельные копии сценариев и выполнить соответствующие на каждой машине.
/home/myname/bin/perl
ломается, если #!/usr/bin/env
является плохим, но он делает почти что угодно. Попытка работать с плохим PATH
очень редко полезна и указывает, что вы очень мало знаете о системе, в которой работает скрипт, поэтому вы не можете полагаться на какой-либо абсолютный путь.
Есть две программы, местоположение которых можно опираться почти на каждый вариант unix: PATH
и /bin/sh
. Некоторые неясные и в основном отставные варианты Unix имели /usr/bin/env
, не имея /bin/env
, но вы вряд ли столкнетесь с ними. Современные системы имеют /usr/bin/env
именно из-за его широкого использования в shebangs. /usr/bin/env
- это то, на что вы можете рассчитывать.
Помимо /usr/bin/env
, единственный раз, когда вы должны использовать абсолютный путь в shebang, заключается в том, что ваш скрипт не предназначен для переносимости, поэтому вы можете рассчитывать на известное местоположение для переводчика. Например, скрипт bash, который работает только в Linux, может безопасно использовать /bin/sh
. Сценарий, который предназначен только для использования внутри компании, может опираться на соглашения о местоположении переводчика.
#!/bin/bash
имеет недостатки. Это более гибко, чем указание абсолютного пути, но требует знания имени интерпретатора. Иногда вам может понадобиться запустить интерпретатор, который не находится в #!/usr/bin/env
, например, в местоположении относительно сценария. В таких случаях вы часто можете сделать polyglot script , который может быть интерпретирован как стандартной оболочкой, так и вашим желаемым интерпретатором. Например, чтобы сделать сценарий Python 2 переносимым как в системы, где $PATH
- это Python 3 и python
- это Python 2, а также в системах, где python2
это Python 2 и python
не существует:
python2
В частности, для perl использование #!/usr/bin/env
является плохой идеей по двум причинам.
Во-первых, он не переносится. На некоторых неясных платформах env не находится в /usr /bin. Во-вторых, как заметил кто-то другой, это может вызвать проблемы с передачей аргументов на линии shebang. Максимально портативное решение таково:
#!/bin/sh
exec perl -x $0 "[email protected]"
#!perl
Подробнее о том, как это работает, см. «perldoc perlrun» и то, что он говорит о аргументе -x.
Причина различия между ними заключается в том, как выполняются скрипты.
Использование /usr/bin/env
(которое, как упоминалось в других ответах, не находится в /usr/bin
на некоторых операционных системах), требуется, t просто введите исполняемое имя после #!
- это должен быть абсолютный путь. Это связано с тем, что механизм #!
работает на более низком уровне, чем оболочка. Это часть двоичного загрузчика ядра. Это можно проверить. Поместите это в файл и отметьте его исполняемым:
#!bash
echo 'foo'
Вы обнаружите, что при попытке запустить его выдает такую ошибку:
Failed to execute process './test.sh'. Reason:
The file './test.sh' does not exist or could not be executed.
Если файл отмечен как исполняемый файл и начинается с #!
, ядро (которое не знает о $PATH
) или текущем каталоге: это пользовательская земля концепции) будет искать файл, используя абсолютный путь. Поскольку использование абсолютного пути является проблематичным (как упоминалось в других ответах), кто-то придумал трюк: вы можете запустить /usr/bin/env
(который почти всегда находится в этом месте) для запуска чего-то используя $PATH
.
Добавление другого примера здесь:
Использование env
также полезно, если вы хотите совместно использовать сценарии между несколькими средами rvm
.
Запуск этого в строке cmd показывает, какая рубиновая версия будет использоваться, когда в скрипте используется #!/usr/bin/env ruby
:
env ruby --version
Поэтому, когда вы используете env
, вы можете использовать разные версии ruby через rvm без изменения ваших скриптов.
Есть еще две проблемы с использованием #!/usr/bin/env
-
Он не решает проблему указания полного пути к интерпретатору, он просто перемещает его в
env
.env
больше не гарантируется в/usr/bin/env
, чемbash
гарантированно находится в/bin/bash
или python в/usr/bin/python
. -
env
перезаписывает ARGV [0] с именем интерпретатора (e..g bash или python).Это предотвращает появление имени вашего скрипта, например,
ps
(или изменяет способ /где он появляется) и делает невозможным его поиск, например,ps -C scriptname.sh
[обновление 2016-06-04]
И третья проблема:
-
Изменение PATH выполняется больше , чем редактирование первой строки скрипта, особенно при написании таких изменений тривиально. например:
printf "%s\n" 1 i '#!'$(type -P python2) . w | ed foo.py
Добавление или предварительный просмотр каталога в $ PATH довольно просто (хотя вам все равно нужно отредактировать файл, чтобы сделать его постоянным - ваш
~/.profile
или что-то еще), и это далеко не просто чтобы сценарий THAT редактировал, потому что PATH можно было установить где угодно в скрипте, а не в первой строке).Изменение порядка каталогов PATH значительно сложнее ... и намного сложнее, чем просто редактировать строку
#!
.И у вас все еще есть другие проблемы, связанные с использованием
#!/usr/bin/env
.@jlliagre предлагает в комментарии, что
#!/usr/bin/env
полезен для тестирования вашего скрипта с несколькими версиями интерпретатора, «только изменяя их порядок PATH /PATH»Если вам нужно это сделать, гораздо проще иметь несколько строк
#!
в верхней части вашего скрипта (они всего лишь комментарии в любом месте, кроме самой первой строки) и вырезать /копировать и вставьте тот, который вы хотите использовать, в первую строку.В
vi
это будет так же просто, как перемещение курсора к нужной строке#!
, затем введитеdd1GP
илиY1GP
. Даже если редактор такой же тривиальный, какnano
, для копирования и вставки понадобится несколько секунд, чтобы использовать мышь.
В целом преимущества использования #!/usr/bin/env
в лучшем случае минимальны и, конечно же, даже не приближаются к перевесу недостатков. Даже преимущество «удобства» в значительной степени иллюзорно.
IMO, это глупая идея, которую продвигает особый тип программиста, который думает, что операционные системы не с чем работать, они - проблема , которая будет работать (в лучшем случае игнорируется) .
PS: вот простой скрипт, который сразу меняет интерпретатор нескольких файлов.
change-shebang.sh
:
#!/bin/bash
interpreter="$1"
shift
if [ -z "$(type -P $interpreter)" ] ; then
echo "Error: '$interpreter' is not executable." >&2
exit 1
fi
if [ ! -d "$interpreter" ] && [ -x "$interpreter" ] ; then
shebang='#!'"$(realpath -e $interpreter)" || exit 1
else
shebang='#!'"$(type -P $interpreter)"
fi
for f in "[email protected]" ; do
printf "%s\n" 1 i "$shebang" . w | ed "$f"
done
Запустите его как, например, change-shebang.sh python2.7 *.py
или change-shebang.sh $HOME/bin/my-experimental-ruby *.rb
Если вы пишете чисто для себя или для своей работы, а местоположение переводчика, которого вы вызываете, всегда находится в одном и том же месте, обязательно используйте прямой путь. Во всех остальных случаях используйте #!/usr/bin/env
.
Вот почему: в вашей ситуации интерпретатор python
находился в одном и том же месте, независимо от того, какой синтаксис вы использовали, но для многих людей он мог быть установлен в другом месте. Хотя большинство основных интерпретаторов программирования расположены в /usr/bin/
, многие новые программы по умолчанию имеют значение /usr/local/bin/
.
Я бы даже советовал всегда использовать #!/usr/bin/env
, потому что если у вас несколько версий одного и того же интерпретатора, и вы не знаете, что ваша оболочка будет по умолчанию, тогда вы должны, вероятно, исправить это.
Для удобства переносимости и совместимости лучше использовать
#!/usr/bin/env bash
вместо
#!/usr/bin/bash
Существует множество возможностей, в которых двоичный файл может быть расположен в системе Linux /Unix. Проверьте справочную страницу hier (7) для подробного описания иерархии файловой системы.
FreeBSD, например, устанавливает все программное обеспечение, которое не является частью базовой системы, в /usr /local /. Поскольку bash не является частью базовой системы, двоичный файл bash устанавливается в /usr /local /bin /bash .
Если вам нужен переносимый скрипт bash /csh /perl /whatever, который работает в большинстве дистрибутивов Linux и FreeBSD, вы должны использовать #! /usr /bin /env .
Также обратите внимание, что большинство установок Linux также (жестко) связали двоичный файл env с /bin /env или softlinked /usr /bin в /bin, который должен использоваться не в shebang. Поэтому не используйте #! /Bin /env .