Виберіть інтерпретатора після запуску сценарію, наприклад, якщо / else всередині hashbang


16

Чи є спосіб динамічного вибору інтерпретатора, який виконує сценарій? У мене є сценарій, що я працюю на двох різних системах, і інтерпретатор, який я хочу використовувати, розташований у різних місцях на двох системах. Що мені доводиться робити - це змінювати лінію хешбангу кожен раз, коли я переключаюсь. Я хотів би зробити щось, що є логічним еквівалентом цього (я розумію, що ця точна конструкція неможлива):

if running on system A:
    #!/path/to/python/on/systemA
elif running on system B:
    #!/path/on/systemB

#Rest of script goes here

Або ще краще було б це, щоб він намагався використовувати перший інтерпретатор, а якщо він не знаходить, він використовує другий:

try:
    #!/path/to/python/on/systemA
except: 
    #!path/on/systemB

#Rest of script goes here

Очевидно, я можу замість цього виконати його як /path/to/python/on/systemA myscript.py або /path/on/systemB myscript.py залежно від того, де я є, але насправді є сценарій обгортки, який запускається myscript.py, тому я хотів би вказати шлях до інтерпретатора python програмно, а не вручну.


3
передача «решти сценарію» у вигляді файлу інтерпретатору без шебангу, а використання ifумови для вас не є варіантом? як,if something; then /bin/sh restofscript.sh elif...
mazs

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

Мені подобається широкий спектр різних відповідей на це питання.
Оскар ского

Відповіді:


27

Ні, це не спрацює. Два символи #!абсолютно повинні бути першими двома символами у файлі (як би ви вказали, що інтерпретував if-операцію?). Це являє собою "магічне число", яке exec()сімейство функцій виявляє, коли вони визначають, чи файл, який вони збираються виконати, це сценарій (який потребує інтерпретатора) або двійковий файл (який не має).

Формат лінії shebang досить суворий. Потрібно мати абсолютний шлях до перекладача і принаймні один аргумент до нього.

Що ви можете зробити env:

#!/usr/bin/env interpreter

Зараз шлях зазвичайenv є , але технічно це не є гарантією. /usr/bin/env

Це дозволяє регулювати PATHзмінну оточення на кожній системі , так що interpreter(будь то bash, pythonабо , perlабо те , що у вас є), не знайдено.

Недоліком такого підходу є те, що переносити аргумент перекладачеві неможливо.

Це означає що

#!/usr/bin/env awk -f

і

#!/usr/bin/env sed -f

навряд чи буде працювати в деяких системах.

Іншим очевидним підходом є використання автоінструментів GNU (або якоїсь більш простої системи шаблонів) для пошуку інтерпретатора та розміщення правильного шляху у файл ./configureкроком, який би виконувався при встановленні сценарію в кожній системі.

Можна також вдатися до запуску сценарію з явним інтерпретатором, але цього, очевидно, ви намагаєтеся уникати:

$ sed -f script.sed

Правильно, я усвідомлюю, що #!потрібно підходити на початку, оскільки це не оболонка, яка обробляє цю лінію. Мені було цікаво, чи є спосіб ввести логіку всередині лінії хешбанг, яка була б еквівалентною if / else. Я також сподівався уникнути возитися з моїми, PATHале, мабуть, це єдині мої варіанти.
dkv

1
Під час використання #!/usr/bin/awkви можете навести точно один аргумент, як #!/usr/bin/awk -f. Якщо двійковий файл, на який ви вказуєте env, аргумент - це бінарний файл, якого ви просите envшукати, як в #!/usr/bin/env awk.
DopeGhoti

2
@dkv Це не так. Він використовує інтерпретатор з двома аргументами, і він може працювати в деяких системах, але точно не на всіх.
Кусалаланда

3
@dkv в Linux він працює /usr/bin/envз одним аргументом awk -f.
ilkkachu

1
@Kusalananda, ні, це було суть. Якщо у вас є скрипт, який викликається foo.awkлінією хешбанг, #!/usr/bin/env awk -fі виклик його ./foo.awkтоді, в Linux, що envбачить, це два параметри awk -fі ./foo.awk. Він насправді шукає /usr/bin/awk -f(тощо) з пробілом.
ilkkachu

27

Ви завжди можете зробити скрипт для обгортки, щоб знайти правильного перекладача для фактичної програми:

#!/bin/bash
if something ; then
    interpreter=this
    script=/some/path/to/program.real
    flags=()
else
    interpreter=that
    script=/other/path/to/program.real
    flags=(-x -y)
fi
exec "$interpreter" "${flags[@]}" "$script" "$@"

Збережіть обгортку у користувачів PATHяк programі відкладіть фактичну програму вбік або з іншим іменем.

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


2
Я exec "$interpreter" "${flags[@]}" "$script" "$@"також бачив, що використовувався для збереження чистоти технологічного дерева. Він також поширює вихідний код.
rrauenza

@rrauenza, ах так, природно, з exec.
ilkkachu

1
Не #!/bin/shбуло б краще замість #!/bin/bash? Навіть якщо /bin/shце посилання на іншу оболонку, вона повинна існувати у більшості (якщо не у всіх) системах * nix, плюс це змусить автора сценарію зробити переносний скрипт, а не потрапити в башизми.
Сергій Колодяжний

@SergiyKolodyazhnyy, хе, я думав про те, щоб згадати про це раніше, але ні, тоді. Масив, який використовується, flags- це нестандартна функція, але вона досить корисна для зберігання змінної кількості прапорів, тому я вирішив зберегти її.
ilkkachu

Або ж за допомогою / бен / ш і просто викличте інтерпретатор безпосередньо в кожній галузі: script=/what/ever; something && exec this "$script" "$@"; exec that "$script" -x -y "$@". Ви також можете додати перевірку помилок на помилки у виконанні.
jrw32982 підтримує Моніку

11

Можна також написати поліглот (поєднати дві мови). / bin / sh гарантовано існує.

Це має і зворотний бік некрасивого коду, і, можливо, деякі /bin/shз них можуть заплутатися. Але його можна використовувати, коли envне існує або існує десь інше, ніж / usr / bin / env. Він також може бути використаний, якщо ви хочете зробити досить вигадливий вибір.

Перша частина сценарію визначає, який інтерпретатор використовувати при запуску з / bin / sh в якості інтерпретатора, але ігнорується при виконанні правильним інтерпретатором. Використовуйте execдля запобігання запуску оболонки більше, ніж першої частини.

Приклад Python:

#!/bin/sh
'''
' 2>/dev/null
# Python thinks this is a string, docstring unfortunately.
# The shell has just tried running the <newline> program.
find_best_python ()
{
    for candidate in pypy3 pypy python3 python; do
        if [ -n "$(which $candidate)" ]; then
            echo $candidate
            return
        fi
    done
    echo "Can't find any Python" >/dev/stderr
    exit 1
}
interpreter="$(find_best_python)"   # Replace with something fancier.
# Run the rest of the script
exec "$interpreter" "$0" "$@"
'''

3
Я думаю, що я бачив один із них раніше, але ідея все ще однаково жахлива ... Але, напевно, ви хочете exec "$interpreter" "$0" "$@"отримати назву самого сценарію і фактичному перекладачеві. (І тоді сподіваюся, що ніхто не збрехав при налаштуванні $0.)
ilkkachu

6
Scala насправді має підтримку поліглот-скриптів у своєму синтаксисі: якщо сценарій Scala починається з #!, Scala ігнорує все до відповідності !#; це дозволяє помістити туди довільно складний код сценарію довільною мовою, а потім execмеханізм виконання Scala зі скриптом.
Йорг W Міттаг

1
@ Jörg W Mittag: +1 для Scala
jrw32982 підтримує Моніку

2

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

#!/usr/bin/ruby -e exec "non-existing-interpreter", ARGV[0] rescue exec "python", ARGV[0]

if True:
  print("hello world!")

Зауважте, що це можна зробити лише тоді, коли інтерпретатор дозволить написати код у першому аргументі. Тут -eі все після того, як це буде дослівно сприйнято як аргумент до рубіну. Наскільки я можу сказати, ви не можете використовувати bash для коду shebang, тому що bash -cвимагає, щоб код був в окремому аргументі.

Я спробував зробити те ж саме з python для коду shebang:

#!/usr/bin/python -cexec("import sys,os\ntry: os.execlp('non-existing-interpreter', 'non-existing-interpreter', sys.argv[1])\nexcept: os.execlp('ruby', 'ruby', sys.argv[1])")

if true
  puts "hello world!"
end

але виходить занадто довго, і Linux (принаймні на моїй машині) обрізає шебанг до 127 символів. Пробачте про використання execвставки нових рядків, оскільки python не дозволяє пробувати винятки або imports без нових рядків.

Я не впевнений, наскільки це портативний, і я б не робив цього за кодом, який поширювався. Тим не менш, це можливо. Можливо, комусь це стане в нагоді для швидкої та брудної налагодження чи чогось іншого.


2

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

Створіть символьне посилання (або тверде посилання за бажанням), щоб вказати на потрібний шлях перекладача. Наприклад, у моїй системі perl і python знаходяться в / usr / bin:

cd /bin
ln -s /usr/bin/perl perl
ln -s /usr/bin/python python

створив би посилання, щоб дозволити хешбангу вирішувати / bin / perl тощо. Це зберігає можливість передачі параметрів і сценаріям.


1
+1 Це так просто. Як ви зазначаєте, це не зовсім відповідає на питання, але, здається, робить саме те, чого хоче ОП. Хоча я думаю, що використання env обходить корінь доступу до кожної машини.
Джо

0

Я зіткнувся з подібною проблемою, як ця сьогодні ( python3вказуючи на версію пітона, яка була занадто старою в одній системі), і придумав підхід, який трохи відрізняється від обговорюваного тут: Використовуйте "неправильну" версію python для завантаження в "правильний". Обмеження полягає в тому, що деяка версія python повинна бути надійно доступною, але цього, як правило, можна досягти, наприклад #!/usr/bin/env python3.

Тож, що я роблю, це почати свій сценарій з:

#!/usr/bin/env python3
import sys
import os

# On one of our systems, python3 is pointing to python3.3
# which is too old for our purposes. 'Upgrade' if needed
if sys.version_info[1] < 4:
    for py_version in ['python3.7', 'python3.6', 'python3.5', 'python3.4']:
        try:
            os.execlp(py_version, py_version, *sys.argv)
        except:
            pass # Deliberately ignore errors, pick first available version

Що це робить:

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