Почему лучше использовать «#! / Usr / bin / env NAME» вместо «#! / Path / to / NAME» в качестве моего шебанга?


460

Я заметил, что некоторые сценарии, которые я приобрел у других, имеют шебанг, в #!/path/to/NAMEто время как другие (использующие тот же инструмент, NAME) имеют шебанг #!/usr/bin/env NAME.

Кажется, что оба работают правильно. В руководствах (например, по Python), кажется, есть предположение, что последний шебанг лучше. Но я не совсем понимаю, почему это так.

Я понимаю, что для использования последнего шебанга ИМЯ должна быть в ПУТИ, тогда как у первого шебанга нет этого ограничения.

Кроме того, мне кажется, что первым будет лучший шебанг, поскольку он точно указывает, где находится NAME. Таким образом, в этом случае, если существует несколько версий NAME (например, / usr / bin / NAME, / usr / local / bin / NAME), в первом случае указывается, какой из них использовать.

Мой вопрос: почему первый шебанг предпочтительнее второго?


12
Смотрите этот ответ ...
Jasonwryan

@ TheGeeko61: В моем случае что-то сломалось, а некоторые переменные отсутствовали в env. Поэтому я предлагаю использовать этот шебанг, чтобы проверить, правильно ли загружен env.
Gigamegs

Ответы:


463

Это не обязательно лучше.

Преимущество #!/usr/bin/env pythonзаключается в том, что он будет использовать любой pythonисполняемый файл, который появляется первым у пользователя $PATH.

Недостаток в #!/usr/bin/env pythonтом , что он будет использовать любой pythonисполняемый файл появляется первым в пользователе $PATH.

Это означает, что скрипт может вести себя по-разному в зависимости от того, кто его запускает. Для одного пользователя он может использовать тот, /usr/bin/pythonкоторый был установлен вместе с ОС. Для другого, это могло бы использовать эксперимент /home/phred/bin/python, который не совсем корректно работает.

И если pythonтолько установлен в /usr/local/bin, пользователь , который не имеет /usr/local/binв $PATHдаже не в состоянии запустить скрипт. (Это вероятно не слишком вероятно в современных системах, но это может легко произойти для более неясного интерпретатора.)

Указывая, #!/usr/bin/pythonвы точно указываете, какой интерпретатор будет использоваться для запуска сценария в конкретной системе .

Другая потенциальная проблема заключается в том, что #!/usr/bin/envуловка не позволяет передавать аргументы интерпретатору (кроме имени сценария, который передается неявно). Это , как правило , не является проблемой, но это может быть. Многие Perl-скрипты написаны с использованием #!/usr/bin/perl -w, но use warnings;это рекомендуемая замена в наши дни. Сценарии Csh должны использоваться, #!/bin/csh -fно сценарии csh не рекомендуются в первую очередь. Но могут быть и другие примеры.

У меня есть несколько сценариев Perl в персональной системе контроля версий, которые я устанавливаю при настройке учетной записи в новой системе. Я использую #!установочный скрипт, который изменяет строку каждого скрипта, так как он устанавливает его в моем $HOME/bin. (Мне не приходилось использовать ничего, кроме #!/usr/bin/perlпоследнего времени; это восходит к временам, когда Perl часто не устанавливался по умолчанию.)

Незначительный момент: #!/usr/bin/envхитрость, возможно, заключается в злоупотреблении envкомандой, которая изначально была предназначена (как следует из названия) для вызова команды с измененной средой. Кроме того, некоторые старые системы (включая SunOS 4, если я правильно помню) не имели envкоманды /usr/bin. Ни один из них, вероятно, не будет серьезной проблемой. envработает так, многие скрипты используют эту #!/usr/bin/envхитрость, и поставщики ОС вряд ли что-то предпримут, чтобы ее сломать. Это может быть проблемой, если вы хотите, чтобы ваш скрипт работал на действительно старой системе, но тогда вам, вероятно, придется все равно его изменить.

Другая возможная проблема (спасибо Sopalajo de Arrierez за указание на это в комментариях) заключается в том, что задания cron выполняются в ограниченной среде. В частности, $PATHкак правило, что-то вроде /usr/bin:/bin. Таким образом, если каталог, содержащий интерпретатор, не находится в одном из этих каталогов, даже если он находится по умолчанию $PATHв пользовательской оболочке, этот /usr/bin/envтрюк не сработает. Вы можете указать точный путь, или вы можете добавить строку в ваш crontab, чтобы установить $PATH( man 5 crontabдля деталей).


4
Если / usr / bin / perl равен perl 5.8, $ HOME / bin / perl равен 5.12, а сценарий, требующий 5.12 жестких кодов / usr / bin / perl в shebangs, запускать сценарий может быть очень сложно. Я редко видел, чтобы / usr / bin / env perl grab perl из PATH был проблемой, но это часто очень полезно. И это намного красивее, чем взломать exec!
Уильям Перселл

8
Стоит отметить, что если вы хотите использовать конкретную версию интерпретатора, / usr / bin / env все еще лучше. просто потому, что на вашем компьютере обычно установлено несколько версий интерпретатора с именами perl5, perl5.12, perl5.10, python3.3, python3.32 и т. д., и если ваше приложение было протестировано только на этой конкретной версии, вы все равно можете указать #! / usr / bin / env perl5.12 и все будет в порядке, даже если пользователь установил его где-то необычно. По моему опыту, «python» - это обычно просто символическая ссылка на стандартную версию системы (не обязательно самая последняя версия в системе).
root

6
@root: для Perl, use v5.12;служит некоторым из этих целей. И #!/usr/bin/env perl5.12потерпит неудачу, если система имеет Perl 5.14, но не 5.12. Для Python 2 против 3, #!/usr/bin/python2и #!/usr/bin/python3, скорее всего, будет работать.
Кит Томпсон,

3
@GoodPerson: Предположим, я пишу Perl-скрипт для установки в /usr/local/bin. Я знаю, что это работает правильно /usr/bin/perl. Я понятия не имею , работает ли он с тем perlисполняемым файлом, который какой-либо случайный пользователь имеет в своем распоряжении $PATH. Может быть, кто-то экспериментирует с какой-то древней версией Perl; потому что я указал #!/usr/bin/perl, мой сценарий (который пользователь не обязательно даже знает или не заботится о сценарии Perl) не перестанет работать.
Кит Томпсон

5
Если это не сработает /usr/bin/perl, я выясню это очень быстро, и владелец системы / администратор системы обязан поддерживать его в актуальном состоянии. Если вы хотите запустить мой скрипт со своим собственным perl, не стесняйтесь захватывать и изменять копию или вызывать ее через perl foo. (И вы можете подумать о том, что 55 человек, проголосовавших за этот ответ, тоже знают одну или две вещи. Конечно, возможно, что вы правы, и они все неправы, но я бы не поспорил.)
Кит Томпсон

101

Потому что / 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предпочтительнее жестко закодированных путей.


19
Пользовательские каталоги для pythonисполняемых файлов особенно распространены по мере virtualenvувеличения использования.
Xiong Chiamiov

1
А как #! python, почему это не используется?
Кристианп

6
#! pythonне используется, потому что вы должны находиться в том же каталоге, что и двоичный файл python, поскольку голое слово pythonинтерпретируется как полный путь к файлу. Если в текущем каталоге нет двоичного файла python, вы получите сообщение об ошибке типа bash: ./script.py: python: bad interpreter: No such file or directory. Это так же, как если бы вы использовали#! /not/a/real/path/python
Тим Кеннеди

47

Указание абсолютного пути является более точным в данной системе. Недостатком является то, что это слишком точно. Предположим, вы понимаете, что системная установка Perl слишком устарела для ваших сценариев, и вы хотите вместо этого использовать свой собственный: тогда вам нужно отредактировать сценарии и перейти #!/usr/bin/perlна #!/home/myname/bin/perl. Хуже того, если у вас есть Perl /usr/binна некоторых машинах, /usr/local/binна других и /home/myname/bin/perlна других машинах, вам придется поддерживать три отдельные копии сценариев и выполнять соответствующую копию на каждой машине.

#!/usr/bin/envломается, если PATHплохо, но так же почти все. Попытка работать с плохим PATHочень редко бывает полезна и указывает на то, что вы очень мало знаете о системе, в которой работает скрипт, поэтому вы все равно не можете полагаться на какой-либо абсолютный путь.

Есть две программы, на местоположение которых вы можете положиться практически на каждый вариант Unix: /bin/shи /usr/bin/env. У некоторых непонятных и в основном вышедших на пенсию вариантов Unix /bin/envне было /usr/bin/env, но вы вряд ли столкнетесь с ними. Современные системы имеют /usr/bin/envименно из-за его широкого использования в Шебанге. /usr/bin/envэто то, на что вы можете рассчитывать.

Кроме того /bin/sh, единственный раз, когда вы должны использовать абсолютный путь в шебанге, это когда ваш сценарий не должен быть переносимым, поэтому вы можете рассчитывать на известное место для интерпретатора. Например, можно безопасно использовать скрипт bash, который работает только в Linux #!/bin/bash. Сценарий, предназначенный только для внутреннего использования, может опираться на соглашения о расположении домашнего переводчика.

#!/usr/bin/envесть минусы. Это более гибко, чем указание абсолютного пути, но все же требует знания имени интерпретатора. Иногда вам может понадобиться запустить интерпретатор, которого нет в $PATH, например, в месте, относящемся к сценарию. В таких случаях вы часто можете создать сценарий полиглота, который может интерпретироваться как стандартной оболочкой, так и желаемым интерпретатором. Например, чтобы сделать скрипт Python 2 переносимым как на системы, где pythonесть Python 3 и python2Python 2, так и на системы, где pythonесть Python 2 и python2не существует:

#!/bin/sh
''':'
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0" "$@"
else
  exec python "$0" "$@"
fi
'''
# real Python script starts here
def …

22

В частности, для Perl использование #!/usr/bin/envявляется плохой идеей по двум причинам.

Во-первых, это не портативно. На некоторых малоизвестных платформах env отсутствует в / usr / bin. Во-вторых, как отметил Кит Томпсон , это может вызвать проблемы с передачей аргументов по линии Шебанга. Максимально портативное решение:

#!/bin/sh
exec perl -x "$0" "$@"
#!perl

Для получения подробной информации о том, как это работает, смотрите perldoc perlrun и что он говорит об аргументе -x.


Именно тот ансер, который я искал: как написать «шебанг» для сценария perl, который позволит передавать дополнительные aruments в perl (последняя строка в вашем примере принимает дополнительные aruments).
AmokHuginnsson

15

Причина различия между ними заключается в том, как выполняются сценарии.

Использование /usr/bin/env(которое, как упоминалось в других ответах, /usr/binна некоторых ОС отсутствует) является обязательным, потому что вы не можете просто поставить имя исполняемого файла после #!- это должен быть абсолютный путь. Это потому, что #!механизм работает на более низком уровне, чем оболочка. Это часть двоичного загрузчика ядра. Это можно проверить. Поместите это в файл и отметьте его как исполняемый:

#!bash

echo 'foo'

Вы обнаружите, что он печатает ошибку, как это, когда вы пытаетесь запустить его:

Failed to execute process './test.sh'. Reason:
The file './test.sh' does not exist or could not be executed.

Если файл помечен как исполняемый и начинается с a #!, ядро ​​(которое не знает о $PATHтекущем каталоге или о текущем каталоге: это понятия пользовательской области) будет искать файл, используя абсолютный путь. Поскольку использование абсолютного пути проблематично (как упоминалось в других ответах), кто-то придумал хитрость: вы можете запустить /usr/bin/env(который почти всегда находится в этом месте), чтобы запустить что-то, используя $PATH.


11

Есть еще две проблемы с использованием #!/usr/bin/env

  1. Это не решает проблему указания полного пути к интерпретатору, оно просто перемещает его в env.

    envне более гарантированно находится в, /usr/bin/envчем bashгарантированно находится в /bin/bashили в Python /usr/bin/python.

  2. env перезаписывает ARGV [0] именем интерпретатора (например, bash или python).

    Это предотвращает появление имени вашего скрипта, например, в psвыводе (или изменяет, как / где оно появляется) и делает невозможным его поиск, например,ps -C scriptname.sh

[обновление 2016-06-04]

И третья проблема:

  1. Изменение вашего PATH - это больше, чем просто редактирование первой строки скрипта, особенно когда скрипты такого редактирования тривиальны. например:

    printf "%s\n" 1 i '#!'$(type -P python2) . w | ed foo.py

    Присоединение или предварительное ожидание каталога к $ PATH довольно простое (хотя вам все равно нужно отредактировать файл, чтобы сделать его постоянным - вашим ~/.profileили каким-либо другим), и сценарий ЭТОГО редактирования отнюдь не прост, так как PATH можно установить в любом месте скрипта не в первой строке).

    Изменить порядок каталогов PATH значительно сложнее .... и гораздо сложнее, чем просто редактирование #!строки.

    И у вас все еще есть все другие проблемы, которые #!/usr/bin/envдает вам использование.

    @jlliagre предлагает в комментарии, который #!/usr/bin/envполезен для тестирования вашего скрипта с несколькими версиями интерпретатора, «только изменяя их порядок PATH / PATH»

    Если вам нужно это сделать, гораздо проще просто иметь несколько #!строк в верхней части вашего скрипта (это просто комментарии в любом месте, но не самую первую строку) и вырезать / скопировать и вставить ту, которую вы хотите использовать сейчас, чтобы первая строка.

    Во-первых vi, это будет так же просто, как переместить курсор на нужную #!строку, а затем набрать dd1GPили Y1GP. Даже с таким простым редактором, как nanoнесколько секунд, мышь можно использовать для копирования и вставки.


В целом, преимущества использования #!/usr/bin/envминимальны в лучшем случае и, конечно, даже близко не перевешивают недостатки. Даже преимущество «удобства» во многом иллюзорно.

ИМО, это глупая идея способствовала определенному виду программиста , который считает , что операционные системы не то , чтобы работали, они являются проблема для работаться вокруг (или игнорируется в лучшем случае ).

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 "$@" ; 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


4
Никто другой здесь не упомянул проблему ARGV [0]. И никто не упомянул проблему / path / to / env в форме, которая напрямую обращается к одному из аргументов для его использования (то есть, что bash или perl могут находиться в неожиданном месте).
саз

2
Проблема с путём интерпретатора легко решается системным администратором с символическими ссылками или пользователем, редактирующим их скрипт. Это, безусловно, недостаточно серьезная проблема, чтобы побудить людей мириться со всеми остальными проблемами, вызванными использованием env в строке shebang, которые упоминаются здесь и по другим вопросам. Это продвигает плохое решение так же, как поощрение cshсценариев продвигает плохое решение: оно работает, но есть гораздо лучшие альтернативы.
саз

3
не-системные администраторы могут попросить своего системного администратора сделать это. или они могут просто отредактировать скрипт и изменить #!строку.
саз

2
Это именно то, что env помогает избежать: в зависимости от сисадмина или конкретных технических знаний ОС, не связанных с python или чем-то еще.
Jlliagre

1
Хотелось бы, чтобы я проголосовал за это тысячу раз, потому что это действительно отличный ответ в любом случае - если кто-то использует, /usr/bin/envи мне нужно избавиться от него локально, или если они не использовали его, и мне нужно добавить его. Могут возникнуть оба случая, и поэтому сценарии, представленные в этом ответе, являются потенциально полезным инструментом для набора инструментов.
Jstine

8

Добавив еще один пример здесь:

Использование envтакже полезно, если вы хотите, например, совместно использовать сценарии между несколькими rvmсредами.

Выполнение этого в строке cmd показывает, какая версия ruby ​​будет использоваться при #!/usr/bin/env rubyиспользовании внутри скрипта:

env ruby --version

Поэтому, когда вы используете env, вы можете использовать разные версии ruby ​​через rvm, не меняя сценарии.


1
// Отличная идея. Весь смысл использования нескольких интерпретаторов не в том, чтобы нарушать код или иметь код, зависящий от этого конкретного интерпретатора .
Натан Басанезе

4

Если вы пишете исключительно для себя или для своей работы, и местоположение переводчика, которому вы звоните, всегда находится в одном и том же месте, обязательно используйте прямой путь. Во всех остальных случаях используйте #!/usr/bin/env.

И вот почему: в вашей ситуации pythonпереводчик находился в одном и том же месте независимо от того, какой синтаксис вы использовали, но для многих людей он мог быть установлен в другом месте. Хотя большинство основных программных интерпретаторов находятся во /usr/bin/многих более новых программах, по умолчанию /usr/local/bin/.

Я бы даже посоветовал всегда использовать, #!/usr/bin/envпотому что, если у вас установлено несколько версий одного и того же интерпретатора, и вы не знаете, к чему по умолчанию будет работать ваша оболочка, то вам, вероятно, следует это исправить.


5
«Во всех остальных случаях использование #! / Usr / bin / env» слишком сильно. // Как указывают @KeithThompson и другие, #! / Usr / bin / env означает, что скрипт может вести себя по-разному в зависимости от того, кто / как работает. Иногда это другое поведение может быть ошибкой безопасности. // Например, НИКОГДА не используйте «#! / Usr / bin / env python» для сценария setuid или setgid, потому что пользователь, вызывающий сценарий, может поместить вредоносное ПО в файл с именем «python» в PATH. Является ли сценарий setuid / gid хорошей идеей - это совсем другой вопрос, но, конечно, ни один исполняемый файл setuid / gid никогда не должен доверять среде, предоставленной пользователем.
Крейзи Глеу

@KrazyGlew, вы не можете установить скрипт в Linux. Вы делаете это через исполняемый файл. И при написании этого исполняемого файла рекомендуется очистить переменные окружения. Некоторые переменные среды также целенаправленно игнорируются .
MayeulC

3

Из соображений портативности и совместимости лучше использовать

#!/usr/bin/env bash

вместо

#!/usr/bin/bash

Существует множество возможностей, когда двоичный файл может находиться в системе Linux / Unix. Проверьте man-страницу hier (7) для подробного описания иерархии файловой системы.

Например, FreeBSD устанавливает все программное обеспечение, которое не является частью базовой системы, в / usr / local / . Поскольку bash не является частью базовой системы, двоичный файл bash устанавливается в / usr / local / bin / bash .

Если вам нужен переносимый скрипт bash / csh / perl / what, который работает в большинстве дистрибутивов Linux и FreeBSD, вы должны использовать #! / Usr / bin / env .

Также обратите внимание, что большинство установок Linux также (жестко) связали двоичный файл env с / bin / env или софт-линклированный / usr / bin с / bin, который не должен использоваться в shebang. Так что не используйте #! / Bin / env .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.