В чем разница между «#! / Usr / bin / env bash» и «#! / Usr / bin / bash»?

372

В чем разница между этими двумя операторами в заголовке Bash-скрипта:

  1. #!/usr/bin/env bash

  2. #!/usr/bin/bash

Когда я сверился со env справочной страницей , я получил это определение:

 env - run a program in a modified environment

Что это значит?

tarrsalah
источник
7
Смотрите этот вопрос и мой ответ .
Кит Томпсон
6
Кто может сказать мне, почему этот вопрос закрыт, «не связан с программированием или разработкой программного обеспечения»?
tarrsalah
1
Я согласен, что это не вне темы, но, вероятно, это дубликат нескольких других вопросов, таких как этот .
Кит Томпсон
16
Этот вопрос не должен быть помечен как не по теме. Нужно только 5 человек с оценкой выше 3000, чтобы пометить его как «по теме», и его можно открыть заново. Это вопрос - конкретно о программировании.
Даниэль-Джеймс W
3
Я в шоке. Шокирован, обнаружив, что в документации Linux много тавтологий. xkcd.com/703 git-man-page-generator.lokaltog.net
allyourcode

Ответы:

323

Выполнение команды сквозной /usr/bin/envимеет преимущество ищет то , что по умолчанию версия программы находится в текущем окр ironment.

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

Одним из недостатков является то, что вы не сможете передать более одного аргумента (например, вы не сможете писать /usr/bin/env awk -f), если вы хотите поддерживать Linux, поскольку POSIX неопределен в отношении того, как должна интерпретироваться строка, и Linux интерпретирует все после первого пространство для обозначения одного аргумента. Вы можете использовать /usr/bin/env -Sв некоторых версиях, envчтобы обойти это, но тогда сценарий станет еще менее переносимым и сломается на довольно недавних системах (например, даже Ubuntu 16.04, если не позже).

Другим недостатком является то, что, поскольку вы не вызываете явный исполняемый файл, у него есть вероятность ошибок и проблем с безопасностью многопользовательских систем (например, если кому-то удалось получить вызываемый файл bashна вашем пути).

#!/usr/bin/env bash #lends you some flexibility on different systems
#!/usr/bin/bash     #gives you explicit control on a given system of what executable is called

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

Алек Беннетт
источник
22
Другим недостатком является то, что вы не можете передать дополнительный аргумент интерпретатору.
Кит Томпсон
1
@KeithThompson: Неверная информация. Вы можете передать параметры соответствующему интерпретатору, используя / usr / bin / env!
Гаурав Агарвал
4
@GauravAgarwal: Не в моей системе. Сценарий , содержащий только эту одну строку: #!/usr/bin/env echo Helloжалуется: /usr/bin/env: echo Hello: No such file or directory. По-видимому, это рассматривается echo Helloкак один аргумент /usr/bin/env.
Кит Томпсон
1
@ AndréLaszlo: envкоманда, безусловно, позволяет передавать аргументы команде. Проблема заключается в семантике #!строки, и это зависит от ядра. Последние ядра Linux допускают такие вещи, как #!/usr/bin/env command argsстарые ядра Linux и другие системы.
Кит Томпсон
1
Почему в блоке кода есть обратные пометки? Разве они не должны быть удалены?
Бенджамин В.
64

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

Dolphiniac
источник
9
По крайней мере, вы должны знать, где находится env :).
ᐅ devrimbaris
1
Отличный ответ. Кратко объясняет, что делает env shebang, вместо того, чтобы сказать «выбирает программу на основе конфигурации вашей системы»
De Novo
13

Вместо того, чтобы явно определять путь к интерпретатору, как /usr/bin/bash/при использовании команды env, интерпретатор ищется и запускается оттуда, где он был впервые найден. Это имеет как плюсы, так и минусы

safay
источник
В большинстве систем они будут одинаковыми, но это зависит от расположения исполняемых файлов bash и env. Не уверен, как это повлияет на переменные среды, хотя.
Safay
2
«Можно указать переводчика без использования env, указав полный путь к интерпретатору. Проблема заключается в том, что в разных компьютерных системах точный путь может быть разным. Вместо этого при использовании env ищется и располагается переводчик в время выполнения сценария. Это делает сценарий более переносимым, но также увеличивает риск выбора неправильного интерпретатора, поскольку он ищет совпадения в каждом каталоге на исполняемом пути поиска. Он также страдает от той же проблемы, что путь к двоичному файлу env также может быть разным для каждой машины. "- Википедия
Майк Кларк
10

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

Почему это было бы полезно? Предположим, вы хотите запустить bashсценарии, для которых требуется bash 4.x или новее, но в вашей системе установлена ​​только bash3.x, и в настоящее время ваш дистрибутив не предлагает более новую версию, или вы не являетесь администратором и не можете изменить то, что установлено в этой системе. ,

Конечно, вы можете скачать исходный код bash и создать свой собственный bash с нуля, разместив его, ~/binнапример. И вы также можете изменить свою $PATHпеременную в своем .bash_profileфайле, чтобы включить ~/binв качестве первой записи ( PATH=$HOME/bin:$PATHпоскольку ~не будет расширяться в $PATH). Если вы сейчас позвоните bash, оболочка сначала будет искать его $PATHпо порядку, поэтому она начинается с того места ~/bin, где она найдет ваш bash. То же самое происходит, если скрипты ищут для bashиспользования #!/usr/bin/env bash, поэтому эти скрипты теперь будут работать в вашей системе с использованием вашей пользовательской bashсборки.

Недостатком является то, что это может привести к неожиданному поведению, например, один и тот же сценарий на одном и том же компьютере может работать с разными интерпретаторами для разных сред или пользователей с разными путями поиска, вызывая все виды головной боли.

Самым большим недостатком envявляется то, что некоторые системы допускают только один аргумент, поэтому вы не можете сделать это #!/usr/bin/env <interpreter> <arg>, так как системы будут рассматривать <interpreter> <arg>как один аргумент (они будут обрабатывать его, как если бы выражение было заключено в кавычки) и, таким образом, envбудут искать интерпретатор с именем <interpreter> <arg>. Обратите внимание, что это не проблема самой envкоманды, которая всегда позволяла передавать несколько параметров, но с помощью анализатора shebang системы, которая анализирует эту строку еще до вызова env. Между тем, это было исправлено в большинстве систем, но если ваш скрипт хочет быть сверхпортативным, вы не можете полагаться, что это было исправлено в системе, которую вы будете использовать.

Он может даже иметь последствия для безопасности, например, если sudoон не был настроен для очистки окружающей среды или $PATHбыл исключен из очистки. Позвольте мне продемонстрировать это:

Обычно /binэто хорошо защищенное место, только там rootможно что-то изменить. Ваш домашний каталог, однако, не любая программа, которую вы запускаете, может внести в нее изменения. Это означает, что вредоносный код может поместить фальшивку bashв какой-то скрытый каталог, изменить его, .bash_profileчтобы включить этот каталог в свой $PATH, так что все используемые скрипты в #!/usr/bin/env bashконечном итоге будут работать с этой фальшивкой bash. Если sudoдержится $PATH, у вас большие проблемы.

Например, рассмотрим инструмент, создающий файл ~/.evil/bashсо следующим содержанием:

#!/bin/bash

if [ $EUID -eq 0 ]; then
  echo "All your base are belong to us..."
  # We are root - do whatever you want to do
fi

/bin/bash "$@"

Давайте сделаем простой скрипт sample.sh:

#!/usr/bin/env bash

echo "Hello World"

Подтверждение концепции (в системе, где sudoхранится $PATH):

$ ./sample.sh
Hello World

$ sudo ./sample.sh
Hello World

$ export PATH="$HOME/.evil:$PATH"

$ ./sample.sh
Hello World

$ sudo ./sample.sh
All your base are belong to us...
Hello World

Обычно все классические оболочки должны быть расположены внутри, /binи если вы не хотите размещать их там по какой-либо причине, на самом деле не является проблемой разместить символическую ссылку, /binкоторая указывает на их реальное местоположение (или, возможно, /binсама является символической ссылкой), поэтому Я бы всегда пошел с #!/bin/shи #!/bin/bash. Слишком много всего сломалось бы, если бы они больше не работали. Дело не в том, что POSIX потребует эти позиции (POSIX не стандартизирует имена путей и, следовательно, он даже не стандартизирует функцию shebang), но они настолько распространены, что даже если система не предлагает /bin/sh, она, вероятно, все еще понимает #!/bin/shи знать, что с ним делать, и может ли это быть только для совместимости с существующим кодом.

Но для более современных, нестандартных, необязательных интерпретаторов, таких как Perl, PHP, Python или Ruby, на самом деле не указано, где они должны быть расположены. Они могут быть в, /usr/binно они могут также находиться в /usr/local/binили в совершенно другой ветви иерархии ( /opt/..., /Applications/...и т. Д.). Вот почему они часто используют #!/usr/bin/env xxxсинтаксис Шебанга.

Mecki
источник
4

Я нахожу это полезным, потому что, когда я не знал об env, до того, как начал писать скрипт, я делал это:

type nodejs > scriptname.js #or any other environment

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

sudo97
источник