Muitas linhas shebang (declaração de script) - alguma maneira de reduzir sua quantidade?

12

Eu tenho um projeto composto por cerca de 20 .sharquivos pequenos . Eu os chamo de "pequenos" porque, geralmente, nenhum arquivo tem mais de 20 linhas de código. Adotei uma abordagem modular porque, portanto, sou fiel à filosofia Unix e é mais fácil para mim manter o projeto.

No início de cada .sharquivo, eu coloquei #!/bin/bash.

Simplificando, entendo que as declarações de script têm dois propósitos:

  1. Eles ajudam o usuário a lembrar qual shell é necessário para executar o arquivo (digamos, depois de alguns anos sem usá-lo).
  2. Eles garantem que o script seja executado apenas com um determinado shell (nesse caso, Bash) para evitar comportamentos inesperados no caso de outro shell ser usado.

Quando um projeto começa a crescer, digamos 5 arquivos para 20 arquivos ou de 20 para 50 arquivos (não neste caso, mas apenas para demonstrar), temos 20 linhas ou 50 linhas de declarações de script. Admito que, embora possa ser engraçado para alguns, parece um pouco redundante usar 20 ou 50 em vez de dizer apenas 1 por projeto (talvez no arquivo principal do projeto).

Existe uma maneira de evitar essa suposta redundância de 20 ou 50 ou um número muito maior de linhas de declarações de script usando alguma declaração de script "global", em algum arquivo principal?

user9303970
fonte
2
Você não deveria, mas poderia; remova-o e execute todos os seus scripts com/bin/bash -x "$script"
jesse_b
... Normalmente, quando algo chega a esse ponto, eu já concluí que é hora de parar de usar shell scripts e obter uma linguagem de script real para fazer o trabalho.
Shadur

Respostas:

19

Embora seu projeto agora possa consistir apenas de 50 scripts Bash, mais cedo ou mais tarde ele começará a acumular scripts escritos em outros idiomas, como Perl ou Python (pelos benefícios que essas linguagens de script têm que o Bash não possui).

Sem uma #!linha adequada em cada script, seria extremamente difícil usar os vários scripts sem também saber qual intérprete usar. Não importa se todos os scripts são executados a partir de outros scripts , isso apenas move a dificuldade dos usuários finais para os desenvolvedores. Nenhum desses dois grupos de pessoas precisa saber em que idioma o script foi escrito para poder usá-lo.

Os scripts de shell executados sem uma #!linha e sem um intérprete explícito são executados de maneiras diferentes, dependendo de qual shell os chama (consulte, por exemplo, a pergunta Qual interpretador de shell executa um script sem shebang? E especialmente a resposta de Stéphane ), que não é o que você deseja em um ambiente de produção (você deseja um comportamento consistente e possivelmente até portabilidade).

Os scripts executados com um intérprete explícito serão executados por esse intérprete, independentemente do que #!diz a linha. Isso causará problemas mais adiante, se você decidir reimplementar, digamos, um script Bash em Python ou em qualquer outro idioma.

Você deve gastar essas teclas extras e sempre adicionar uma #!linha a cada script.


Em alguns ambientes, há textos legais padronizados com vários parágrafos em cada script em cada projeto. Fique muito feliz porque é apenas uma #!linha que parece "redundante" em seu projeto.

Kusalananda
fonte
A resposta deve ser expandida com o seguinte: O shell determina o intérprete pela #!linha assim que o arquivo tiver permissão executável e não for carregado por um intérprete, mas pelo shell. Ou seja, /path/to/myprog.plexecuta um programa perl se a #!linha estiver presente (apontando para o interpretador perl) e o arquivo tiver permissão de execução.
Rexkogitans
11

Você entende mal o ponto #!. Na verdade, é uma diretiva para o sistema operacional executar este programa com o intérprete definido.

Portanto, pode haver um script #!/usr/bin/perle o programa resultante pode ser executado como ./myprogramo interpretador perl. Semelhante #!/usr/bin/pythonfaria com que um programa fosse executado em python.

Portanto, a linha #!/bin/bashdiz ao sistema operacional para executar este programa no bash.

Aqui está um exemplo:

$ echo $0
/bin/ksh

$ cat x
#!/bin/bash

ps -aux | grep $$

$ ./x
sweh      2148  0.0  0.0   9516  1112 pts/5    S+   07:58   0:00 /bin/bash ./x
sweh      2150  0.0  0.0   9048   668 pts/5    S+   07:58   0:00 grep 2148

Portanto, apesar de meu shell ser ksh, o programa "x" é executado no bash por causa da #!linha, e podemos ver isso explicitamente na lista de processos.

Stephen Harris
fonte
ps -auxpode dar aviso,
Weijun Zhou 03/02
@WeijunZhou Say more ...
Kusalananda
Alguma versão do psdá o aviso Warning: bad syntax, perhaps a bogus '-'?.
Weijun Zhou 03/02
2
pssuporta a sintaxe de argumento BSD e UXIX . -auxestá errado UNIX Stephen provavelmente quer dizer auxqual é o BSD correto
Jasen 03/02
1
Pessoas, 2 coisas; (1) concentre-se no conceito de exemplo (que psmostra o chamado bash) e não na sintaxe explícita; (2) ps -auxfunciona perfeitamente no CentOS 7 e Debian 9 sem avisos; esse cut'n'paste era exatamente o resultado da minha máquina; toda a discussão sobre se -é necessário ou não é aplicável a versões mais antigas dos procs do linux, e não às versões atuais.
Stephen Harris
9

Em .sh

O que é ruim é o .shfinal do nome do arquivo.

Imagine que você reescreve um dos scripts em python.

Bem, agora você precisa alterar a primeira linha para #!/usr/bin/python3isso não é tão ruim, pois você também precisa alterar todas as outras linhas de código no arquivo. No entanto, você também deve alterar o nome do arquivo de prog.shpara prog.py. E então você precisa encontrar em todos os outros scripts e todos os scripts de usuário (os que você nem sabia que existiam) e alterá-los para usar o novo script. sed -e 's/.sh/.py/g'pode ajudar aqueles que você conhece. Mas agora sua ferramenta de controle de revisão está mostrando uma alteração em muitos arquivos não relacionados.

Alternativamente fazer isso da maneira Unix eo nome do programa prognão prog.sh.

Em #!

Se você tiver o correto #!, e defina a permissão / modo para incluir execute. Então você não precisa conhecer o intérprete. O computador fará isso por você.

chmod +x prog #on gnu adds execute permission to prog.
./prog #runs the program.

Para sistemas não-gnu, leia o manual chmod(e aprenda octal), talvez o leia de qualquer maneira.

ctrl-alt-delor
fonte
1
Hummm, as extensões não são necessariamente ruins, pelo menos ajudam a se comunicar com outros usuários, qual é o tipo de arquivo.
Sergiy Kolodyazhnyy 03/02
@SergiyKolodyazhnyy Mas você não precisa conhecer o idioma, só precisa executá-lo. Até a Microsoft está tentando se afastar das extensões (infelizmente estão fazendo isso por escondê-las). No entanto, eu concordo que eles poderiam ser uma boa idéia: .imagepara fotos. .audiopara som etc. Assim, dizendo o que é, mas não sobre a implementação / codificação.
ctrl-alt-Delor
1
@ ctrl-alt-Delor Se o arquivo é chamado launch-missilesou delete-project-and-make-manager-pissedeu não quero fazer "basta executá-lo" quando eu tinha apenas curioso sobre a linguagem :)
Sergiy Kolodyazhnyy
2
se você estiver curioso para o idioma, use o filecomando reconhece a maioria dos tipos de arquivo.
Jasen
2
Eu concordo que as extensões são ruins para scripts executáveis, exatamente pelas razões indicadas. Eu uso uma extensão .sh para um script que precisa ser originado por outro script shell (ou seja, um script não executável) - nesse cenário, o sufixo é importante / válido porque nos diz como podemos usar o arquivo.
Laurence Renshaw
8

[As linhas hashbang] garantem que o script seja executado apenas com um determinado shell (Bash nesse caso) para evitar comportamento inesperado no caso de outro shell ser usado.

Provavelmente vale ressaltar que isso está completamente errado. A linha hashbang de forma alguma impede que você tente alimentar o arquivo com um shell / intérprete incorreto:

$ cat array.sh
#!/bin/bash
a=(a b c)
echo ${a[1]} $BASH_VERSION
$ dash array.sh
array.sh: 2: array.sh: Syntax error: "(" unexpected

(ou similarmente com awk -f array.shetc.)


Mas, em qualquer caso, outra abordagem da modularidade seria definir funções de shell nos arquivos, uma função por arquivo, se você preferir, e depois sourceos arquivos do programa principal. Dessa forma, você não precisaria de hashbangs: eles seriam tratados como quaisquer outros comentários e tudo seria executado usando o mesmo shell. A desvantagem seria que você precisaria sourcedos arquivos com as funções (por exemplo for x in functions/*.src ; do source "$x" ; done), e todos eles rodariam usando o mesmo shell, para que você não pudesse substituir diretamente um deles por uma implementação em Perl.

ilkkachu
fonte
+1 por exemplo, com matrizes bash. Os usuários não familiarizados com vários shells ou com o que o POSIX define podem assumir que existem alguns recursos de um shell em outro. Também para funções, isso pode ser relevantes: unix.stackexchange.com/q/313256/85039
Sergiy Kolodyazhnyy
4

Existe uma maneira de evitar essa suposta redundância de 20 ou 50 ou um número muito maior de linhas de declarações de script usando alguma declaração de script "global", em algum arquivo principal?

Existe - é chamado portabilidade ou conformidade com POSIX. Procure sempre escrever scripts de maneira portátil e neutra em shell, origine-os somente de um script que use #!/bin/shou quando você use /bin/shinterativamente (ou pelo menos um shell compatível com POSIX, como ksh).

No entanto, scripts portáteis não o salvam de:

  • necessidade de usar os recursos de uma das conchas ( a resposta de ilkkachu é um exemplo)
  • necessidade de usar linguagens de script mais avançadas que shells (Python e Perl)
  • Erro PEBKAC: seu script cai nas mãos do usuário J.Doe, que executa o script não como você pretendia, por exemplo, ele executa o /bin/shscript csh.

Se eu posso expressar minha opinião, "evitar redundância" eliminando #!é uma meta errada. O objetivo deve ser um desempenho consistente e, na verdade, um script de trabalho que funcione, e considere casos extremos, segue certas regras gerais, como não analisar ls ou sempre citar variáveis, a menos que seja necessário dividir palavras .

Eles ajudam o usuário a lembrar qual shell é necessário para executar o arquivo (digamos, depois de alguns anos sem usá-lo).

Eles não são realmente para o usuário - são para o sistema operacional executar o interpretador adequado. "Adequado", neste caso, significa que o SO encontra o que está em shebang; se o código abaixo for incorreto - isso é culpa do autor. Quanto à memória do usuário, acho que o "usuário" de programas como o Microsoft Word ou o Google Chrome nunca precisou saber em que programas de idiomas estão escritos; autores podem precisar.

Por outro lado, scripts escritos de maneira portável podem eliminar a necessidade de lembrar qual shell você usou originalmente, porque os scripts portáteis devem funcionar em qualquer shell compatível com POSIX.

Eles garantem que o script seja executado apenas com um determinado shell (nesse caso, Bash) para evitar comportamentos inesperados no caso de outro shell ser usado.

Eles não. O que realmente impede um comportamento inesperado é escrever scripts portáteis.

Sergiy Kolodyazhnyy
fonte
1

A razão pela qual você precisa colocar #!/bin/bashno topo de cada script é que você está executando-o como um processo separado.

Quando você executa um script como um novo processo, o sistema operacional vê que é um arquivo de texto (em oposição a um executável binário) e precisa saber qual interpretador executar. Se você não contar, o sistema operacional só pode adivinhar, usando sua configuração padrão (que um administrador pode alterar). Portanto, a #!linha (além de adicionar permissões de executáveis, é claro) transforma um simples arquivo de texto em um executável que pode ser executado diretamente, com a confiança de que o sistema operacional sabe o que fazer com ele.

Se você deseja remover a #!linha, suas opções são:

  1. Execute o script diretamente (como você está fazendo agora) e espere que o intérprete padrão corresponda ao script - você provavelmente está seguro na maioria dos sistemas Linux, mas não é portátil em outros sistemas (por exemplo, Solaris) e pode ser alterado para qualquer coisa (eu poderia até configurá-lo para executar vimno arquivo!).

  2. Execute o script usando bash myscript.sh. Isso funciona bem, mas você precisa especificar o caminho para o script, e é confuso.

  3. Origem do script usando . myscript.sh, que executa o script no processo atual do intérprete (ou seja, não como um subprocesso). Mas isso quase certamente exigirá modificações nos seus scripts e os tornará menos modulares / reutilizáveis.

Nos casos 2 e 3, o arquivo não precisa (e não deveria ter) executar permissões, e fornecer um .sh(ou .bash) sufixo é uma boa idéia.

Mas nenhuma dessas opções parece boa para o seu projeto, como você disse que deseja manter seus scripts modulares (=> independentes e independentes), portanto, você deve manter sua #!/bin/bashlinha no topo dos scripts.

Na sua pergunta, você também disse que está seguindo a filosofia do Unix. Se isso for verdade, você deve aprender para que #!serve realmente a linha e continuar usando-a em todos os scripts executáveis!

Laurence Renshaw
fonte
Obrigado pela boa resposta. Eu amei tudo na resposta e pensado upvoting até este: then you should learn what the #! line is really for, and keep using it in every executable script!. Pode-se seguir a filosofia da universidade até certo ponto do conhecimento. Depois de todas essas respostas, entendi por que ela pode ser incluída nesse termo. Sugiro editar a resposta, substituindo-a por "Veja o shebang como uma parte interna da filosofia do Unix que você respeita e adota".
user9303970
1
Desculpe se eu fui direto ou se esse comentário pareceu rude. Seus comentários sugeriram que você prefere trabalhar em um IDE - com um 'projeto' que permita definir regras globais para os scripts desse projeto. Essa é uma maneira válida de desenvolver, mas não se encaixa bem em tratar cada script como um programa independente e independente (a filosofia do Unix a que você se referiu).
21418 Laurence Renshaw # 1: