Após uma pesquisa muito rápida, parece que o Bash é uma linguagem completa de Turing .
Eu me pergunto, por que o Bash é usado quase exclusivamente para escrever scripts relativamente simples? Como um shell Bash vem com o Linux, você pode executar scripts de shell sem nenhum interpretador ou compilador externo, conforme necessário para outras linguagens de computador populares. Essa é uma enorme vantagem, que poderia compensar a mediocridade da própria linguagem em alguns casos.
Então, existe um limite para a complexidade de tais programas? O Bash puro é usado para escrever programas complexos? É possível escrever, digamos, um compressor / descompressor de arquivos no Bash puro? Um compilador? Um simples videogame?
É tão escassamente usado apenas porque existem apenas ferramentas de depuração muito limitadas?
fonte
sh
scriptconfigure
usado como parte do processo de compilação para muitos pacotes un * x não é 'relativamente simples'.m4
macros.configure
Os scripts também são lentos, fazem um monte de trabalho inútil e foram alvo de alguns divertidos comentários. É claro que o shell pode ser usado para programas grandes, mas também as pessoas criaram computadores com o Game of Life e o Minecraft de Conway , e também existem linguagens de programação como Brainf ** k e Hexagony . Aparentemente, algumas pessoas gostam de criar algo com átomos realmente pequenos e confusos. Você pode até mesmo vender jogos com essa ideia ...Respostas:
O conceito de Turing completude é totalmente separado de muitos outros conceitos úteis em uma linguagem de programação na grande : usabilidade, expressividade, understandabilty, velocidade, etc.
Se Turing-completude foram todos nós necessário, não teríamos quaisquer linguagens de programação em tudo , nem mesmo linguagem assembly . Todos os programadores de computador escreveriam apenas no código da máquina , já que nossas CPUs também são completas em Turing.
Scripts shell grandes e complexos - como os
configure
scripts produzidos pelo GNU Autoconf - são atípicos por vários motivos:Até relativamente recentemente, você não podia contar com um shell compatível com POSIX em todos os lugares .
Muitos sistemas, principalmente os mais antigos, tecnicamente possuem um shell compatível com POSIX em algum lugar do sistema, mas pode não estar em um local previsível
/bin/sh
. Se você está escrevendo um script de shell e ele precisa ser executado em muitos sistemas diferentes, como então você escreve a linha shebang ? Uma opção é seguir em frente e usar/bin/sh
, mas opte por se restringir ao dialeto shell Bourne anterior ao POSIX, caso ele seja executado em um sistema desse tipo.Os reservatórios Bourne pré-POSIX nem possuem aritmética embutida; você precisa chamar
expr
oubc
fazer isso.Mesmo com um shell POSIX, você está perdendo matrizes associativas e outros recursos que esperamos encontrar nas linguagens de script Unix desde que o Perl se tornou popular no início dos anos 90 .
Esse fato da história significa que há uma tradição de décadas em ignorar muitos dos recursos poderosos dos modernos interpretadores de scripts da família Bourne, puramente porque você não pode contar com eles em todos os lugares.
Na verdade, isso ainda continua até hoje: o Bash não conseguiu matrizes associativas até a versão 4 , mas você pode se surpreender com a quantidade de sistemas ainda em uso baseados no Bash 3. A Apple ainda envia o Bash 3 com o macOS em 2017 - aparentemente para motivos de licenciamento - e os servidores Unix / Linux geralmente são executados praticamente sem tocar por muito tempo, portanto, você pode ter um sistema antigo estável ainda executando o Bash 3, como uma caixa do CentOS 5. Se você possui esses sistemas em seu ambiente, não pode usar matrizes associativas em scripts de shell que precisam ser executados neles.
Se sua resposta para esse problema é que você apenas escreve scripts de shell para sistemas "modernos", precisa lidar com o fato de que o último ponto de referência comum para a maioria dos shells do Unix é o padrão de shell POSIX , que permanece praticamente inalterado desde que foi introduzido em 1989. Existem muitas conchas diferentes com base nesse padrão, mas todas divergiram em graus variados desse padrão. Para tirar arrays associativos de novo,
bash
,zsh
, eksh93
todos têm essa característica, mas existem várias incompatibilidades de implementação. Sua escolha, então, é usar apenas o Bash, ou apenas o Zsh, ou apenas o usoksh93
.Se a sua resposta para esse problema for "instale o Bash 4" ou
ksh93
, o que for, por que não "instale" o Perl, o Python ou o Ruby? Isso é inaceitável em muitos casos; os padrões importam.Nenhuma das linguagens de script de shell da família Bourne suporta módulos .
O mais próximo que você pode chegar de um sistema de módulo em um script de shell é o
.
comando - também conhecidosource
em variantes de shell Bourne mais modernas - que falha em vários níveis em relação a um sistema de módulo apropriado, o mais básico dos quais é o namespacing .Independentemente da linguagem de programação, o entendimento humano começa a sinalizar quando qualquer arquivo único em um programa geral maior excede alguns milhares de linhas. O motivo pelo qual estruturamos programas grandes em muitos arquivos é para que possamos abstrair seu conteúdo em uma ou duas frases, no máximo. O arquivo A é o analisador de linha de comando, o arquivo B é a bomba de E / S da rede, o arquivo C é o calço entre a biblioteca Z e o restante do programa, etc. Quando seu único método para reunir muitos arquivos em um único programa é a inclusão de texto , você limita o tamanho dos seus programas para crescer razoavelmente.
Para comparação, seria como se a linguagem de programação C não tivesse vinculador, apenas
#include
instruções. Esse dialeto C-lite não precisaria de palavras-chave comoextern
oustatic
. Esses recursos existem para permitir modularidade.O POSIX não define uma maneira de definir variáveis de escopo para uma única função de script de shell, muito menos para um arquivo.
Isso efetivamente torna todas as variáveis globais , o que prejudica a modularidade e a composição.
Existem soluções para este em conchas de pós-POSIX - certamente
bash
,ksh93
ezsh
pelo menos - mas isso só traz de volta ao ponto 1 acima.Você pode ver o efeito disso nos guias de estilo na gravação de macro do GNU Autoconf, onde eles recomendam que você prefixe os nomes das variáveis com o nome da própria macro, levando a nomes de variáveis muito longos apenas para reduzir a chance de colisão de maneira aceitável perto de zero.
Mesmo C é melhor nessa pontuação, por uma milha. Além de a maioria dos programas C serem escritos principalmente com variáveis locais de função, C também oferece suporte ao escopo de blocos, permitindo que vários blocos em uma única função reutilizem nomes de variáveis sem contaminação cruzada.
As linguagens de programação do shell não possuem biblioteca padrão.
É possível argumentar que a biblioteca padrão de uma linguagem de script de shell é o conteúdo de
PATH
, mas que apenas diz que, para obter alguma conseqüência, um script de shell precisa chamar outro programa inteiro, provavelmente um escrito em uma linguagem mais poderosa para começar com.Também não existe um arquivo amplamente usado de bibliotecas de utilitários de shell, como no CPAN do Perl . Sem uma grande biblioteca disponível de código de utilitário de terceiros, um programador deve escrever mais código manualmente, para que seja menos produtivo.
Mesmo ignorando o fato de que a maioria dos shell scripts dependem de programas externos normalmente escritos em C para obter alguma coisa útil fazer, há a sobrecarga de todos aqueles
pipe()
→fork()
→exec()
cadeias de chamadas. Esse padrão é bastante eficiente no Unix, comparado ao IPC e ao processo iniciado em outros sistemas operacionais, mas aqui está efetivamente substituindo o que você faria com uma chamada de sub - rotina em outra linguagem de script, que é muito mais eficiente ainda. Isso coloca um limite sério no limite superior da velocidade de execução de scripts de shell.Os scripts de shell têm pouca capacidade interna de aumentar seu desempenho via execução paralela.
Shells Bourne tem
&
,wait
e dutos para isso, mas isso é em grande parte apenas útil para compor vários programas, não para alcançar CPU ou I / paralelismo S. É provável que você não consiga identificar os núcleos ou saturar uma matriz RAID apenas com scripts de shell e, se o fizer, provavelmente poderá obter um desempenho muito maior em outros idiomas.Os pipelines, em particular, são maneiras fracas de aumentar o desempenho via execução paralela. Ele permite apenas que dois programas sejam executados em paralelo, e um dos dois provavelmente será bloqueado na E / S de / para o outro a qualquer momento.
Há maneiras dos últimos dias em torno deste, como
xargs -P
e GNUparallel
, mas isto só recai para o ponto 4 acima.Com efetivamente nenhuma capacidade embutida de tirar o máximo proveito dos sistemas com vários processadores, os scripts de shell sempre serão mais lentos do que um programa bem escrito em uma linguagem que pode usar todos os processadores do sistema. Para pegar o
configure
exemplo de script GNU Autoconf novamente, dobrar o número de núcleos no sistema fará pouco para melhorar a velocidade na qual ele é executado.As linguagens de script do shell não têm ponteiros ou referências .
Isso impede que você faça várias coisas facilmente em outras linguagens de programação.
Por um lado, a incapacidade de se referir indiretamente a outra estrutura de dados na memória do programa significa que você está limitado às estruturas de dados internas . Seu shell pode ter matrizes associativas , mas como elas são implementadas? Existem várias possibilidades, cada uma com diferentes vantagens: árvores vermelho-pretas , árvores AVL e tabelas de hash são as mais comuns, mas existem outras. Se você precisar de um conjunto diferente de vantagens e desvantagens, ficará sem dinheiro porque, sem referências, não há como manipular manualmente muitos tipos de estruturas de dados avançadas. Você está preso ao que recebeu.
Ou pode ser que você precise de uma estrutura de dados que nem sequer tenha uma alternativa adequada incorporada ao seu interpretador de script de shell, como um gráfico acíclico direcionado , necessário para modelar um gráfico de dependência . Eu tenho sido programação por décadas, e a única maneira que eu posso pensar em fazer isso em um shell script seria abusar do sistema de arquivos , usando links simbólicos como referências falsas. Esse é o tipo de solução que você obtém quando confia apenas na integridade de Turing, que não diz nada sobre se a solução é elegante, rápida ou fácil de entender.
Estruturas de dados avançadas são apenas um uso para ponteiros e referências. Existem vários outros aplicativos para eles , o que simplesmente não pode ser feito facilmente em uma linguagem de script de shell da família Bourne.
Eu poderia continuar, mas acho que você está entendendo o ponto aqui. Simplificando, existem muitas linguagens de programação mais poderosas para sistemas do tipo Unix.
Claro, e é exatamente por isso que o GNU Autoconf usa um subconjunto intencionalmente restrito da família Bourne de linguagens de script shell para suas
configure
saídas de script: para que seusconfigure
scripts sejam executados praticamente em todos os lugares.Você provavelmente não encontrará um grupo maior de crentes na utilidade de escrever em um dialeto Bourne shell altamente portátil do que os desenvolvedores do GNU Autoconf, mas sua própria criação é escrita principalmente em Perl, além de alguns
m4
, e apenas um pouco de shell roteiro; somente a saída do Autoconf é um script shell Bourne puro. Se isso não implora a questão de quão útil é o conceito "Bourne em todos os lugares", não sei o que será.Tecnicamente falando, não, como sugere a observação de Turing-completeness.
Mas isso não é o mesmo que dizer que scripts de shell arbitrariamente grandes são agradáveis de escrever, fáceis de depurar ou rápidos de executar.
Bash "puro", sem chamadas para as coisas no
PATH
? O compressor provavelmente é possível usandoecho
seqüências de escape hexagonais, mas seria bastante doloroso. Pode ser impossível escrever o descompactador dessa maneira devido à incapacidade de manipular dados binários no shell . Você acabaria chamandood
e traduzindo dados binários para o formato de texto, a maneira nativa do shell de manipular dados.Depois que você começa a falar sobre o uso de scripts de shell da maneira que se pretendia, como cola para direcionar outros programas
PATH
, as portas se abrem, porque agora você está limitado apenas ao que pode ser feito em outras linguagens de programação, ou seja, você não tem limites. Um script shell que recebe todo o seu poder, chamando a outros programas noPATH
não correr tão rápido como programas monolíticas escritos em linguagens mais poderosas, mas não executado.E esse é o ponto. Se você precisa de um programa para executar rapidamente, ou se precisa ser poderoso por si só, em vez de emprestar energia de outras pessoas, não o escreve com casca.
Aqui está Tetris com casca . Outros jogos estão disponíveis, se você for procurar.
Eu colocaria o suporte à ferramenta de depuração em 20º lugar na lista de recursos necessários para dar suporte à programação em geral. Muitos programadores confiam muito mais na
printf()
depuração do que nos depuradores apropriados, independentemente da linguagem.No shell, você tem
echo
eset -x
, que juntos são suficientes para depurar muitos problemas.fonte
&
você pode executar processos em paralelo. Você podewait
concluir os processos filhos. Você pode configurar pipelines e redes mais complexas de pipes usando pipes nomeados. Mais importante, é simples executar o processamento paralelo da maneira correta, com muito pouco código padrão e evitar os riscos e as dificuldades do multiencadeamento de memória compartilhada.Podemos caminhar ou nadar em qualquer lugar, então por que nos incomodamos com bicicletas, carros, trens, barcos, aviões e outros veículos? Claro, caminhar ou nadar pode ser cansativo, mas há uma enorme vantagem em não precisar de nenhum equipamento extra.
Por um lado, embora o bash seja Turing-complete, ele não é bom para manipular dados que não sejam números inteiros (não muito grandes), strings, matrizes (unidimensionais) de strings e mapas finitos de strings para strings. Qualquer outro tipo de dados precisa de uma codificação incômoda, o que dificulta a gravação do programa e geralmente impõe um desempenho que não é bom o suficiente na prática. Por exemplo, operações de ponto flutuante no bash são difíceis e lentas.
Além disso, o bash tem muito poucas maneiras de interagir com seu ambiente. Ele pode executar processos, pode executar alguns tipos simples de acesso a arquivos (através do redirecionamento), e é isso. O Bash também possui um cliente de rede do lado do cliente. O Bash pode emitir bytes nulos com facilidade (
printf \\0
), mas não analisa bytes nulos em sua entrada, o que o torna inadequado para ler dados binários. O Bash não pode fazer outras coisas diretamente: precisa chamar programas externos para isso. E tudo bem: os shells são projetados com o objetivo principal de executar programas externos! Os reservatórios são a linguagem da cola para combinar programas. Mas se você estiver executando um programa externo, isso significa que o programa precisa estar disponível - e você reduz a vantagem da portabilidade:)O Bash não possui nenhum tipo de recurso que facilite a gravação de programas robustos, além de
set -e
. Não possui tipos (úteis), espaços para nome, módulos ou estruturas de dados aninhadas. Bugs são a dificuldade número um na programação; embora a facilidade de escrever programas livres de bugs nem sempre seja o fator decisivo na escolha de um idioma, o bash está mal classificado nesse sentido. O Bash também tem um desempenho ruim ao fazer outras coisas além de combinar programas.Por um longo tempo, o bash não funcionou no Windows, e ainda hoje não está presente em uma instalação padrão do Windows e não é executado de forma totalmente nativa (mesmo na WSL) no sentido de que não possui interfaces para Recursos nativos do Windows. O Bash não é executado no iOS e não é instalado por padrão no Android. Portanto, a menos que você esteja escrevendo um aplicativo somente para Unix, o bash não é de todo portátil.
Exigir um compilador não é um problema de portabilidade. O compilador é executado na máquina dos desenvolvedores. Exigir um intérprete ou bibliotecas de terceiros pode ser um problema, mas no Linux é um problema resolvido por meio de pacotes de distribuição e, no Windows, Android e iOS, as pessoas geralmente agrupam componentes de terceiros em seus pacotes de aplicativos. Portanto, o tipo de preocupação de portabilidade que você tem em mente não é uma preocupação prática para aplicativos comuns.
Minha resposta se aplica a conchas que não sejam o bash. Alguns detalhes variam de shell para shell, mas a idéia geral é a mesma.
fonte
Algumas razões para não usar scripts de shell para programas grandes, bem no topo da minha cabeça:
mkdir
ougrep
internamente.zsh
tem algum suporte. Isso ocorre também porque a interface para programas externos é baseada principalmente em texto e\0
é usada como um separador.bash -c ...
oussh -c ...
)fonte