Почему лучше использовать «#! /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), первый случай указывает, что использовать.

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

355 голосов | спросил TheGeeko61 21 Jam1000000amSat, 21 Jan 2012 05:06:33 +040012 2012, 05:06:33

9 ответов


360

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

Преимущество #!/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).

ответил Keith Thompson 21 Jam1000000amSat, 21 Jan 2012 10:45:39 +040012 2012, 10:45:39
77

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

ответил Tim Kennedy 21 Jam1000000amSat, 21 Jan 2012 05:35:11 +040012 2012, 05:35:11
37

Указание абсолютного пути более точно для данной системы. Недостатком является то, что он слишком точен. Предположим, вы понимаете, что системная установка 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
ответил Gilles 30 Mayam13 2013, 03:38:43
19

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

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

#!/bin/sh
exec perl -x $0 "[email protected]"
#!perl

Подробнее о том, как это работает, см. «perldoc perlrun» и то, что он говорит о аргументе -x.

ответил DrHyde 2 Maypm13 2013, 19:19:01
10

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

Использование /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.

ответил Tiffany Bennett 19 Jam1000000amMon, 19 Jan 2015 05:54:18 +030015 2015, 05:54:18
6

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

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

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

env ruby --version

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

ответил Not Now 23 Jam1000000amMon, 23 Jan 2012 01:35:06 +040012 2012, 01:35:06
5

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

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

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

  2. env перезаписывает ARGV [0] с именем интерпретатора (e..g 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 или что-то еще), и это далеко не просто чтобы сценарий 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

ответил cas 25 +03002015-10-25T09:18:27+03:00312015bEurope/MoscowSun, 25 Oct 2015 09:18:27 +0300 2015, 09:18:27
3

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

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

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

ответил Mauvis Ledford 3 PMpFri, 03 Apr 2015 12:21:42 +030021Friday 2015, 12:21:42
1

Для удобства переносимости и совместимости лучше использовать

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

ответил Mirko Steiner 13 WedEurope/Moscow2017-12-13T13:41:06+03:00Europe/Moscow12bEurope/MoscowWed, 13 Dec 2017 13:41:06 +0300 2017, 13:41:06

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132