Múltiplos argumentos em shebang

24

Eu estou querendo saber se existe uma maneira geral de passar várias opções para um executável através da linha shebang ( #! ).

Eu uso o NixOS, e a primeira parte do shebang em qualquer script que eu escrevo geralmente é /usr/bin/env . O problema que encontro então é que tudo que vem depois é interpretado como um único arquivo ou diretório pelo sistema.

Suponha, por exemplo, que eu queira escrever um script para ser executado por bash no modo posix. A maneira ingênua de escrever o shebang seria:

#!/usr/bin/env bash --posix

mas tentar executar o script resultante produz o seguinte erro:

/usr/bin/env: ‘bash --posix’: No such file or directory

Estou ciente de este post , mas eu queria saber se havia uma solução mais geral e mais limpa.

EDIT : Eu sei que para scripts Guile , existe uma maneira de alcançar o que eu quero, documentado em Seção 4.3.4 do manual:

 #!/usr/bin/env sh
 exec guile -l fact -e '(@ (fac) main)' -s "$0" "$@"
 !#

O truque, aqui, é que a segunda linha (começando com exec ) é interpretada como código por sh mas, estando no bloco #! ... !# , como um comentário, e assim ignorado, pelo intérprete Guile.

Não seria possível generalizar esse método para qualquer intérprete?

Segundo EDIT : depois de brincar um pouco, parece que, para os intérpretes que podem ler suas entradas de stdin , o seguinte método funcionaria:

#!/usr/bin/env sh
sed '1,2d' "$0" | bash --verbose --posix /dev/stdin; exit;

Provavelmente não é o ideal, pois o processo sh permanece até que o intérprete tenha terminado seu trabalho. Qualquer comentário ou sugestão seria apreciado.

    
por Rastapopoulos 22.10.2017 / 13:23

6 respostas

22

Não há uma solução geral, pelo menos não se você precisar dar suporte ao Linux, porque o kernel do Linux trata tudo após a primeira “palavra” na linha shebang como um único argumento .

Eu não sei quais são as restrições do NixOS, mas normalmente eu apenas escrevia o seu shebang como

#!/bin/bash --posix

ou, quando possível, definir opções no script :

set -o posix

Como alternativa, você pode fazer com que o script reinicie a si mesmo com a invocação de shell apropriada:

#!/bin/sh -

if [ "$1" != "--really" ]; then exec bash --posix -- "$0" --really "$@"; fi

shift

# Processing continues

Essa abordagem pode ser generalizada para outros idiomas, contanto que você descubra que as primeiras linhas (que são interpretadas pelo shell) sejam ignoradas pelo idioma de destino.

    
por 22.10.2017 / 13:55
10

Embora não seja exatamente portátil, começando com o 8.00 e de acordo com a documentação , você poderá usar:

#!/usr/bin/env -S command arg1 arg2 ...

Então, dado:

$ cat test.sh
#!/usr/bin/env -S showargs here 'is another' long arg -e "this and that " too

você receberá:

% ./test.sh 
$0 is '/usr/local/bin/showargs'
$1 is 'here'
$2 is 'is another'
$3 is 'long'
$4 is 'arg'
$5 is '-e'
$6 is 'this and that '
$7 is 'too'
$8 is './test.sh'

e caso você esteja curioso, showargs é:

#!/usr/bin/env sh
echo "\
#!/usr/bin/env -S command arg1 arg2 ...
is '$0'" i=1 for arg in "$@"; do echo "\$$i is '$arg'" i=$((i+1)) done
    
por 25.10.2018 / 03:07
8

O padrão POSIX é muito conciso ao descrever #! :

Na seção justificativa da documentação da família exec() de interfaces do sistema :

Another way that some historical implementations handle shell scripts is by recognizing the first two bytes of the file as the character string #! and using the remainder of the first line of the file as the name of the command interpreter to execute.

A partir da seção Introdução à Shell :

The shell reads its input from a file (see sh), from the -c option or from the system() and popen() functions defined in the System Interfaces volume of POSIX.1-2008. If the first line of a file of shell commands starts with the characters #!, the results are unspecified.

Isso basicamente significa que qualquer implementação (o Unix que você está usando) está livre para fazer as especificidades da análise da linha shebang como quiser.

Alguns Unices, como o macOS (não podem testar ATM), dividirão os argumentos dados ao interpretador na linha shebang em argumentos separados, enquanto o Linux e a maioria dos outros Unices fornecerão os argumentos como uma única opção ao interpretador.

Portanto, é insensato confiar na linha shebang sendo capaz de aceitar mais do que um único argumento.

Veja também a seção Portabilidade do artigo do Shebang na Wikipedia .

Uma solução fácil, que é generalizável para qualquer utilitário ou idioma, é criar um script wrapper que execute o script real com os argumentos de linha de comando apropriados:

#!/bin/sh
exec /bin/bash --posix /some/path/realscript "$@"

Eu não acho que eu pessoalmente tentaria fazê-lo re-executar ele mesmo , pois isso parece um pouco frágil.

    
por 22.10.2017 / 14:06
7

O shebang é descrito na execve (2) página man da seguinte forma:

#! interpreter [optional-arg]

Dois espaços são aceitos nessa sintaxe:

  1. Um espaço antes do caminho do interpretador , mas esse espaço é opcional.
  2. Um espaço separando o caminho do interpretador e seu argumento opcional.

Note que eu não usei o plural quando falei de um argumento opcional, nem a sintaxe acima usa [optional-arg ...] , pois você pode fornecer no máximo um único argumento .

No que diz respeito ao script de shell, você pode usar o comando interno set próximo ao início do script, o que permitirá definir parâmetros de intérpretes, fornecendo o mesmo resultado como se você usasse argumentos de linha de comando. / p>

No seu caso:

set -o posix

Em um prompt do Bash, verifique a saída de help set para obter todas as opções disponíveis.

    
por 22.10.2017 / 13:51
3

No Linux, o shebang não é muito flexível; de acordo com várias respostas ( resposta de Stephen Kitt e Jörg W Mittag's ), não há maneira designada de passar múltiplos argumentos em uma linha shebang.

Não tenho certeza se será útil para qualquer pessoa, mas escrevi um pequeno script para implementar o recurso que falta. Consulte o link .

Também é possível escrever soluções incorporadas. A seguir, apresento quatro soluções alternativas aplicadas ao mesmo script de teste e o resultado que cada um imprime. Eu suponho que o script é executável e está em /tmp/shebang .

Envolvendo o seu script em um processo de substituição dentro do processo

Até onde eu sei, esta é a maneira mais confiável e independente de linguagem de fazê-lo. Permite passar argumentos e preserva stdin. A desvantagem é que o intérprete não sabe a localização (real) do arquivo que lê.

#!/bin/bash
exec python3 -O <(cat << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv
try:
    print("input() 0 ::", input())
    print("input() 1 ::", input())
except EOFError:
    print("input() caused EOFError")
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")
EOWRAPPER
) "$@"

Chamando echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \escapes\' imprime:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /dev/fd/62
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\ uses\ \\escapes\\']
__debug__ :: False
PYTHON_SCRIPT_END

Observe que a substituição do processo produz um arquivo especial. Isso pode não atender a todos os executáveis. Por exemplo, #!/usr/bin/less reclama: /dev/fd/63 is not a regular file (use -f to see it)

Eu não sei se é possível ter heredoc dentro da substituição do processo em traço.

Envolvendo seu script em um heredoc simples

Mais curto e simples, mas você não poderá acessar stdin do seu script e exigirá que o intérprete possa ler e executar um script em stdin .

#!/bin/sh
exec python3 - "$@" << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

try:
    print("input() 0 ::", input())
    print("input() 1 ::", input())
except EOFError:
    print("input() caused EOFError")
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")
EOWRAPPER

Chamando echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \escapes\' imprime:

PYTHON_SCRIPT_BEGINNING
input() caused EOFError
argv[0]   :: -
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\ uses\ \\escapes\\']
__debug__ :: True
PYTHON_SCRIPT_END

Use awk system() chamada mas sem argumentos

Passa corretamente o nome do arquivo executado, mas seu script não recebe os argumentos que você fornece. Note que o awk é a única linguagem que conheço, cujo interpretador está instalado no linux por padrão e lê suas instruções a partir da linha de comando por padrão.

#!/usr/bin/gawk BEGIN {system("python3 -O " ARGV[1])}
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")

Chamando echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \escapes\' imprime:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: []
__debug__ :: False
PYTHON_SCRIPT_END

Use o awk 4.1+ system() call, desde que seus argumentos não contenham espaços

Bom, mas apenas se você tiver certeza de que seu script não será chamado com argumentos que contenham espaços. Como você pode ver, seus argumentos contendo espaços seriam divididos, a menos que os espaços tenham escapado.

#!/usr/bin/gawk @include "join"; BEGIN {system("python3 -O " join(ARGV, 1, ARGC, " "))}
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")

Chamando echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \escapes\' imprime:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: ['arg1', 'arg2', 'contains', 'spaces', 'arg3 uses \escapes\']
__debug__ :: False
PYTHON_SCRIPT_END

Para versões awk abaixo de 4.1, você terá que usar a concatenação de strings dentro de um loop for, veja a função exemplo link .

    
por 27.05.2018 / 03:05
2

Um truque para usar LD_LIBRARY_PATH com python na linha #! (shebang) que não depende de nada além do shell e funciona como um tratamento:

#!/bin/sh
'''' 2>/dev/null; exec /usr/bin/env LD_LIBRARY_PATH=. python -x "$0" "$@" #'''

__doc__ = 'A great module docstring'

Como explicado em outra parte desta página, alguns shells como sh podem receber um script em sua entrada padrão.

O script que damos sh tenta executar o comando '''' , que é simplificado para '' (a string vazia) por sh e, é claro, falha ao executá-lo, pois não há comando '' , então normalmente gera line 2: command not found no descritor de erro padrão, mas redirecionamos essa mensagem usando 2>/dev/null para o buraco negro mais próximo porque seria confuso e confuso para o usuário deixar sh exibi-lo.

Em seguida, prosseguimos para o comando de interesse para nós: exec , que substitui o processo de shell atual pelo seguinte, no nosso caso: /usr/bin/env python com os parâmetros adequados:

  • "$0" para deixar o python saber qual script deve abrir e interpretar, e também definir sys.argv[0]
  • "$@" para definir sys.argv[1:] do python para os argumentos transmitidos na linha de comando do script.

E também pedimos ao env para definir a variável de ambiente LD_LIBRARY_PATH , que é o único ponto do hack.

O comando shell termina no comentário que começa com # , de modo que o shell ignora as aspas triplas à direita ''' .

sh é então substituído por uma nova instância brilhante do interpretador python que abre e lê o script de origem python fornecido como primeiro argumento (o "$0" ).

O Python abre o arquivo e pula a primeira linha da fonte, graças ao argumento -x . Observação: ele também funciona sem -x porque para Python um shebang é apenas um comentário .

O Python então interpreta a segunda linha como a docstring para o arquivo de módulo atual, então se você precisar de um docstring de módulo válido, apenas defina __doc__ primeira coisa em seu programa python como no exemplo acima.

    
por 19.02.2019 / 22:36