Как я могу иметь более одной возможности в строке сценария?


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, который поддерживает скрипт.

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

Моим первым желанием было изменить строку Шебанга с:

#!/usr/bin/python

в

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

так как это хорошо работает в Bash. Но запуск скрипта дает:

/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
@Bernhard Да, это правда, но к тому времени уже слишком поздно что-то предпринимать. Для третьей ситуации, которую я перечислил выше, прямой запуск сценария (т. ./script.pyЕ.) Заставит python2.4 выполнить его, что заставит ваш код обнаружить, что это была неправильная версия (и, по-видимому, выход). Но есть совершенно хороший python2.5, который можно было бы использовать вместо интерпретатора!
— user108471

2
Используйте скрипт-обертку, чтобы выяснить, есть ли подходящий питон, и execесли да, то выведите ошибку.
— Кевин

1
Так что сделайте это первым делом в том же файле.
— Кевин

9
@ user108471: Вы предполагаете, что строка shebang обрабатывается bash. Это не системный вызов ( execve). Аргументы - строковые литералы, без глобализации, без регулярных выражений. Вот и все. Даже если первый аргумент - «/ bin / bash», а второй параметр («-c ...»), эти параметры не анализируются оболочкой. Они передаются необработанным в исполняемый файл bash, поэтому вы получаете эти ошибки. Плюс, шебанг работает, только если он в начале. Боюсь, вам здесь не повезло (если не считать сценария, который находит интерпретатора python и передает его в ЗДЕСЬ документ, который звучит как ужасный беспорядок).
— Златовласка

Ответы:


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 во всех ситуациях, которые меня интересуют.
— user108471

1
Пожалуйста, посмотрите мое обновление, чтобы узнать, почему я не могу «просто проверить внутри него, запущен ли он в Python 2.4, распечатать некоторую информацию и выйти».
— user108471

Вы правы. Я только что нашел этот вопрос на 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 все, что находится между первой и второй тройной одинарной кавычкой, интерпретируется как строка уровня модуля, а третья тройная одинарная кавычка закомментируется. Остальное обычный Python.

При непосредственном запуске (как в bash-скрипте) первые две одинарные кавычки становятся пустой строкой, а третья одинарная кавычка образует другую строку с четвертой одинарной кавычкой, содержащей только двоеточие. Эта строка интерпретируется Bash как no-op. Все остальное - это синтаксис Bash для перемещения двоичных файлов Python в / usr / bin, выбора последнего и запуска exec, передавая оставшуюся часть файла в виде документа здесь. Документ здесь начинается с тройной одинарной кавычки Python, содержащей только знак хеш / фунт / октоторп. Затем остальная часть сценария интерпретируется как нормальная, пока строка, читающая «# EOF», не завершит документ здесь.

Я чувствую, что это извращение, поэтому я надеюсь, что у кого-то есть лучшее решение.


Может быть, это не так противно в конце концов;) +1
— Златовласка

Недостатком этого является то, что это испортит синтаксическую окраску у большинства редакторов
— Lie Ryan

@LieRyan Это зависит. Мой сценарий использует расширение имени файла .py, которое большинство текстовых редакторов предпочитают при выборе синтаксиса для раскраски. Гипотетически, если бы я переименовать это без .py расширения, я мог бы использовать режимной намекнуть правильный синтаксис (для пользователей Vim по крайней мере) что - то вроде: # ft=python.
— user108471

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в конце, как в этом ответе . Ваш подход в основном такой же, как изложенный здесь .
— Щуберт

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? так нормальные вещи будут выполняться дважды?
— Янус Троелсен

1
execv заменяет текущую исполняемую программу новым загруженным образом программы
— Matt

Это похоже на отличное решение, которое кажется куда менее уродливым, чем то, что я придумал. Я должен попытаться проверить, работает ли он для этой цели.
— user108471

Это предложение почти работает для того, что мне нужно. Единственный недостаток - это то, что я изначально не упомянул: для поддержки Python 2.5 я использую from __future__ import with_statement, что должно быть первым в скрипте Python. Я не думаю, что вы знаете, как выполнить это действие при запуске нового переводчика?
— user108471

Вы уверены, что это должно быть самым первым делом? или просто перед тем, как вы попробуете использовать withs как обычный импорт? Есть ли дополнительная if mypy == 25: from __future__ import with_statementработа перед «нормальными вещами»? Возможно, вам не нужен if, если вы не поддерживаете 2.4.
— Мэтт

0

Вы можете написать небольшой скрипт bash, который проверяет наличие исполняемого файла phython и вызывает его со скриптом в качестве параметра. Затем вы можете сделать этот скрипт целевым объектом линии 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

Это более или менее решение, о котором уже говорилось в последнем абзаце изложения!
— Бернхард

Умно, но для этого требуется, чтобы этот магический скрипт был установлен где-то в каждой системе.
— user108471

@ Бернхард Ооо, пойман. В будущем буду читать до конца. В качестве компенсации я собираюсь улучшить это решение одним файлом.
— Хауке Лагинг

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

@Bernhard Вы хотите сделать поиск в пределах линии Шебанга? Ядро IIRC не заботится о цитировании в строке shebang. В противном случае (если это изменилось) можно сделать что-то вроде `#! / Bin / bash -c do_search_here_without_whitespace ...; exec $ python" $ 1 "Но как это сделать без пробелов?
— Хауке Лагинг
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.