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

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')
user108471
источник
Не совсем то, что вы хотите, для комментариев. Но вы можете использовать, 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», не завершит документ здесь.

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

user108471
источник
Может быть, это не так противно в конце концов;) +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")

Извиняюсь за мой неуклюжий питон

Matt
источник
не будет ли продолжать работать после 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 "Но как это сделать без пробелов?
Хауке Лагинг