Por que é melhor usar “#! / usr / bin / env NAME” em vez de “#! / path / to / NAME” como minha shebang?

391

Eu noto que alguns scripts que eu adquiri de outros têm o shebang #!/path/to/NAME enquanto outros (usando a mesma ferramenta, NAME) possuem o shebang #!/usr/bin/env NAME .

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

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

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

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

    
por TheGeeko61 21.01.2012 / 02:06

9 respostas

395

Não é necessariamente melhor.

A vantagem de #!/usr/bin/env python é que ele usará qualquer executável python aparece primeiro no usuário $PATH .

A desvantagem de #!/usr/bin/env python é que ele usará qualquer executável python aparece primeiro no usuário $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/python que foi instalado com o sistema operacional. Por outro lado, pode usar um /home/phred/bin/python experimental que não funciona corretamente.

E se python for instalado apenas em /usr/local/bin , um usuário que não tenha /usr/local/bin em $PATH nem poderá executar o script. (Isso provavelmente não é muito provável nos sistemas modernos, mas pode facilmente acontecer com um intérprete mais obscuro.)

Especificando #!/usr/bin/python você especifica exatamente qual interpretador será usado para executar o script em um sistema particular .

Outro problema em potencial é que o truque #!/usr/bin/env não permite que você passe argumentos para o intrepreter (diferente do nome do script, que é passado implicitamente). Isso geralmente não é um problema, mas pode ser. Muitos scripts Perl são gravados com #!/usr/bin/perl -w , mas use warnings; é a substituição recomendada nos dias de hoje. Os scripts Csh devem usar #!/bin/csh -f - mas os scripts csh são não recomendados no primeiro lugar. Mas poderia haver outros exemplos.

Eu tenho vários scripts Perl em um sistema de controle de código pessoal que eu instalo quando configuro uma conta em um novo sistema. Eu uso um script de instalador que modifica a linha #! de cada script à medida que é instalado no meu $HOME/bin . (Eu não tive que usar nada além de #!/usr/bin/perl ultimamente; ele volta a tempos em que o Perl geralmente não era instalado por padrão).

Um ponto menor: o truque #!/usr/bin/env é possivelmente um abuso do comando env , que foi originalmente planejado (como o nome indica) para invocar um comando com um ambiente alterado. Além disso, alguns sistemas mais antigos (incluindo o SunOS 4, se bem me lembro) não tinham o comando env em /usr/bin . Nenhum destes é provável que seja uma preocupação significativa. env funciona assim, muitos scripts usam o truque #!/usr/bin/env , e os provedores do sistema operacional provavelmente não farão nada para quebrá-lo. Pode ser um problema se você quiser que seu script seja executado em um sistema realmente antigo, mas é provável que você precise modificá-lo de qualquer maneira.

Outra possível questão (graças a Sopalajo de Arrierez por apontá-la nos comentários) é que as tarefas agendadas são executadas com um ambiente restrito. Em particular, $PATH é tipicamente algo como /usr/bin:/bin . Portanto, se o diretório que contém o interpretador não estiver em um desses diretórios, mesmo que esteja em seu $PATH padrão em um shell de usuário, o truque /usr/bin/env não funcionará. Você pode especificar o caminho exato, ou você pode adicionar uma linha ao seu crontab para definir $PATH ( man 5 crontab para detalhes).

    
por 21.01.2012 / 07:45
87

Porque / usr / bin / env pode interpretar 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 $PATH e encontrar o python em qualquer diretório no seu $PATH .

Assim, seu script é mais portátil e funciona sem modificação em sistemas nos quais o python é instalado como /usr/bin/python ou /usr/local/bin/python ou até mesmo diretórios personalizados (que foram adicionados a $PATH ), como /opt/local/bin/python .

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

    
por 21.01.2012 / 02:35
44

Especificar o caminho absoluto é mais preciso em um determinado sistema. A desvantagem é que é muito preciso. Suponha que você perceba que a instalação do sistema do Perl é muito antiga para seus scripts e deseja usá-los em seu lugar: então você precisa editar os scripts e alterar #!/usr/bin/perl to #!/home/myname/bin/perl . Pior ainda, se você tiver Perl em /usr/bin em algumas máquinas, /usr/local/bin em outras e /home/myname/bin/perl em outras máquinas, será necessário manter três cópias separadas dos scripts e executar a apropriada em cada máquina .

#!/usr/bin/env quebra se PATH é ruim, mas quase nada. A tentativa de operar com um PATH ruim é muito raramente útil e indica que você sabe muito pouco sobre o sistema em que o script está sendo executado, portanto você não pode depender de nenhum caminho absoluto.

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

Além de /bin/sh , a única vez em que você deve usar um caminho absoluto em um shebang é quando seu script não é para ser portável, portanto você pode contar com um local conhecido para o interpretador. Por exemplo, um script bash que funciona apenas no Linux pode usar com segurança o #!/bin/bash . Um script que só deve ser usado internamente pode contar com convenções de localização de intérpretes domésticos.

#!/usr/bin/env tem desvantagens. É mais flexível do que especificar um caminho absoluto, mas ainda requer conhecer o nome do interpretador. Ocasionalmente, você pode querer executar um interpretador que não esteja no $PATH , por exemplo, em um local relativo ao script. Nesses casos, você geralmente pode fazer uma script poliglota que pode ser interpretado tanto pelo shell padrão quanto pelo interpretador desejado. Por exemplo, para tornar um script do Python 2 portátil para sistemas em que python é Python 3 e python2 é Python 2 e para sistemas em que python é Python 2 e python2 nã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 …
    
por 30.05.2013 / 01:38
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, pode causar problemas ao transmitir argumentos sobre a linha shebang. A solução maximamente portátil é esta:

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

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

    
por 02.05.2013 / 17:19
11

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

Usar /usr/bin/env (que, como mencionado em outras respostas, não está em /usr/bin em alguns sistemas operacionais) é necessário porque você não pode simplesmente colocar um nome executável após o #! - ele deve ser um caminho absoluto . Isso ocorre porque o mecanismo #! funciona em um nível inferior ao 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á um erro assim 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 estiver marcado como executável e começar com #! , o kernel (que não sabe sobre $PATH ou o diretório atual: estes são conceitos de usuário) irá procurar por 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 nesse local) para executar algo usando $PATH .

    
por 19.01.2015 / 03:54
8

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

  1. Ele não resolve o problema de especificar o caminho completo para o interpretador, apenas o move para env .

    env não tem mais garantia de estar em /usr/bin/env do que bash é garantido como /bin/bash ou python em /usr/bin/python .

  2. env sobrescreve ARGV [0] com o nome do interpretador (e..g bash ou python).

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

[atualização 2016-06-04]

E um terceiro problema:

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

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

    Anexar ou pré-pendurar um diretório a $ PATH é bastante fácil (embora você ainda tenha que editar um arquivo para torná-lo permanente - seu ~/.profile ou o que for) e está longe de ser fácil roteirar essa edição porque o PATH poderia ser definido em qualquer lugar no 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 usam #!/usr/bin/env .

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

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

    Em vi , seria tão simples quanto mover o cursor para a linha #! desejada e, em seguida, digitar dd1GP ou 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 de usar #!/usr/bin/env são mínimas, na melhor das hipóteses, e certamente não chegam nem perto de compensar as desvantagens. Até mesmo a vantagem da "conveniência" é em grande parte ilusória.

IMO, é uma idéia boba promovida por um tipo particular de programador que pensa que os sistemas operacionais não são algo para se trabalhar, eles são um problema a ser trabalhado (ou ignorado na melhor das hipóteses) .

PS: aqui está um script simples para alterar o intérprete de vários arquivos de uma só vez.

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 como, por exemplo, change-shebang.sh python2.7 *.py ou change-shebang.sh $HOME/bin/my-experimental-ruby *.rb

    
por 25.10.2015 / 07:18
7

Adicionando outro exemplo aqui:

Usar env também é útil quando você deseja compartilhar scripts entre vários rvm ambientes, por exemplo.

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

env ruby --version

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

    
por 22.01.2012 / 22:35
4

Se você está escrevendo apenas para si mesmo ou para o seu trabalho e a localização do intérprete que está chamando 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 interpretador python estava no mesmo local, independentemente de qual sintaxe você usou, 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 dos softwares mais recentes são padronizados como /usr/local/bin/ .

Eu até diria que sempre use #!/usr/bin/env , porque se você tem múltiplas versões do mesmo interpretador instalado e você não sabe qual será o padrão do seu shell, então provavelmente você deve corrigir isso.

    
por 03.04.2015 / 11:21
2

Por motivos de portabilidade e compatibilidade, é melhor usar

#!/usr/bin/env bash

em vez de

#!/usr/bin/bash

Existem várias possibilidades, onde um binário pode estar localizado em um sistema Linux / Unix. Verifique a hier manual (7) para 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 básico, em / usr / local / . Como o bash não faz parte do sistema básico, o binário bash é instalado em / usr / local / bin / bash .

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

Observe também que a maioria das instalações do Linux também ligou (duro) o binário env para / bin / env ou / usr / bin com softlink para / bin, o que deve não ser usado no shebang. Então não use #! / Bin / env .

    
por 13.12.2017 / 11:41