O que exatamente “/ usr / bin / env node” faz no início dos arquivos de nó?

109

Eu tinha visto esta linha #!/usr/bin/env nodeno início de alguns exemplos em nodejse pesquisei no Google sem encontrar nenhum tópico que pudesse responder o motivo daquela linha.

A natureza das palavras torna a pesquisa não tão fácil.

Eu tinha lido alguns javascripte nodejslivros recentemente e não me lembrava de ter visto isso em nenhum deles.

Se você quiser um exemplo, pode ver o tutorialRabbitMQ oficial , eles o têm em quase todos os seus exemplos, aqui está um deles:

#!/usr/bin/env node

var amqp = require('amqplib/callback_api');

amqp.connect('amqp://localhost', function(err, conn) {
  conn.createChannel(function(err, ch) {
    var ex = 'logs';
    var msg = process.argv.slice(2).join(' ') || 'Hello World!';

    ch.assertExchange(ex, 'fanout', {durable: false});
    ch.publish(ex, '', new Buffer(msg));
    console.log(" [x] Sent %s", msg);
  });

  setTimeout(function() { conn.close(); process.exit(0) }, 500);
});

Alguém poderia me explicar qual é o significado desta linha?

Qual é a diferença se eu colocar ou remover esta linha? Em que casos eu preciso disso?

Gepser
fonte
3
basicamente pega o ambiente do shell de chamada e enfia esse ambiente em qualquer aplicativo especificado. neste caso,node
Marc B
Na verdade não, não estou vindo do Windows, mas obrigado por atualizar sua resposta. Estou apenas esperando para ver se mais alguém tem uma opinião diferente. Há apenas uma coisa que acho que você não mencionou em sua resposta. Acabei de descobrir algumas horas atrás. As coisas que eles mencionam aqui parecem meio importantes, mas ainda não estão claras o suficiente para mim. stackoverflow.com/questions/14517535/… (você pode atualizar se quiser, eu realmente aprecio isso, mas não ache isso uma obrigação, sua resposta é boa o suficiente agora).
Gepser
@Gepser: Entendi. Resumindo: se você deseja npminstalar um script de origem Node.js como um CLI (potencialmente disponível globalmente) , você deve usar uma linha shebang - e npmaté fará com que funcione no Windows; veja minha resposta mais uma vez atualizada.
mklement0
"A natureza das palavras torna a pesquisa não tão fácil" - você pode tentar duckduckgo.com para este caso de uso de pesquisa específico
Ricardo

Respostas:

146

#!/usr/bin/env nodeé uma instância de uma linha shebang : a primeira linha em um arquivo de texto simples executável em plataformas do tipo Unix que informa ao sistema para qual intérprete passar esse arquivo para execução , através da linha de comando seguindo o #!prefixo mágico (chamado shebang ) .

Observação: o Windows não oferece suporte a linhas shebang , portanto, elas são efetivamente ignoradas lá; no Windows, é apenas a extensão de nome de arquivo de um determinado arquivo que determina qual executável o interpretará. No entanto, você ainda precisa deles no contexto denpm . [1]

A seguir, a discussão geral de linhas shebang é limitada a plataformas do tipo Unix:

Na discussão a seguir, assumirei que o arquivo que contém o código-fonte para execução pelo Node.js é simplesmente nomeado file.

  • Você PRECISA desta linha , se quiser invocar um arquivo de origem Node.js diretamente , como um executável por si só - isso pressupõe que o arquivo foi marcado como executável com um comando como chmod +x ./file, que permite invocar o arquivo com, por exemplo,, ./fileou, se estiver localizado em um dos diretórios listados na $PATHvariável, simplesmente como file.

    • Especificamente, você precisa de uma linha shebang para criar CLIs com base em arquivos de origem Node.js como parte de um pacote npm , com as CLI (s) a serem instaladas com npmbase no valor da "bin"chave em um package.jsonarquivo de pacote ; também veja esta resposta para saber como isso funciona com pacotes instalados globalmente . A nota de rodapé [1] mostra como isso é tratado no Windows.
  • Você NÃO precisa desta linha para invocar um arquivo explicitamente por meio do nodeinterpretador, por exemplo,node ./file


Informações básicas opcionais :

#!/usr/bin/env <executableName>é uma forma de especificar portavelmente um interpretador: em poucas palavras, ele diz: execute <executableName>onde quer que você (primeiro) o encontre entre os diretórios listados na $PATHvariável (e passe implicitamente o caminho para o arquivo em questão).

Isso explica o fato de que um determinado intérprete pode ser instalado em diferentes locais nas plataformas, o que é definitivamente o caso nodedo binário Node.js.

Em contraste, a localização do envpróprio utilitário pode ser considerada no mesmo local em todas as plataformas, a saber /usr/bin/env- e especificar o caminho completo para um executável é necessário em uma linha shebang.

Observe que o utilitário POSIX envestá sendo reaproveitado aqui para localizar por nome de arquivo e executar um executável no $PATH.
O verdadeiro propósito de envé gerenciar o ambiente para um comando - veja envas especificações POSIX e a resposta útil de Keith Thompson .


Também é importante notar que o Node.js está abrindo uma exceção de sintaxe para linhas shebang, visto que elas não são código JavaScript válido ( #não é um caractere de comentário em JavaScript, ao contrário de shells semelhantes a POSIX e outros interpretadores).


[1] No interesse da consistência de plataforma cruzada, npmcria arquivos wrapper *.cmd (arquivos em lote) no Windows ao instalar executáveis ​​especificados em um package.jsonarquivo de pacote (por meio da "bin"propriedade). Essencialmente, esses arquivos em lote de wrapper imitam a funcionalidade shebang do Unix: eles invocam o arquivo de destino explicitamente com o executável especificado na linha shebang - portanto, seus scripts devem incluir uma linha shebang mesmo se você pretende executá-los apenas no Windows - veja esta resposta meu para obter detalhes.
Uma vez que os *.cmdarquivos podem ser chamados sem o.cmdextensão, isso contribui para uma experiência de plataforma cruzada perfeita: no Windows e no Unix, você pode invocar com eficácia uma npmCLI instalada por seu nome original sem extensão.

mklement0
fonte
Você pode fornecer uma explicação ou resumo para manequins como eu?
Andrew Lam
4
@AndrewLam: No Windows, extensões de nome de arquivo como .cmde .pydeterminam qual programa será usado para executar esses arquivos. No Unix, a linha shebang executa essa função. Para fazer npmfuncionar em todas as plataformas suportadas, você precisa da linha shebang mesmo no Windows.
mklement0
28

Os scripts que devem ser executados por um intérprete normalmente têm uma linha shebang na parte superior para informar ao SO como executá-los.

Se você tiver um script foocujo nome é a primeira linha #!/bin/sh, o sistema lerá essa primeira linha e executará o equivalente de /bin/sh foo. Por causa disso, a maioria dos intérpretes são configurados para aceitar o nome de um arquivo de script como um argumento de linha de comando.

O nome do intérprete após o #!deve ser um caminho completo; o sistema operacional não pesquisará o seu $PATHpara encontrar o intérprete.

Se você tiver um script para ser executado node, a maneira óbvia de escrever a primeira linha é:

#!/usr/bin/node

mas isso não funciona se o nodecomando não estiver instalado em /usr/bin.

Uma solução alternativa comum é usar o envcomando (que não foi realmente projetado para essa finalidade):

#!/usr/bin/env node

Se o seu script for chamado foo, o sistema operacional fará o equivalente a

/usr/bin/env node foo

O envcomando executa outro comando cujo nome é fornecido em sua linha de comando, passando todos os argumentos a seguir para esse comando. O motivo pelo qual ele é usado aqui é para envpesquisar $PATHo comando. Portanto, se nodeestiver instalado /usr/local/bin/nodee você tiver /usr/local/binno seu $PATH, o envcomando será invocado /usr/local/bin/node foo.

O objetivo principal do envcomando é executar outro comando com um ambiente modificado, adicionando ou removendo variáveis ​​de ambiente especificadas antes de executar o comando. Mas sem argumentos adicionais, ele apenas executa o comando com um ambiente inalterado, que é tudo de que você precisa neste caso.

Existem algumas desvantagens nessa abordagem. A maioria dos sistemas modernos do tipo Unix /usr/bin/envsim, mas trabalhei em sistemas mais antigos, onde o envcomando foi instalado em um diretório diferente. Pode haver limitações em argumentos adicionais que você pode passar usando este mecanismo. Se o usuário não tiver o diretório que contém o nodecomando em $PATH, ou se algum comando diferente for chamado node, ele poderá invocar o comando errado ou não funcionar.

Outras abordagens são:

  • Use uma #!linha que especifica o caminho completo para o nodepróprio comando, atualizando o script conforme necessário para diferentes sistemas; ou
  • Invoque o nodecomando com seu script como um argumento.

Veja também esta pergunta (e minha resposta ) para mais discussão sobre o #!/usr/bin/envtruque.

Aliás, no meu sistema (Linux Mint 17.2), ele está instalado como /usr/bin/nodejs. De acordo com as minhas anotações, ele mudou de /usr/bin/nodeque /usr/bin/nodejsentre Ubuntu 12.04 e 12.10. O #!/usr/bin/envtruque não ajudará com isso (a menos que você configure um link simbólico ou algo semelhante).

ATUALIZAÇÃO: Um comentário de mtraceur diz (reformatado):

Uma solução alternativa para o problema nodejs vs node é iniciar o arquivo com as seguintes seis linhas:

#!/bin/sh -
':' /*-
test1=$(nodejs --version 2>&1) && exec nodejs "$0" "$@"
test2=$(node --version 2>&1) && exec node "$0" "$@"
exec printf '%s\n' "$test1" "$test2" 1>&2
*/

Isso tentará primeiro nodejse depois tentará node, e somente imprimirá as mensagens de erro se ambas não forem encontradas. Uma explicação está fora do escopo desses comentários, estou apenas deixando-a aqui para o caso de ajudar alguém a lidar com o problema, já que esta resposta trouxe o problema à tona.

Não tenho usado o NodeJS recentemente. Minha esperança é que o problema nodejsvs. nodetenha sido resolvido nos anos desde que publiquei esta resposta pela primeira vez. No Ubuntu 18.04, o nodejspacote é instalado /usr/bin/nodejscomo um link simbólico para /usr/bin/node. Em algum sistema operacional anterior (Ubuntu ou Linux Mint, não tenho certeza de qual), havia um nodejs-legacypacote fornecido nodecomo um link simbólico para nodejs. Nenhuma garantia de que tenha todos os detalhes corretos.

Keith Thompson
fonte
Uma resposta muito completa fornecendo o porquê das coisas.
Suraj Jain
1
Uma solução alternativa para o problema nodejsvs nodeé iniciar o arquivo com as seguintes seis linhas: 1) #!/bin/sh -, 2) ':' /*-3) test1=$(nodejs --version 2>&1) && exec nodejs "$0" "$@"4) test2=$(node --version 2>&1) && exec node "$0" "$@", 5) exec printf '%s\n' "$test1" "$test2" 1>&26) */. Isso tentará primeiro nodejse depois tentará node, e somente imprimirá as mensagens de erro se ambas não forem encontradas. Uma explicação está fora do escopo desses comentários, estou apenas deixando-a aqui para o caso de ajudar alguém a lidar com o problema, uma vez que esta resposta trouxe o problema à tona.
mtraceur
@mtraceur: Incorporei seu comentário à minha resposta. Por que está -na #!linha?
Keith Thompson
o - in #!/bin/sh -é apenas um hábito que garante que o shell se comporte corretamente no conjunto extremamente estreito e improvável de circunstâncias em que o nome do script ou caminho relativo que o shell vê começa com um -. (Além disso, sim, parece que todas as distro mainstream convergiram de volta para nodeo nome principal. Não me preocupei em verificar quando fiz meu comentário, mas, pelo que sei, apenas a árvore genealógica da distribuição Debian usada nodejs, e parece como se todos eles voltassem ao suporte nodeassim que o Debian fez.)
mtraceur
Tecnicamente, o único traço como primeiro argumento não significava "fim das opções" - originalmente significava "desligar -xe -v", mas como os primeiros gostos de Bourne analisavam apenas o primeiro argumento como opções possíveis, e como o shell começa com essas opções desativadas , era abusivo fazer com que o shell não tentasse analisar o nome do script desde o original e, portanto, permaneceria abusivo porque o comportamento é mantido em estilos Bourne modernos por motivos de compatibilidade. Se bem me lembro de toda a minha história Bourne e curiosidades sobre portabilidade, certo.
mtraceur
0

Resposta curta: É o caminho para o intérprete.

EDIT (Resposta longa): O motivo de não haver nenhuma barra antes de "nó" é porque você nem sempre pode garantir a confiabilidade de #! / Bin /. O bit "/ env" torna o programa mais multiplataforma, executando o script em um ambiente modificado e sendo capaz de encontrar o programa interpretador de forma mais confiável.

Você não precisa necessariamente, mas é bom usar para garantir portabilidade (e profissionalismo)

Quantum
fonte
1
O /usr/bin/envbit não modifica o ambiente. É apenas um comando em um local (principalmente) conhecido que invoca outro comando fornecido como um argumento e pesquisa $PATHpara encontrá-lo. O ponto é que a #!linha requer o caminho completo para o comando que está sendo invocado e você não necessariamente sabe onde nodeestá instalado.
Keith Thompson
Era isso que eu queria, obrigado pelo esclarecimento!
Quantum