Eu tenho um script que eu quero poder executar em duas máquinas. Essas duas máquinas obtêm cópias do script do mesmo repositório git. O script precisa ser executado com o intérprete correto (por exemplo zsh
).
Infelizmente, tanto env
e zsh
vivem em diferentes locais em máquinas locais e remotos:
Máquina remota
$ which env
/bin/env
$ which zsh
/some/long/path/to/the/right/zsh
Máquina local
$ which env
/usr/bin/env
$which zsh
/usr/local/bin/zsh
Como posso configurar o shebang para que a execução do script como /path/to/script.sh
sempre use o Zsh
disponível no PATH
?
env
não está em / bin e / usr / bin? Tentewhich -a env
confirmar.Respostas:
Você não pode resolver isso diretamente através do shebang, pois o shebang é puramente estático. O que você pode fazer é ter um "multiplicador menos comum" (da perspectiva do shell) no shebang e executar novamente seu script com o shell correto, se esse LCM não for zsh. Em outras palavras: já seu script executado por uma concha encontrada em todos os sistemas, teste para um
zsh
recurso -apenas e se as voltas de teste fora falsa, tem o scriptexec
comzsh
, onde o teste será bem-sucedida e você só continuar.Um recurso exclusivo
zsh
, por exemplo, é a presença da$ZSH_VERSION
variável:Neste caso simples, o script é executado pela primeira vez
/bin/sh
(todos os sistemas do tipo Unix pós-década de 80 compreendem#!
e possuem um/bin/sh
Bourne ou POSIX, mas nossa sintaxe é compatível com ambos). Se não$ZSH_VERSION
estiver definido, o próprio script será concluído . Se estiver definido (resp. O script já foi executado ), o teste é simplesmente ignorado. Voilà.exec
zsh
$ZSH_VERSION
zsh
Isso só falha se
zsh
não estiver presente$PATH
.Edit: Para certificar-se, só
exec
umzsh
nos locais habituais, você poderia usar algo comoIsso pode impedir você de acidentalmente
exec
algo no seu$PATH
que não é o quezsh
você está esperando.fonte
zsh
na$PATH
não é o que você espera.zsh
binário nos locais padrão é realmente umzsh
.zsh
onde estázsh -c 'whence zsh'
. Mais simplesmente, você pode apenascommand -v zsh
. Veja minha resposta para saber como localizar dinamicamente o arquivo#!bang
.zsh
binário de$PATH
para obter o caminho dozsh
binário não resolveria completamente o problema apontado por @RyanReich, seria? :)zsh
, não, acho que não. Mas se você incorporar a sequência resultante em seu hash bang e depois executar seu próprio script, pelo menos saberá o que está recebendo. Ainda assim, seria um teste mais simples do que fazer um loop.Durante anos, usei algo semelhante para lidar com os vários locais do Bash em sistemas que eu precisava que meus scripts fossem executados.
Bash / Zsh / etc.
O acima pode ser facilmente adaptado para uma variedade de intérpretes. O ponto principal é que esse script seja executado inicialmente como shell Bourne. Em seguida, recursivamente se chama uma segunda vez, mas analisa tudo acima do comentário
### BEGIN
usandosed
.Perl
Aqui está um truque semelhante para o Perl:
Este método utiliza a capacidade do Perl quando um arquivo é executado para analisar o arquivo pulando todas as linhas anteriores à linha
#! perl
.fonte
$*
, em vez de"$@"
, o uso inútil de eval, status de saída não avisados (você não usouexec
para o primeiro), faltando-
/--
, mensagens de erro e não em stderr, 0 status de saída para condições de erro , usando / bin / sh para LIN_BASH, ponto e vírgula inútil (cosmético), usando todas as letras maiúsculas para variáveis não-env.uname -s
é comouname
(uname é para o nome Unix). Você esqueceu de mencionar que o skipping é desencadeada pela-x
opção deperl
.NOTA: @ jw013 faz a seguinte objeção sem suporte nos comentários abaixo:
Eu respondi suas objeções de segurança, salientando que quaisquer permissões especiais são exigido somente uma vez por instalação / atualização de ação, a fim de instalar / atualizar o auto-instalação roteiro - que eu pessoalmente muito chamada segura. Eu também o apontei para uma
man sh
referência para alcançar objetivos semelhantes por meios semelhantes. Na época, eu não me incomodei em apontar que quaisquer falhas de segurança ou práticas geralmente desaconselhadas que possam ou não ser representadas na minha resposta, elas provavelmente estão mais enraizadas na própria pergunta do que na minha resposta:Não satisfeito, @ jw013 continuou a objetar, promovendo seu argumento ainda não suportado com pelo menos algumas declarações erradas:
Em primeiro lugar:
A ÚNICA EXECUTABLE código em qualquer EXECUTABLE Shell Script é o
#!
SE(embora mesmo não
#!
seja oficialmente especificado )Um script de shell é apenas um arquivo de texto - para que ele tenha algum efeito, ele deve ser lido por outro arquivo executável, suas instruções então interpretadas por esse outro arquivo executável, antes de finalmente o outro arquivo executável executar sua interpretação do script de shell. Não é possível para a execução de um arquivo de script de shell envolver menos de dois arquivos. Existe uma possível exceção no
zsh
próprio compilador, mas com isso eu tenho pouca experiência e não é de forma alguma representada aqui.O hashbang de um script de shell deve apontar para o intérprete pretendido ou ser descartado como irrelevante.
O COMPORTAMENTO DE RECONHECIMENTO / EXECUÇÃO DE SHELL DO SHELL É DEFINIDO PADRÃO
O shell possui dois modos básicos de analisar e interpretar sua entrada: ou sua entrada atual está definindo um
<<here_document
ou está definindo um{ ( command |&&|| list ) ; } &
- em outras palavras, o shell interpreta um token como um delimitador para um comando que ele deve executar depois de lê-lo em ou como instruções para criar um arquivo e mapeá-lo para um descritor de arquivo para outro comando. É isso aí.Ao interpretar comandos para executar, o shell delimita os tokens em um conjunto de palavras reservadas. Quando o shell encontra uma abertura simbólica deve continuar a ler em uma lista de comandos até que a lista é ou delimitado por um fechamento simbólico como uma nova linha - quando aplicável - ou o fechamento token de como
})
para({
antes da execução.O shell distingue entre um comando simples e um comando composto. O comando composto é o conjunto de comandos que devem ser lidos antes da execução, mas o shell não executa
$expansion
nenhum dos seus comandos simples constituintes até que ele execute individualmente cada um.Portanto, no exemplo a seguir, as
;semicolon
palavras reservadas delimitam comandos simples individuais, enquanto o\newline
caractere não escapado delimita entre os dois comandos compostos:Essa é uma simplificação das diretrizes. Fica muito mais complicado quando você considera shell-builtins, subshells, ambiente atual e etc, mas, para meus propósitos aqui, é suficiente.
E por falar em built-ins e listas de comandos, a
function() { declaration ; }
é apenas um meio de atribuir um comando composto a um comando simples. O shell não deve executar nenhuma$expansions
declaração na declaração em si - para incluir<<redirections>
-, mas deve armazenar a definição como uma única cadeia literal e executá-la como um shell especial embutido quando solicitado.Portanto, uma função de shell declarada em um script de shell executável é armazenada na memória do shell de interpretação em sua forma de string literal - não expandida para incluir documentos aqui anexados como entrada - e executada independentemente do seu arquivo de origem toda vez que é chamada de shell. enquanto o ambiente atual do shell durar.
A
<<HERE-DOCUMENT
É UM ARQUIVO EM LINHAEntende? Para cada shell acima do shell, cria um arquivo e o mapeia para um descritor de arquivo. No
zsh, (ba)sh
shell, cria um arquivo regular/tmp
, despeja a saída, mapeia-o para um descritor e exclui o/tmp
arquivo para que a cópia do descritor do kernel seja o que resta.dash
evita todo esse absurdo e simplesmente coloca seu processamento de saída em um|pipe
arquivo anônimo destinado ao<<
destino de redirecionamento .Isso faz com que
dash
:funcionalmente equivalente a
bash
's:enquanto
dash
a implementação é pelo menos POSIXly portátil.QUE FAZ DIVERSOS ARQUIVOS
Então, na resposta abaixo quando eu faço:
O seguinte acontece:
A primeira vez que
cat
o conteúdo de qualquer arquivo do shell criado paraFILE
dentro./file
, torná-lo executável, em seguida, executá-lo.O kernel interpreta
#!
e chama/usr/bin/sh
com um<read
descritor de arquivo atribuído./file
.sh
mapeia uma string na memória que consiste no comando composto começando em_fn()
e terminando emSCRIPT
.Quando
_fn
é chamado,sh
deve primeiro interpretar, em seguida, mapear para um descritor de arquivo definido no<<SCRIPT...SCRIPT
antes invocando_fn
como um especial built-in utilitário porqueSCRIPT
é_fn
's<input.
A saída de cordas por
printf
ecommand
estão escritas para_fn
's padrão-out>&1
- que é redirecionada para o atual shell deARGV0
- ou$0
.cat
concatena seu descritor de arquivo de<&0
entrada padrão -SCRIPT
- sobre o argumento>
do shell atual truncadoARGV0
, ou$0
.A conclusão do comando composto atual já lido ,
sh exec
é o$0
argumento executável - e recentemente reescrito - .Desde o momento em que
./file
é chamado até que suas instruções contidas especifiquem que ele deve serexec
d novamente, osh
lê em um único comando composto de cada vez enquanto os executa, enquanto./file
ele próprio não faz nada, exceto aceitar alegremente seu novo conteúdo. Os arquivos que estão realmente no trabalho são/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.
OBRIGADO, DEPOIS DE TUDO
Então, quando @ jw013 especifica que:
... entre suas críticas errôneas a essa resposta, ele está, na verdade, sem querer, desculpando o único método usado aqui, que basicamente funciona apenas para:
RESPONDA
Todas as respostas aqui são boas, mas nenhuma delas está totalmente correta. Todo mundo parece reivindicar que você não pode seguir seu caminho de forma dinâmica e permanente
#!bang
. Aqui está uma demonstração de como configurar um caminho independente de caminho:DEMO
SAÍDA
Entende? Acabamos de fazer o script se sobrescrever. E isso só acontece uma vez após uma
git
sincronização. A partir desse ponto, ele tem o caminho certo na linha #! Bang.Agora, quase tudo isso lá em cima é apenas fofo. Para fazer isso com segurança, você precisa:
Uma função definida na parte superior e chamada na parte inferior que faz a escrita. Dessa forma, armazenamos tudo o que precisamos na memória e garantimos que todo o arquivo seja lido antes de começarmos a escrevê-lo.
Alguma maneira de determinar qual deve ser o caminho.
command -v
é muito bom para isso.Heredocs realmente ajudam porque são arquivos reais. Enquanto isso, eles armazenam seu script. Você pode usar cordas também, mas ...
Você precisa garantir que o shell leia o comando que sobrescreve seu script na mesma lista de comandos que a executada.
Veja:
Observe que eu mudei o
exec
comando apenas uma linha. Agora:Não recebo a segunda metade da saída porque o script não pode ler no próximo comando. Ainda assim, porque o único comando que faltava era o último:
O script surgiu como deveria - principalmente porque estava no heredoc - mas se você não planejar corretamente, poderá interromper o fluxo de arquivos, o que aconteceu comigo acima.
fonte
man command
uma opinião contraditória.man command
uma opinião contraditória - não a encontre. Você pode me direcionar para a seção / parágrafo específico de que estava falando?man sh
- procure por 'command -v'. Eu sabia que estava em uma dasman
páginas que eu estava olhando no outro dia.command -v
exemplo do qual você estava falandoman sh
. Esse é um script de instalação normal e não modificado automaticamente . Até os instaladores independentes contêm apenas entradas de pré-modificação e as produzem em outro lugar. Eles não se reescrevem da maneira que você está recomendando.Aqui está uma maneira de ter um script auto-modificado que corrige seu shebang. Este código deve ser anexado ao seu script real.
Alguns comentários:
Deve ser executado uma vez por alguém que tenha o direito de criar e excluir arquivos no diretório em que o script está localizado.
Ele usa apenas a sintaxe herdada do shell bourne, pois, apesar da crença popular,
/bin/sh
não é garantido que seja um shell POSIX, mesmo em sistemas operacionais compatíveis com POSIX.Ele configurou o PATH para um compatível com POSIX, seguido por uma lista de possíveis locais zsh para evitar escolher um zsh "falso".
Se, por algum motivo, um script auto-modificável não for bem-vindo, seria trivial distribuir dois scripts em vez de um, o primeiro sendo o que você deseja corrigir e o segundo, o que sugeri que foi modificado levemente para processar o primeiro.
fonte
/bin/sh
ponto é bom - mas nesse caso você precisa de um pré#!
- modificado ? E não éawk
tão provável que seja falso comozsh
é?sed -i
fazem de qualquer maneira. Pessoalmente, acho que o$PATH
problema observado nos comentários de outra resposta e que você aborda com a maior segurança possível, em algumas linhas, é melhor tratado com definições simples e explícitas de dependências e / ou testes rigorosos e explícitos - por exemplo, agoragetconf
pode ser falso, mas as chances são perto de zero, mesmo que eles foram parazsh
eawk.
$(getconf PATH)
não é Bourne.cp $0 $0.old
é a sintaxe zsh. O equivalente Bourne seriacp "$0" "$0.old"
que você gostariacp -- "$0" "$0.old"