Як я можу мати кілька можливостей у рядку shebang сценарію?


16

Я знаходжуся в цікавій ситуації, коли у мене є сценарій Python, який теоретично може управляти різними користувачами в різних середовищах (і PATH) і в різних системах Linux. Я хочу, щоб цей сценарій виконувався на якомога більше таких без штучних обмежень. Ось кілька відомих налаштувань:

  • Python 2.6 - це системна версія Python, тому python, python2 та python2.6 всі існують у / usr / bin (і є еквівалентними).
  • Python 2.6 - це системна версія Python, як було зазначено вище, але Python 2.7 встановлений поряд з нею як python2.7.
  • Python 2.4 - це системна версія Python, яку мій сценарій не підтримує. У / usr / bin у нас є python, python2 та python2.4, які еквівалентні, та python2.5, який підтримує сценарій.

Я хочу запустити один і той же виконуваний скрипт python на всіх трьох. Було б добре, якби він спробував спочатку скористатись /usr/bin/python2.7, якщо він існує, потім повернутися до /usr/bin/python2.6, потім повернутися до /usr/bin/python2.5, потім просто помиліться, якщо жодного з них не було. Я не надто зациклювався на цьому, використовуючи найсвіжіший 2.x можливий варіант, хоча, поки він зможе знайти одного з правильних перекладачів, якщо він присутній.

Першим моїм нахилом було змінити лінію shebang з:

#!/usr/bin/python

до

#!/usr/bin/python2.[5-7]

так як це добре працює в баш. Але запуск сценарію дає:

/usr/bin/python2.[5-7]: bad interpreter: No such file or directory

Гаразд, тому я спробую наступне, що також працює в bash:

#!/bin/bash -c /usr/bin/python2.[5-7]

Але знову ж таки, це не вдається:

/bin/bash: - : invalid option

Гаразд, очевидно, що я міг просто написати окремий скрипт оболонки, який знаходить правильний інтерпретатор і запускає скрипт python, використовуючи будь-який інтерпретатор, який він знайшов. Я просто знайшов би клопотання розповсюджувати два файли, де одного вистачить, доки він запущений із встановленим найсучаснішим інтерпретатором python 2. Попросити людей чітко викликати перекладача (наприклад, $ python2.5 script.py) - це не варіант. Покладатися на налаштування PATH користувача певним чином також не є варіантом.

Редагувати:

Перевірка версій у скрипті Python не буде працювати, оскільки я використовую оператор "з", який існує на Python 2.6 (і може використовуватися в 2.5 зfrom __future__ import with_statement ). Це призводить до негайного відмови сценарію із непривітним для користувача SyntaxError, і заважає мені коли-небудь мати можливість перевірити версію спочатку та видати відповідну помилку.

Приклад: (спробуйте це з інтерпретатором Python менше 2,6)

#!/usr/bin/env python

import sys

print "You'll never see this!"
sys.exit()

with open('/dev/null', 'w') as out:
    out.write('something')

Не дуже те, що ви хочете, для цього прокоментуйте. Але ви можете import sys; sys.version_info()перевірити, чи є у користувача необхідну версію python.
Бернхард

2
@ Бернхард Так, це правда, але до цього часу вже пізно щось робити. Для третьої ситуації, яку я перераховував вище, запуск сценарію безпосередньо (тобто ./script.py) призведе до його виконання python2.4, що призведе до того, що ваш код виявить, що він був неправильною версією (і вийшов, імовірно). Але є ідеально хороший python2.5, який міг бути використаний як інтерпретатор!
користувач108471

2
Використовуйте скрипт для обгортки, щоб з’ясувати, чи є відповідний пітон, і execякщо так, то в іншому випадку надрукуйте помилку.
Кевін

1
Тож зробіть це спочатку в одному файлі.
Кевін

9
@ user108471: Ви припускаєте, що лінію shebang обробляє bash. Це не так, це системний виклик ( execve). Аргументи - це рядкові літерали, ні глобалізація, ні регулярні виразки. Це воно. Навіть якщо перший аргумент - "/ bin / bash", а другий параметр ("-c ..."), ці параметри не розбираються оболонкою. Вони передаються без обробки до виконуваного файлу bash, саме тому ви отримуєте ці помилки. Плюс до всього, шебанг працює лише в тому випадку, якщо він знаходиться на початку. Отож, я боюсь, що вам тут не пощастило (окрім сценарію, який знаходить інтерпретатора пітону та подає його ТУТ, що звучить як жахливий безлад).
goldilocks

Відповіді:


13

Я не експерт, але я вважаю, що ви не повинні вказувати точну версію python для використання та залишати цей вибір системі / користувачеві.

Також вам слід використовувати це замість жорсткого кодування до сценарію python:

#!/usr/bin/env python

або

#!/usr/bin/env python3 (or python2)

Він рекомендується при допомоги Python док у всіх версіях:

Як правило, хороший вибір

#!/usr/bin/env python

який шукає інтерпретатора Python у цілому PATH. Однак деякі Unices можуть не мати команди env, тому вам може знадобитися жорсткий код / ​​usr / bin / python як шлях інтерпретатора.

У різних дистрибутивах Python може бути встановлений у різних місцях, тому envвін шукатиме його в PATH. Він повинен бути доступний у всіх основних дистрибутивах Linux і в тому, що я бачу у FreeBSD.

Сценарій повинен бути виконаний з тією версією Python, яка знаходиться у вашій PATH та обраною вами дистрибутивом *.

Якщо ваш скрипт сумісний з усією версією Python, окрім 2.4, ви просто перевірте, чи він працює у Python 2.4 та надрукуйте інформацію та вийдіть.

Більше читати

  • Тут ви можете знайти приклади, в яких місцях Python може бути встановлений в різних системах.
  • Тут ви можете знайти деякі переваги та недоліки використання env.
  • Тут ви можете знайти приклади маніпуляцій з PATH та різні результати.

Зноска

* У Gentoo є інструмент під назвою eselect. Використовуючи його, ви можете встановити за замовчуванням версії різних програм (включаючи Python):

$ eselect python list
Available Python interpreters:
  [1]   python2.6
  [2]   python2.7 *
  [3]   python3.2
$ sudo eselect python set 1
$ eselect python list
Available Python interpreters:
  [1]   python2.6 *
  [2]   python2.7
  [3]   python3.2

2
Я ціную, що те, що я хочу зробити, суперечить тому, що вважалося б хорошою практикою. Те, що ви опублікували, цілком розумно, але в той же час я не прошу просити. Я не хочу, щоб мої користувачі явно вказували мій сценарій на відповідну версію Python, коли цілком можливо виявити відповідну версію Python у будь-яких ситуаціях, які мене цікавлять.
користувач108471

1
Будь ласка, ознайомтесь з моїм оновленням, чому я не можу "просто перевірити всередині нього, якщо воно запускається в Python 2.4 і надрукувати деяку інформацію та вийти".
користувач108471

Ти правий. Я щойно знайшов це питання на SO та тепер я можу побачити, що немає жодного варіанту для цього, якщо ви хочете мати лише один файл ...
pbm

9

На основі декількох ідей з декількох коментарів мені вдалося спіткати воістину некрасивий хакер, який, здається, працює. Сценарій стає bash-скриптом, обгортає сценарій Python і передає його інтерпретатору Python через документ "тут".

На початку:

#!/bin/bash

''':'
vers=( /usr/bin/python2.[5-7] )
latest="${vers[$((${#vers[@]} - 1))]}"
if !(ls $latest &>/dev/null); then
    echo "ERROR: Python versions < 2.5 not supported"
    exit 1
fi
cat <<'# EOF' | exec $latest - "$@"
''' #'''

Тут йде код Python. Тоді в самому кінці:

# EOF

Коли користувач запускає сценарій, для інтерпретації решти сценарію як документа тут використовується остання версія Python від 2,5 до 2,7.

Пояснення деяких шенагігів:

Додані трійкою цитати також дозволяють імпортувати цей самий скрипт як модуль Python (який я використовую для тестування). При імпорті Python все між першим і другим потрійним одноцитатами інтерпретується як рядок на рівні модуля, а третій потрійний одноцитат коментується. Решта - звичайний Пітон.

При запуску безпосередньо (як баш-скрипт зараз) перші два одноцитати стають порожнім рядком, а третій одноціткою утворює інший рядок з четвертим одноцитатам, що містить лише двокрапку. Цей рядок трактується Башем як неоператив. Все інше - синтаксис Bash для поглинання бінарних файлів Python в / usr / bin, вибору останнього та запуску exec, передаючи решту файлу як документ тут. Тут документ починається з потрійної одноцитати Python, що містить лише знак хеш / фунт / октоторп. Решта сценарію потім інтерпретується як звичайна, поки зчитування рядка "# EOF" не припинить тут документ.

Я відчуваю, що це перекручено, тож сподіваюся, що хтось має краще рішення.


Можливо, зрештою це не так противно;) +1
goldilocks

Недоліком цього є те, що воно зіпсує синтаксичне забарвлення для більшості редакторів
Lie Ryan

@LieRyan Це залежить. У моєму сценарії використовується розширення назви файлу .py, яке більшість текстових редакторів надає перевагу, обираючи синтаксис для розфарбовування. Гіпотетично, якби я перейменувати це без .py розширення, я міг би використовувати режимної натякнути правильний синтаксис (для користувачів Vim принаймні) що - щось на кшталт: # ft=python.
користувач108471

7

Рядок shebang може вказувати лише фіксований шлях до перекладача. Існує #!/usr/bin/envхитрість шукати перекладача, PATHале це все. Якщо ви хочете більше вишуканості, вам потрібно буде написати якийсь код оболонки оболонки.

Найбільш очевидне рішення - написати сценарій обгортки. Викличте сценарій python foo.realі зробіть сценарій обгортки foo:

#!/bin/sh
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0.real" "$@"
else
  exec python "$0.real" "$@"
fi

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

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

(Весь текст між '''і '''являє собою рядок Python на топлевелі, що не має ефекту. Для оболонки другий рядок - ''':'це після зняття лапок команда no-op :.)


Друге рішення є приємним, оскільки не потребує додавання # EOFв кінці, як у цій відповіді . В основному ваш підхід такий же, як описано тут .
sschuberth

6

Оскільки ваші вимоги містять відомий список бінарних файлів, ви можете зробити це в Python з наступним. Це не працюватиме однозначною мінорною / основною версією Python, але я не думаю, що це станеться незабаром.

Запускає найвищу версію, розташовану на диску, із впорядкованого, збільшуючи список перетворених пітонів, якщо версія, позначена бінарним файлом, вища, ніж поточна версія виконавця python. Важливим бітом для цього коду є "упорядкований список, що збільшується".

#!/usr/bin/env python
import os, sys

pythons = [ '/usr/bin/python2.3','/usr/bin/python2.4', '/usr/bin/python2.5', '/usr/bin/python2.6', '/usr/bin/python2.7' ]
py = list(filter( os.path.isfile, pythons ))
if py:
  py = py.pop()
  thepy = int( py[-3:-2] + py[-1:] )
  mypy  = int( ''.join( map(str, sys.version_info[0:2]) ) )
  if thepy > mypy:
    print("moving versions to "+py)
    args = sys.argv
    args.insert( 0, sys.argv[0] )
    os.execv( py, args )

print("do normal stuff")

Вибачте за мій скрипучий пітон


чи не буде він продовжувати працювати після execv'ing? тож нормальний матеріал буде виконуватися двічі?
Janus Troelsen

1
execv замінює програму, що виконується в даний час, на щойно завантажений образ програми
Метт

Це виглядає як чудове рішення, яке відчуває себе менш потворним, ніж те, що я придумав. Мені доведеться спробувати перевірити, чи працює він для цієї мети.
користувач108471

Ця пропозиція майже працює для того, що мені потрібно. Єдиний недолік - це те, про що я не згадував спочатку: для підтримки Python 2.5 я використовую from __future__ import with_statement, що повинно бути першим ділом у сценарії Python. Я не думаю, що ви знаєте спосіб виконати цю дію під час запуску нового перекладача?
користувач108471

Ви впевнені , що вона повинна бути дуже насамперед? або безпосередньо перед тим, як спробувати використовувати будь-який withs, як звичайний імпорт ?. Чи працює додатково if mypy == 25: from __future__ import with_statementперед "звичайними речами"? Напевно, вам не потрібно, якщо ви не підтримуєте 2.4.
Метт

0

Ви можете написати невеликий скрипт bash, який перевіряє наявний виконуваний фітон і називає його сценарієм як параметром. Потім ви можете зробити для цього сценарію ціль лінії shebang:

#!/my/python/search/script

І цей сценарій просто робить (після пошуку):

"$python_path" "$1"

Я не знав, чи прийме ядро ​​це непряме сценарій, але я перевірив, і воно працює.

Редагуйте 1

Щоб ця конфузна неприйнятність стала гарною пропозицією, нарешті:

Можна комбінувати обидва сценарії в одному файлі. Ви просто записуєте скрипт python як документ тут у скрипті bash (якщо ви зміните скрипт python, вам потрібно просто знову скопіювати сценарії). Або ви створюєте тимчасовий файл, наприклад в / tmp, або (якщо python це підтримує, я не знаю), ви надаєте сценарій як вхід для інтерпретатора:

# do the search here and then
# either
cat >"tmpfile" <<"EOF" # quoting EOF is important so that bash leaves the python code alone
# here is the python script
EOF
"$python_path" "tmpfile"
# or
"$python_path" <<"EOF"
# here is the python script
EOF

Це більш-менш рішення, про яке вже було сказано в останньому пункті керування!
Бернхард

Розумний, але він вимагає, щоб цей магічний сценарій був встановлений десь у кожній системі.
користувач108471

@Bernhard Oooops, спійманий. Надалі я прочитаю до кінця. В якості компенсації я збираюся вдосконалити його до одного файлового рішення.
Hauke ​​Laging

@ user108471 Магічний скрипт може містити щось подібне: $(ls /usr/bin/python?.? | tail -n1 )але мені не вдалося це вміло використовувати в шебанге.
Бернхард

@Bernhard Ви хочете здійснити пошук у межах shebang? Ядро IIRC не переймається цитуванням у рядку shebang. Інакше (якщо це тим часом змінилося) можна зробити щось на кшталт `#! / Bin / bash -c do_search_here_without_whitespace ...; exec $ python" $ 1 "Але як це зробити без пробілу?
Hauke ​​Laging
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.