Universal Node.js shebang?

42

O Node.js é muito popular hoje em dia e eu escrevi alguns scripts nele. Infelizmente, a compatibilidade é um problema. Oficialmente, o intérprete Node.js. deve ser chamado node, mas o Debian e o Ubuntu enviam um executável chamado nodejs.

Eu quero scripts portáteis com os quais o Node.js possa trabalhar em tantas situações quanto possível. Supondo que o nome do arquivo seja foo.js, eu realmente quero que o script seja executado de duas maneiras:

  1. ./foo.jsexecuta o script, se quer nodeou nodejsestá em $PATH.
  2. node foo.jstambém executa o script (assumindo que o intérprete é chamado node)

Nota: As respostas de xavierm02 e de mim são duas variações de um script poliglota. Ainda estou interessado em uma solução pura, se houver.

dancek
fonte
Eu acho que não há uma solução real para isso, pois é permitido nomear o executável arbitrariamente pelos sistemas de compilação. Não há nada que o impeça de nomear o intérprete python alphacentauri, basta seguir as convenções e nomeá-lo python. Eu sugiro usar o nome padrão nodepara o seu script ou ter um tipo de script make que modifique o shebang.
@ G.Kayaalp Política e convenções de lado, existem muitos usuários do Debian / Ubuntu / Fedora, e eu quero criar scripts que funcionem para eles. Eu não quero configurar um sistema de compilação para isso (quem cria scripts de shell antes de executá-los?), Nem quero dar suporte alphacentaurie tal. Se houver um executável chamado nodejs, você pode ter 99% de certeza de que é Node.js. Por que não apoiar os dois nodejse node?
Dancef
Instale o pacote nodejs-legacy. A razão pela qual isso é necessário é que o nome é muito arrogantemente genérico; outra pessoa recebeu o nome primeiro. Esse outro pacote estava, no entanto, disposto a compartilhar o nome.
User3710044 27/05

Respostas:

54

O melhor que eu criei é esse "shebang de duas linhas" que realmente é um script poliglota (Bourne shell / Node.js):

#!/bin/sh
':' //; exec "$(command -v nodejs || command -v node)" "$0" "$@"

console.log('Hello world!');

A primeira linha é, obviamente, uma bainha de casca de Bourne. O Node.js ignora qualquer shebang encontrado, portanto, este é um arquivo javascript válido no que diz respeito ao Node.js.

A segunda linha chama o shell no-op :com o argumento //e, em seguida, executa nodejsou nodecom o nome desse arquivo como parâmetro. command -vé usado em vez de whichportabilidade. A sintaxe da substituição de comando $(...)não é estritamente Bourne; portanto, opte por backticks se você executar isso na década de 1980.

O Node.js apenas avalia a string ':', que é como um no-op, e o restante da linha é analisado como um comentário.

O restante do arquivo é apenas javascript antigo. O subshell é encerrado após a conclusão da execsegunda linha, portanto o restante do arquivo nunca é lido pelo shell.

Obrigado a xavierm02 pela inspiração e a todos os comentaristas por informações adicionais!

dancek
fonte
1
Ótima solução. Uma alternativa à ':'abordagem é usar // 2>/dev/null(que é o que nvmfaz): bash, é um erro ( bash: //: Is a directory), que o redirecionamento 2>/dev/nullignora silenciosamente; para JavaScript, a linha inteira se torna um comentário. Além disso - e não espero que isso seja um problema, o IRL - commandpossui uma peculiaridade em que também relata funções e aliases de shell com -v- mesmo que não os invoque. Portanto, se você exportou funções de shell ou até mesmo pseudônimos (supondo shopt -s expand_aliases) nomeados nodeou nodejs, as coisas podem quebrar.
mklement0
1
Bem, o POSIX sh é onipresente hoje em dia (exceto o Solaris / bin / sh - mas o Solaris vem com o AT&T ksh pronto para uso, que é compatível com POSIX), e Markus Kuhn recomenda (com justificativa) que fique de fora. IMHO você nunca precisa disso. Mas sim, é o suficiente para mksh, bash, dashe outras conchas em sistemas Unix e GNU modernos.
mirabilos
1
Ótima solução; embora ainda mais portátil seja usado em #!/usr/bin/env shvez de #!/bin/sh(por en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability )
user7089
2
Eu teria cuidado com a publicidade #!/usr/bin/env shcomo "mais portátil". O mesmo artigo da Wikipedia diz: "Isso funciona principalmente porque o caminho / usr / bin / env é comumente usado para o utilitário env ..." Isso não é um endosso maravilhoso, e meu palpite é que você encontrará sistemas sem /usr/bin/envmais frequência do que você encontra sistemas sem /bin/sh, se houver alguma diferença de frequência.
sheldonh
1
@dancek Oi de 2018, não seria //bin/sh -c :; exec ...uma versão mais limpa da segunda linha?
precisa saber é
10
#!/bin/sh
//bin/false || `which node || which nodejs` << `tail -n +2 $0`
console.log('ok');

//bin/falseé a mesma coisa que /bin/falseexceto que a segunda barra a transforma em um comentário para o nó, e é por isso que está aqui. Então, o lado direito do primeiro ||é avaliado. 'which node || which nodejs'com aspas em vez de aspas lança o nó e o <<alimenta o que estiver à direita. Eu poderia ter usado um delimitador começando com o //dancek, ele teria funcionado, mas acho mais limpo ter apenas duas linhas no começo, então eu costumava tail -n +2 $0ter o arquivo lido sozinho, exceto as duas primeiras.

E se você executá-lo no nó, a primeira linha é reconhecida como um shebang e ignorada e a segunda é um comentário de uma linha.

(Aparentemente, o sed pode ser usado para substituir o conteúdo do arquivo de impressão de cauda sem a primeira e a última linha )


Resposta antes da edição:

#!/bin/sh
`which node || which nodejs` <<__HERE__
console.log('ok');
__HERE__

Você não pode fazer o que deseja, então o que você faz é executar um script de shell, daí o #!/bin/sh. Esse script de shell obterá o caminho do arquivo necessário para executar o nó, ou seja which node || which nodejs. As aspas estão aqui para que sejam executadas, então 'which node || which nodejs'(com as aspas em vez das aspas) simplesmente chama o nó. Então, você apenas alimenta seu script com ele <<. O __HERE__são os delimitadores de seu script. E este console.log('ok');é um exemplo de script que você deve substituir pelo seu script.

xavierm02
fonte
uma explicação seria bom
0xC0000022L
Melhor citar o delimitador aqui-documento para expansão de parâmetros evitar, substituição de comandos e expansão aritmética a ser executada no código JavaScript antes da execução: <<'__HERE__'.
manatwork
Isso está ficando interessante! Embora //bin/falsenão funcione no meu ambiente MSYS, e preciso de aspas nos bastidores, pois minha noderesidência está em C:\Program Files\.... Sim, eu trabalho em um ambiente horrível ...
dancek
1
//bin/falsetambém não funciona no Mac OS X. Sinto muito, mas isso não parece mais muito portátil.
dancek
2
O whichcomando também não é portátil.
Jordanm
9

Este é apenas um problema nos sistemas baseados no Debian, onde a política superou o senso.

Não sei quando o Fedora forneceu um binário chamado nodejs, mas nunca o vi. O pacote é chamado nodejs e instala um nó chamado binário.

Basta usar um link simbólico para aplicar o bom senso aos seus sistemas baseados no Debian e, em seguida, você pode usar um shebang sensato. Outras pessoas estarão usando shebangs sãos de qualquer maneira, então você precisará desse link simbólico.

#!/usr/bin/env node

console.log("Spread the love.");
sheldonh
fonte
Sinto muito, mas isso não responde à pergunta. Eu entendo o ponto de vista político, mas esse não é o ponto aqui.
dancek
Para constar, alguns sistemas Fedora têm um nodejsexecutável , mas isso não é culpa do Fedora. Desculpe por deturpar o fato.
dancek
8
Não há necessidade de pedir desculpas. Você está certa. Minha resposta não responde a sua pergunta. Ele fala da sua pergunta, sugerindo que a questão limita o escopo a soluções menos úteis do que as disponíveis. Estou oferecendo conselhos práticos informados pela história. O nó não é o primeiro intérprete a encontrar-se aqui. Você deveria ter visto a comoção que levou Larry Wall a declarar que o perl shebang DEVE ser #!/usr/bin/perl. Dito isto, você não é obrigado a gostar ou aplicar minhas sugestões. Paz.
sheldonh
3

Se você não se importa em criar um pequeno .sharquivo, tenho uma pequena solução para você. Você pode criar um pequeno script de shell para determinar qual nó executável usar e usar esse script em seu shebang:

shebang.sh :

#!/bin/sh
`which node || which nodejs` $@

script.js :

#!./shebang.sh
console.log('hello');

Marque ambos executáveis ​​e execute ./script.js.

Dessa forma, você evita scripts poliglotas. Não acho que seja possível usar várias linhas shebang, embora pareça uma boa ideia.

Embora isso resolva o problema da maneira que você deseja, parece que ninguém se importa com isso. Por exemplo, uglifyjs e coffeescript usam #!/usr/bin/env node, npm usa um script de shell como ponto de entrada, que novamente chama o executável explicitamente com o nome node. Sou usuário do Ubuntu e não sabia disso, pois sempre compilo o nó. Estou pensando em relatar isso como um bug.


fonte
Eu tenho certeza que você pelo menos teria que pedir ao usuário chmod +xno script sh ... então você também pode pedir que ele defina uma variável que dê a localização do executável de seu nó ...
xavierm02
@ xavierm02 Pode-se liberar o script chmod +x'd. E eu concordo que uma variável NODE é melhor que which node || which nodejs. Mas o pesquisador deseja fornecer uma experiência pronta para uso, embora muitos projetos importantes de nós apenas o usem #!/usr/bin/env node.
1

Apenas para completar, aqui estão algumas outras maneiras de fazer a segunda linha:

// 2>/dev/null || echo yes
false //|| echo yes

Mas nenhum dos dois tem vantagem sobre a resposta selecionada:

':' //; || echo yes

Além disso, se você soubesse que um nodeou nodejs(mas não ambos) seria encontrado, o seguinte funciona:

exec `command -v node nodejs` "$0" "$@"

Mas esse é um grande "se", então acho que a resposta selecionada continua sendo a melhor.

Dave Mason
fonte
Além disso, você pode executar foobar // 2>/dev/nulldesde que foobarnão seja um comando. E muitos dos utilitários encontrados em qualquer sistema POSIX podem ser executados com o //argumento
dancek
1

Entendo que isso não responde à pergunta, mas estou convencido de que a pergunta é feita com uma premissa falsa.

O Ubuntu está errado aqui. Escrever um shebang universal para seu próprio script não mudará outros pacotes dos quais você não tem controle sobre os quais usam o padrão de fato #!/usr/bin/env node. Seu sistema deve fornecer nodeem PATHse deseja para todos os scripts visando nodejs para rodar nele.

Por exemplo, nem o npmpacote fornecido pelo Ubuntu reescreve shebangs nos pacotes:

$ cd
$ rm -rf test_npm
$ mkdir test_npm
$ cd test_npm
$ npm install mkdirp 2>&1 | grep -ve home
`-- [email protected]
  `-- [email protected]

npm WARN test_npm No description
npm WARN test_npm No repository field.
npm WARN test_npm No README data
npm WARN test_npm No license field.
$ node_modules/.bin/mkdirp testdir
/usr/bin/env: 'node': No such file or directory
$ head -n1 node_modules/.bin/mkdirp
#!/usr/bin/env node
$ npm --version
3.5.2
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"
binki
fonte