Чому краще використовувати "#! / Usr / bin / env NAME" замість "#! / Path / to / NAME" як мій шебанг?


458

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

Здається, обидва працюють належним чином. У навчальних посібниках (наприклад, на Python), здається, є припущення, що останній шебанг краще. Але я не зовсім розумію, чому це так.

Я усвідомлюю, що для використання останнього шебангу, NAME має бути в PATH, тоді як перший shebang не має цього обмеження.

Крім того, мені здається, що першим буде кращий шебанг, оскільки він точно вказує, де знаходиться НАЗВА. Так, у цьому випадку, якщо є кілька версій NAME (наприклад, / usr / bin / NAME, / usr / local / bin / NAME), перший випадок визначає, яку використовувати.

Моє запитання: чому першому шебангу віддається перевага другому?


12
Дивіться цю відповідь ...
Jasonwryan

@ TheGeeko61: У моєму випадку у мене щось зламалось, а деяких змінних не було у env. Тому я пропоную скористатися цим шебангом, щоб перевірити, чи правильно завантажено env.
Gigamegs

Відповіді:


462

Це не обов'язково краще.

Перевага #!/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трюк, і постачальники ОС, швидше за все, не зроблять нічого, щоб зламати його. Це може бути проблемою, якщо ви хочете, щоб ваш скрипт працював у дійсно старій системі, але тоді вам, швидше за все, потрібно буде його змінити.

Інша можлива проблема (завдяки Сопаладжо де Арріерес за те, що він вказав це у коментарях) - це те, що роботи з крон працюють із обмеженим середовищем. Зокрема, $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 схопив perl з PATH - це проблема, але це часто дуже корисно. І це набагато красивіше, ніж exec hack!
Вільям Перселл

8
Варто зазначити, що, якщо ви хочете використовувати певну версію інтерпретатора, / usr / bin / env все ще краще. просто тому , що , як правило , мають кілька версій інтерпретатора , встановлені на вашу машину з ім'ям perl5, perl5.12, perl5.10, python3.3, python3.32 і т.д. , і якщо ваше додаток було протестовано тільки на цій конкретну версію, ви можете вказати #! / usr / bin / env perl5.12 і будь добре, навіть якщо користувач встановив його десь незвично. На мій досвід, "python", як правило, є лише символьним посиланням на стандартну версію системи (не обов'язково останню версію в системі).
корінь

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 переносним як для систем, де pythonPython 3, так і python2Python 2, а також для систем, де pythonPython 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.


Точно я шукав ансер: як написати porable "shebang" для сценарію perl, який дозволить додаткові аргументи, передані в perl (останній рядок у вашому прикладі приймає додаткові аргументи).
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.

Якщо файл позначений виконуваним і починається з а #!, ядро ​​(яке не знає про $PATHабо поточний каталог: це поняття user-land) шукатиме файл, використовуючи абсолютний шлях. Оскільки використання абсолютного шляху є проблематичним (як згадується в інших відповідях), хтось придумав хитрість: Ви можете запустити /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] з іменем інтерпретатора (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чи будь-яким іншим - і сценарій далеко не простий. ЦЕ редагувати, оскільки 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 "$@" ; 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 можуть бути у несподіваному місці).
cas

2
Проблема шляху інтерпретатора легко виправляється системою адміністратора з символьними посиланнями або користувачем, який редагує їх сценарій. Це, звичайно, не є достатньо важливою проблемою, щоб спонукати людей миритися з усіма іншими проблемами, спричиненими використанням ENV на лінії shebang, що згадуються тут, та з інших питань. Це просування поганого рішення таким же чином, як заохочення cshсценаріїв - це погане рішення: воно працює, але є набагато кращі альтернативи.
cas

3
несисадміни можуть попросити свого системного адміністратора зробити це. або вони можуть просто відредагувати сценарій та змінити #!рядок.
cas

2
Саме цього ENV допомагає уникнути: залежно від специфічних технічних знань sysadmin або OS, не пов'язаних з 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 ніколи не повинен довіряти середовищу, що надається користувачем.
Krazy Glew

@KrazyGlew, ви не можете встановити сценарій в Linux. Ви робите це через виконуваний файл. І при написанні цього виконуваного файлу добре очищати змінні середовища. Деякі змінні середовища також цілеспрямовано ігноруються .
MayeulC

3

Для портативності та сумісності їх краще використовувати

#!/usr/bin/env bash

замість

#!/usr/bin/bash

Існує кілька можливостей, де двійковий файл може бути розміщений у системі Linux / Unix. Перегляньте сторінку hier (7) для отримання детального опису ієрархії файлової системи.

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

Коли ви хочете, щоб портативний скрипт bash / csh / perl / god, який працює під більшістю Linux дистрибутивів і FreeBSD, вам слід використовувати #! / Usr / bin / env .

Також візьміть до уваги, що більшість установок Linux також (жорстко) пов’язали env binary з / bin / env або softlinked / usr / bin у / bin, який не повинен використовуватися в shebang. Тому не використовуйте #! / Bin / env .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.