Por que é melhor usar "#! / Usr / bin / env NAME" em vez de "#! / Path / to / NAME" como meu shebang?

459

Percebo que alguns scripts que adquiri de outras pessoas têm o shebang, #!/path/to/NAMEenquanto outros (usando a mesma ferramenta, NAME) têm o shebang #!/usr/bin/env NAME.

Ambos parecem funcionar corretamente. Nos tutoriais (em Python, por exemplo), parece haver uma sugestão de que o último shebang é melhor. Mas não entendo muito bem por que isso acontece.

Sei que, para usar o último shebang, NAME deve estar no PATH, enquanto o primeiro shebang não possui essa restrição.

Além disso, parece (para mim) que o primeiro seria o melhor shebang, pois especifica exatamente onde NAME está localizado. Portanto, neste caso, se houver várias versões de NAME (por exemplo, / usr / bin / NAME, / usr / local / bin / NAME), o primeiro caso especifica qual usar.

Minha pergunta é por que o primeiro shebang é preferido ao segundo?

TheGeeko61
fonte
12
Veja esta resposta ...
jasonwryan
@ TheGeeko61: No meu caso, eu tinha algo quebrado e algumas variáveis ​​não estavam a favor. Então, sugiro usar esse shebang para verificar se o env está carregado corretamente.
precisa saber é o seguinte

Respostas:

462

Não é necessariamente melhor.

A vantagem #!/usr/bin/env pythoné que ele usará o pythonexecutável que aparecer primeiro no usuário $PATH.

A desvantagem de #!/usr/bin/env pythoné que ele vai usar qualquer pythonexecutável aparece em primeiro lugar o usuário do $PATH.

Isso significa que o script pode se comportar de maneira diferente, dependendo de quem o executa. Para um usuário, ele pode usar o /usr/bin/pythonque foi instalado com o sistema operacional. Por outro lado, pode usar um experimental /home/phred/bin/pythonque não funciona corretamente.

E se pythoné instalado apenas em /usr/local/bin, um usuário que não tem /usr/local/binem $PATHnão vai mesmo ser capaz de executar o script. (Isso provavelmente não é muito provável nos sistemas modernos, mas pode acontecer facilmente para um intérprete mais obscuro.)

Ao especificar, #!/usr/bin/pythonvocê especifica exatamente qual intérprete será usado para executar o script em um sistema específico .

Outro problema em potencial é que o #!/usr/bin/envtruque não permite que você passe argumentos para o intérprete (exceto o nome do script, que é passado implicitamente). Isso geralmente não é um problema, mas pode ser. Muitos scripts Perl são escritos com #!/usr/bin/perl -w, mas use warnings;é a substituição recomendada atualmente. Os scripts #!/bin/csh -fcsh devem usar - mas os scripts csh não são recomendados em primeiro lugar. Mas poderia haver outros exemplos.

Eu tenho vários scripts Perl em um sistema de controle de origem pessoal que instalo quando configuro uma conta em um novo sistema. Eu uso um script de instalação que modifica a #!linha de cada script à medida que o instala no meu $HOME/bin. (Eu não precisei usar nada além de #!/usr/bin/perlultimamente; ele remonta aos tempos em que o Perl geralmente não era instalado por padrão.)

Um ponto secundário: o #!/usr/bin/envtruque é indiscutivelmente um abuso do envcomando, que foi originalmente planejado (como o nome indica) para invocar um comando com um ambiente alterado. Além disso, alguns sistemas mais antigos (incluindo SunOS 4, se bem me lembro) não tinha o envcomando /usr/bin. É provável que nenhum destes seja uma preocupação significativa. envfunciona dessa maneira, muitos scripts usam esse #!/usr/bin/envtruque, e os provedores de SO provavelmente não farão nada para quebrá-lo. Ele pode ser um problema se você quiser que seu script para ser executado em um sistema muito antigo, mas, em seguida, é provável que você precisa modificá-lo de qualquer maneira.

Outra questão possível (graças a Sopalajo de Arrierez por apontar nos comentários) é que os trabalhos cron são executados em um ambiente restrito. Em particular, $PATHnormalmente é algo parecido /usr/bin:/bin. Portanto, se o diretório que contém o intérprete não estiver em um desses diretórios, mesmo que esteja no seu padrão $PATHem um shell de usuário, o /usr/bin/envtruque não funcionará. Você pode especificar o caminho exato ou adicionar uma linha ao seu crontab para definir $PATH( man 5 crontabpara detalhes).

Keith Thompson
fonte
4
Se / usr / bin / perl for perl 5.8, $ HOME / bin / perl for 5.12 e um script que requer 5.12 códigos de acesso / usr / bin / perl nos shebangs, pode ser uma grande dificuldade para executar o script. Eu raramente vi ter / usr / bin / env perl grab perl do PATH um problema, mas geralmente é muito útil. E é muito mais bonito que o exec hack!
William Pursell
8
Vale a pena notar que, se você quiser usar uma versão específica do interpretador, / usr / bin / env ainda será melhor. simplesmente porque geralmente existem várias versões de intérpretes instaladas em sua máquina denominadas perl5, perl5.12, perl5.10, python3.3, python3.32, etc. e se o seu aplicativo foi testado apenas nessa versão específica, você ainda pode especificar #! / usr / bin / env perl5.12 e fique bem, mesmo que o usuário o tenha instalado em algum lugar incomum. Na minha experiência, 'python' geralmente é apenas um link simbólico para a versão padrão do sistema (não necessariamente a versão mais recente do sistema).
Root'
6
@root: para Perl, use v5.12;serve a alguns desses propósitos. E #!/usr/bin/env perl5.12falhará se o sistema tiver Perl 5.14, mas não 5.12. Para Python 2 vs. 3, #!/usr/bin/python2e #!/usr/bin/python3provavelmente funcionará.
Keith Thompson
3
@ GoodPerson: Suponha que eu escreva um script Perl para ser instalado /usr/local/bin. Por acaso sei que ele funciona corretamente /usr/bin/perl. Não tenho idéia se funciona com qualquer perlexecutável que algum usuário aleatório possua em seu computador $PATH. Talvez alguém esteja experimentando alguma versão antiga do Perl; porque eu especifiquei #!/usr/bin/perl, meu script (que o usuário nem sequer conhece ou se importa é um script Perl) não para de funcionar.
Keith Thompson
5
Se não funcionar /usr/bin/perl, descobrirei rapidamente, e é responsabilidade do proprietário / administrador do sistema mantê-lo atualizado. Se você deseja executar meu script com seu próprio perl, sinta-se à vontade para pegar e modificar uma cópia ou invocá-la via perl foo. (E você pode considerar a possibilidade de que as 55 pessoas que aprovaram esta resposta também saibam uma coisa ou duas. Certamente é possível que você esteja certo e que tudo esteja errado, mas não é assim que eu aposto.)
Keith Thompson
101

Porque / usr / bin / env pode interpretar o seu $PATH, o que torna os scripts mais portáteis.

#!/usr/local/bin/python

Só executará seu script se o python estiver instalado em / usr / local / bin.

#!/usr/bin/env python

Irá interpretar o seu $PATHe encontrar python em qualquer diretório no seu $PATH.

Portanto, seu script é mais portátil e funcionará sem modificações nos sistemas em que o python está instalado como /usr/bin/python, ou /usr/local/bin/python, ou mesmo em diretórios personalizados (aos quais foram adicionados $PATH), como /opt/local/bin/python.

A portabilidade é a única razão pela qual o uso envé preferível aos caminhos codificados.

Tim Kennedy
fonte
19
Diretórios personalizados para pythonexecutáveis ​​são particularmente comuns à medida que o virtualenvuso aumenta.
Xiong Chiamiov
1
Que tal #! python, por que isso não é usado?
Kristianp #
6
#! pythonnão é usado porque você precisa estar no mesmo diretório que o binário python, pois a palavra de barra pythoné interpretada como o caminho completo para o arquivo. Se você não possui um binário python no diretório atual, receberá um erro como bash: ./script.py: python: bad interpreter: No such file or directory. É o mesmo que se você usou#! /not/a/real/path/python
Tim Kennedy
47

A especificação do caminho absoluto é mais precisa em um determinado sistema. A desvantagem é que é muito preciso. Suponha que você perceba que a instalação do sistema Perl é muito antiga para seus scripts e que você deseja usar os seus próprios: em seguida, você deve editar os scripts e alterar #!/usr/bin/perlpara #!/home/myname/bin/perl. Pior, se você tiver o Perl em /usr/binalgumas máquinas, /usr/local/binem outras e /home/myname/bin/perlem outras, precisará manter três cópias separadas dos scripts e executar a apropriada em cada máquina.

#!/usr/bin/envquebra se PATHfor ruim, mas quase tudo também. Tentar operar com problemas ruins PATHraramente é útil e indica que você sabe muito pouco sobre o sistema em que o script está sendo executado, portanto, você não pode confiar em nenhum caminho absoluto.

Existem dois programas cuja localização você pode confiar em quase todas as variantes unix: /bin/she /usr/bin/env. Algumas variantes obscuras e quase todas aposentadas do Unix tinham /bin/envsem ter /usr/bin/env, mas é improvável que você as encontre. Os sistemas modernos têm /usr/bin/envprecisamente por causa de seu amplo uso em shebangs. /usr/bin/envé algo em que você pode contar.

Além disso /bin/sh, o único momento em que você deve usar um caminho absoluto em um shebang é quando seu script não deve ser portátil, para que você possa contar com um local conhecido para o intérprete. Por exemplo, um script bash que só funciona no Linux pode ser usado com segurança #!/bin/bash. Um script que deve ser usado apenas internamente pode depender das convenções de localização do intérprete interno.

#!/usr/bin/envtem desvantagens. É mais flexível do que especificar um caminho absoluto, mas ainda exige saber o nome do intérprete. Ocasionalmente, você pode querer executar um intérprete que não esteja no $PATH, por exemplo, em um local relativo ao script. Nesses casos, muitas vezes você pode criar um script poliglota que possa ser interpretado pelo shell padrão e pelo intérprete desejado. Por exemplo, para tornar um script Python 2 portátil, tanto para sistemas onde pythoné Python 3 e python2Python 2 quanto para sistemas onde pythoné Python 2 e python2não existe:

#!/bin/sh
''':'
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0" "$@"
else
  exec python "$0" "$@"
fi
'''
# real Python script starts here
def …
Gilles
fonte
22

Especificamente para perl, usar #!/usr/bin/envé uma má ideia por dois motivos.

Primeiro, não é portátil. Em algumas plataformas obscuras, o env não está em / usr / bin. Segundo, como Keith Thompson observou, isso pode causar problemas ao passar argumentos na linha shebang. A solução portátil máxima é esta:

#!/bin/sh
exec perl -x "$0" "$@"
#!perl

Para obter detalhes sobre como ele funciona, consulte 'perldoc perlrun' e o que ele diz sobre o argumento -x.

DrHyde
fonte
Exatamente o anser que eu estava procurando: como escrever "shebang" portável para script perl que permitirá que argumentos adicionais sejam passados ​​para perl (a última linha do seu exemplo aceita argumentos adicionais).
AmokHuginnsson
15

A razão pela qual há uma distinção entre os dois é por causa de como os scripts são executados.

O uso /usr/bin/env(que, como mencionado em outras respostas, não está presente em /usr/binalguns sistemas operacionais) é necessário porque você não pode simplesmente colocar um nome de executável após o #!- deve ser um caminho absoluto. Isso ocorre porque o #!mecanismo funciona em um nível inferior ao do shell. Faz parte do carregador binário do kernel. Isso pode ser testado. Coloque isso em um arquivo e marque-o como executável:

#!bash

echo 'foo'

Você encontrará que imprime um erro como este ao tentar executá-lo:

Failed to execute process './test.sh'. Reason:
The file './test.sh' does not exist or could not be executed.

Se um arquivo é marcado como executável e começa com a #!, o kernel (que não conhece $PATHou o diretório atual: estes são conceitos de território do usuário) procurará um arquivo usando um caminho absoluto. Como o uso de um caminho absoluto é problemático (como mencionado em outras respostas), alguém criou um truque: você pode executar /usr/bin/env(que quase sempre está nesse local) para executar algo usando $PATH.

Tiffany Bennett
fonte
11

Há mais dois problemas com o uso #!/usr/bin/env

  1. Não resolve o problema de especificar o caminho completo para o intérprete, apenas o move para env.

    envnão é mais garantido estar dentro do /usr/bin/envque bashé garantido estar dentro /bin/bashou python dentro /usr/bin/python.

  2. env sobrescreve ARGV [0] pelo nome do intérprete (por exemplo, bash ou python).

    Isso impede que o nome do seu script apareça, por exemplo, na pssaída (ou altera como / onde aparece) e torna impossível encontrá-lo com, por exemplo,ps -C scriptname.sh

[atualização 04/06/2016]

E um terceiro problema:

  1. Alterar seu PATH é mais trabalhoso do que apenas editar a primeira linha de um script, especialmente quando o script de tais edições é trivial. por exemplo:

    printf "%s\n" 1 i '#!'$(type -P python2) . w | ed foo.py

    Anexar ou prender um diretório a $ PATH é bastante fácil (embora você ainda precise editar um arquivo para torná-lo permanente - o seu ~/.profileou o que quer que seja - e está longe de ser fácil criar um script) , não na primeira linha).

    Alterar a ordem dos diretórios PATH é significativamente mais difícil .... e muito mais difícil do que apenas editar a #!linha.

    E você ainda tem todos os outros problemas que o uso #!/usr/bin/envoferece.

    @jlliagre sugere em um comentário que #!/usr/bin/envé útil para testar seu script com várias versões de intérprete, "alterando apenas a ordem PATH / PATH"

    Se você precisar fazer isso, é muito mais fácil ter várias #!linhas na parte superior do seu script (elas são apenas comentários em qualquer lugar, exceto a primeira linha) e recortar / copiar e colar o que você deseja usar agora para a primeira linha.

    Em vi, isso seria tão simples quanto mover o cursor para a #!linha desejada e digitar dd1GPou Y1GP. Mesmo com um editor tão trivial quanto nano, levaria alguns segundos para usar o mouse para copiar e colar.


No geral, as vantagens do uso #!/usr/bin/envsão mínimas, na melhor das hipóteses, e certamente nem chegam perto de superar as desvantagens. Até a vantagem da "conveniência" é ilusória.

Na IMO, é uma idéia tola promovida por um tipo específico de programador que pensa que os sistemas operacionais não são algo com o qual trabalhar, eles são um problema a ser contornado (ou ignorado, na melhor das hipóteses).

PS: aqui está um script simples para alterar o intérprete de vários arquivos ao mesmo tempo.

change-shebang.sh:

#!/bin/bash

interpreter="$1"
shift

if [ -z "$(type -P $interpreter)" ] ; then
  echo "Error: '$interpreter' is not executable." >&2
  exit 1
fi

if [ ! -d "$interpreter" ] && [ -x "$interpreter" ] ; then
  shebang='#!'"$(realpath -e $interpreter)" || exit 1
else
  shebang='#!'"$(type -P $interpreter)"
fi

for f in "$@" ; do
  printf "%s\n" 1 i "$shebang" . w | ed "$f"
done

Execute-o como, por exemplo, change-shebang.sh python2.7 *.pyouchange-shebang.sh $HOME/bin/my-experimental-ruby *.rb

cas
fonte
4
Ninguém aqui mencionou o problema do ARGV [0]. E ninguém mencionou o problema / path / to / env de uma forma que aborda diretamente um dos argumentos para usá-lo (ou seja, que bash ou perl podem estar em um local inesperado).
cas
2
O problema do caminho do intérprete é facilmente corrigido por um administrador de sistema com links simbólicos ou pelo usuário editando seu script. Certamente, não é um problema significativo o suficiente para incentivar as pessoas a lidar com todos os outros problemas causados ​​pelo uso de env na linha shebang mencionada aqui e em outras questões. Isso está promovendo uma solução ruim da mesma maneira que csho incentivo ao script está promovendo uma solução ruim: meio que funciona, mas existem alternativas muito melhores.
cas
3
os não administradores de sistema podem solicitar que o administrador de sistemas faça isso. ou eles podem simplesmente editar o script e alterar a #!linha.
cas
2
É exatamente isso que o env está ajudando a evitar: dependendo de um conhecimento técnico específico do sysadmin ou do SO, não relacionado ao python ou qualquer outra coisa.
Jlliagre 25/10/2015
1
Eu gostaria de poder votar isso mil vezes, porque na verdade é uma ótima resposta de qualquer maneira - se alguém usa /usr/bin/enve eu preciso me livrar dela localmente, ou se não o usarem e eu preciso adicioná-lo. Os dois casos podem ocorrer e, portanto, os scripts fornecidos nesta resposta são uma ferramenta potencialmente útil para se ter na caixa de ferramentas.
jstine
8

Adicionando outro exemplo aqui:

O uso envtambém é útil quando você deseja compartilhar scripts entre vários rvmambientes, por exemplo.

Executando isso na linha cmd, mostra qual versão ruby ​​será usada quando #!/usr/bin/env rubyusada dentro de um script:

env ruby --version

Portanto, quando você usa env, pode usar diferentes versões do ruby ​​através do rvm, sem alterar seus scripts.

Agora não
fonte
1
// , Excelente ideia. O objetivo de vários intérpretes NÃO é quebrar o código ou ter o código dependente desse intérprete específico .
precisa saber é o seguinte
4

Se você está escrevendo exclusivamente para si ou para o seu trabalho, e o local do intérprete para o qual está ligando está sempre no mesmo lugar, use o caminho direto. Em todos os outros casos, use #!/usr/bin/env.

Eis o porquê: na sua situação, o pythonintérprete estava no mesmo local, independentemente da sintaxe usada, mas para muitas pessoas ele poderia ter sido instalado em um local diferente. Embora a maioria dos principais intérpretes de programação esteja localizada em /usr/bin/muitos softwares mais recentes, ela está por padrão /usr/local/bin/.

Eu diria mesmo que sempre use, #!/usr/bin/envporque se você tem várias versões do mesmo intérprete instaladas e não sabe o que seu shell usará como padrão, provavelmente deverá consertar isso.

Mauvis Ledford
fonte
5
"Em todos os outros casos, use #! / Usr / bin / env" é muito forte. // Como @KeithThompson e outros apontam, #! / Usr / bin / env significa que o script pode se comportar de maneira diferente dependendo de quem / como executar. Às vezes, esse comportamento diferente pode ser um bug de segurança. // Por exemplo, NUNCA use "#! / Usr / bin / env python" para um script setuid ou setgid, porque o usuário que está invocando o script pode colocar malware em um arquivo chamado "python" no PATH. Se os scripts setuid / gid são uma boa idéia é uma questão diferente - mas certamente, nenhum executável setuid / gid deve confiar no ambiente fornecido pelo usuário.
Krazy Glew
@KrazyGlew, você não pode configurar um script no Linux. Você faz isso através de um executável. E ao escrever este executável, é uma boa prática, e amplamente realizada, limpar as variáveis ​​de ambiente. Algumas variáveis ​​de ambiente também são propositadamente ignoradas .
MayeulC
3

Por motivos de portabilidade e compatibilidade, é melhor usar

#!/usr/bin/env bash

ao invés de

#!/usr/bin/bash

Existem várias possibilidades, nas quais um binário pode estar localizado em um sistema Linux / Unix. Verifique a página de manual hier (7) para obter uma descrição detalhada da hierarquia do sistema de arquivos.

O FreeBSD, por exemplo, instala todo o software, que não faz parte do sistema base, em / usr / local / . Como o bash não faz parte do sistema base, o binário do bash é instalado em / usr / local / bin / bash .

Quando você quer um script portátil bash / csh / perl / qualquer que seja, executado na maioria das distribuições Linux e FreeBSD, você deve usar #! / Usr / bin / env .

Observe também que a maioria das instalações Linux também (hard) vinculou o binário env a / bin / env ou softlinked / usr / bin em / bin, o que não deve ser usado no shebang. Portanto, não use #! / Bin / env .

Mirko Steiner
fonte