Escolha o interpretador após o início do script, por ex. se / dentro do hashbang

16

Existe alguma maneira de escolher dinamicamente o interpretador que está executando um script? Eu tenho um script que estou executando em dois sistemas diferentes, e o intérprete que eu quero usar está localizado em locais diferentes nos dois sistemas. O que eu acabo tendo que mudar é a linha de hashbang toda vez que eu troco. Eu gostaria de fazer algo que seja o equivalente lógico disso (percebo que essa construção exata é impossível):

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

#Rest of script goes here

Ou ainda melhor seria isso, para que ele tente usar o primeiro intérprete, e se ele não encontrar, use o segundo:

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

#Rest of script goes here

Obviamente, posso executá-lo como %código% ou %código% Dependendo de onde eu estou, mas eu realmente tenho um script wrapper que lança /path/to/python/on/systemA myscript.py , então eu gostaria de especificar o caminho para o interpretador python programaticamente, em vez de manualmente.

    
por dkv 16.05.2017 / 18:37

6 respostas

27

Não, isso não funcionará. Os dois caracteres #! precisam ser os dois primeiros caracteres no arquivo (como você especificaria o que interpretou a instrução if?). Isso constitui o "número mágico" que a família de funções exec() detecta quando determina se um arquivo que está prestes a executar é um script (que precisa de um intérprete) ou um arquivo binário (o que não é).

O formato da linha shebang é bastante rigoroso. Ele precisa ter um caminho absoluto para um intérprete e no máximo um argumento para ele.

O que você pode fazer é usar env :

#!/usr/bin/env interpreter

Agora, o caminho para env é geralmente /usr/bin/env , mas tecnicamente isso não é garantia.

Isso permite que você ajuste a variável de ambiente PATH em cada sistema para que interpreter (seja bash , python ou perl ou o que você tiver) seja encontrado.

Uma desvantagem dessa abordagem é que será impossível transmitir de forma portável um argumento para o intérprete.

Isso significa que

#!/usr/bin/env awk -f

e

#!/usr/bin/env sed -f

é improvável que funcione em alguns sistemas.

Outra abordagem óbvia é usar autotools GNU (ou algum sistema de modelagem mais simples) para encontrar o interpretador e colocar o caminho correto no arquivo em uma etapa ./configure , que seria executada ao instalar o script em cada sistema. / p>

Também é possível recorrer à execução do script com um intérprete explícito, mas isso é obviamente o que você está tentando evitar:

$ sed -f script.sed
    
por 16.05.2017 / 18:44
26

Você sempre pode criar um script wrapper para encontrar o interpretador correto para o programa:

#!/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" "$@"

Salve o wrapper nos usuários ' PATH as program e coloque o programa real de lado ou com outro nome.

Eu usei #!/bin/bash no hashbang por causa da matriz flags . Se você não precisa armazenar um número variável de sinalizadores ou algo assim e pode fazer sem ele, o script deve funcionar portavelmente com #!/bin/sh .

    
por 16.05.2017 / 19:18
11

Você também pode escrever um poliglota (combinar dois idiomas). / bin / sh é garantido para existir.

Isso tem o lado negativo do código feio e talvez alguns /bin/sh s possam ficar confusos. Mas ele pode ser usado quando env não existe ou existe em algum lugar diferente de / usr / bin / env. Também pode ser usado se você quiser fazer uma seleção bem chique.

A primeira parte do script determina qual interpretador usar quando executado com / bin / sh como interpretador, mas é ignorado quando executado pelo interpretador correto. Use exec para evitar que o shell seja executado mais do que a primeira parte.

Exemplo em 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" "$@"
'''
    
por 16.05.2017 / 19:26
2

Eu prefiro as respostas de Kusalananda e ilkkachu, mas aqui está uma resposta alternativa que mais diretamente faz o que a pergunta estava pedindo, simplesmente porque foi perguntado.

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

if True:
  print("hello world!")

Note que você só pode fazer isso quando o interpretador permite escrever código no primeiro argumento. Aqui, -e e tudo depois é tomado literalmente como 1 argumento para rubi. Tanto quanto eu posso dizer, você não pode usar o bash para o código shebang, porque bash -c requer que o código esteja em um argumento separado.

Eu tentei fazer o mesmo com python para o código 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

mas acabou sendo muito longo e o linux (pelo menos na minha máquina) trunca o shebang para 127 caracteres. Por favor, desculpe o uso de exec para inserir novas linhas, pois o python não permite exceções try ou import s sem novas linhas.

Não sei ao certo como isso é portátil, e não faria isso no código destinado a ser distribuído. No entanto, é factível. Talvez alguém ache útil para depuração rápida e suja ou algo assim.

    
por 18.05.2017 / 03:22
2

Embora isso não selecione o interpretador dentro do script de shell (ele o seleciona por máquina), é uma alternativa mais fácil se você tiver acesso administrativo a todas as máquinas nas quais está tentando executar o script.

Crie um symlink (ou um hardlink se desejado) para apontar para o caminho do interpretador desejado. Por exemplo, no meu sistema perl e python estão em / usr / bin:

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

criaria um symlink para permitir que o hashbang seja resolvido para / bin / perl, etc. Isso preserva a capacidade de passar parâmetros para os scripts também.

    
por 18.05.2017 / 17:21
0

Eu fui confrontado com um problema semelhante como este hoje ( python3 apontando para uma versão do python que era muito antiga em um sistema), e surgiu com uma abordagem que é um pouco diferente das discutidas aqui: a versão "errada" do python para inicializar no "direito". A limitação é que alguma versão do python precisa ser alcançável de forma confiável, mas que geralmente pode ser obtida por meio de #!/usr/bin/env python3 .

Então, o que eu faço é iniciar meu script com:

#!/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

O que isto faz é:

  • Verifique a versão do intérprete para algum critério de aceitação
  • Se não for aceitável, passe por uma lista de versões candidatas e execute-se novamente com a primeira delas disponível
por 21.07.2018 / 23:57