Après quelques recherches très rapides, il semble que Bash soit un langage complet de Turing .
Je me demande, pourquoi Bash est-il utilisé presque exclusivement pour écrire des scripts relativement simples? Comme un shell Bash est livré avec Linux, vous pouvez exécuter des scripts shell sans aucun interpréteur ou compilateur externe, comme requis pour d'autres langages informatiques populaires. C'est un énorme avantage, qui pourrait compenser la médiocrité de la langue elle-même dans certains cas.
Alors, y a-t-il une limite à la complexité de tels programmes? Bash pur est-il utilisé pour écrire des programmes complexes? Est-il possible d'écrire, disons, un compresseur / décompresseur de fichiers en pur Bash? Un compilateur? Un jeu vidéo simple?
Est-il si peu utilisé simplement parce qu'il n'y a que des outils de débogage très limités?
la source
sh
scriptconfigure
qui est utilisé dans le cadre du processus de construction d'un grand nombre de packages un * x n'est pas «relativement simple».m4
macros.configure
les scripts sont également lents, font tout un tas de travaux inutiles et ont fait l'objet de quelques diatribes amusantes. Bien sûr, le shell peut être utilisé pour de gros programmes, mais là encore, les gens ont également fabriqué des ordinateurs avec Game of Life et Minecraft de Conway , et il existe également des langages de programmation comme Brainf ** k et Hexagony . Apparemment, certaines personnes aiment juste construire quelque chose à partir d'atomes vraiment petits et déroutants. Vous pouvez même vendre des jeux avec cette idée ...Réponses:
Le concept d' exhaustivité de Turing est entièrement distinct de nombreux autres concepts utiles dans un langage pour la programmation au sens large : convivialité, expressivité, compréhensibilité, vitesse, etc.
Si Turing complet étaient tous nous avions besoin, nous n'avons pas les langages de programmation du tout , pas même langage d' assemblage . Les programmeurs informatiques écrivent tous du code machine , car nos processeurs sont également complets.
Les scripts shell volumineux et complexes - tels que les
configure
scripts générés par GNU Autoconf - sont atypiques pour de nombreuses raisons:Jusqu'à relativement récemment, vous ne pouviez pas compter sur un shell compatible POSIX partout .
De nombreux systèmes, en particulier les plus anciens, ont techniquement un shell compatible POSIX quelque part sur le système, mais il peut ne pas se trouver dans un emplacement prévisible comme
/bin/sh
. Si vous écrivez un script shell et qu'il doit fonctionner sur de nombreux systèmes différents, comment écrivez-vous la ligne shebang ? Une option est d'aller de l'avant et d'utiliser/bin/sh
, mais choisissez de vous limiter au dialecte shell pré-POSIX Bourne au cas où il serait exécuté sur un tel système.Les coques Bourne pré-POSIX n'ont même pas d'arithmétique intégrée; vous devez appeler
expr
oubc
faire cela.Même avec un shell POSIX, vous manquez des tableaux associatifs et d'autres fonctionnalités que nous nous attendions à trouver dans les langages de script Unix depuis que Perl est devenu populaire au début des années 1990 .
Ce fait historique signifie qu'il existe une tradition de plusieurs décennies d'ignorer bon nombre des fonctionnalités puissantes des interpréteurs de scripts shell modernes de la famille Bourne simplement parce que vous ne pouvez pas compter sur les avoir partout.
Cela continue toujours à ce jour, en fait: Bash n'a pas reçu de tableaux associatifs avant la version 4 , mais vous pourriez être surpris du nombre de systèmes encore utilisés basés sur Bash 3. Apple livre toujours Bash 3 avec macOS en 2017 - apparemment pour pour des raisons de licence - et les serveurs Unix / Linux tournent souvent en production pratiquement intacts pendant très longtemps, donc vous pourriez avoir un ancien système stable exécutant toujours Bash 3, comme une boîte CentOS 5. Si vous avez de tels systèmes dans votre environnement, vous ne pouvez pas utiliser de tableaux associatifs dans des scripts shell qui doivent s'exécuter sur eux.
Si votre réponse à ce problème est que vous écrivez uniquement des scripts shell pour des systèmes "modernes", vous devez alors faire face au fait que le dernier point de référence commun pour la plupart des shells Unix est le standard shell POSIX , qui est en grande partie inchangé depuis qu'il était introduit en 1989. Il existe de nombreux obus différents basés sur cette norme, mais ils ont tous divergé à des degrés divers de cette norme. Pour prendre des tableaux associatifs encore
bash
,zsh
etksh93
tous ont cette fonctionnalité, mais il y a plusieurs incompatibilités de mise en œuvre. Votre choix est donc à n'utiliser Bash, ou seulement utiliser zsh, ou seulement utiliser .ksh93
Si votre réponse à ce problème est ", alors installez simplement Bash 4"
ksh93
, ou quoi que ce soit, alors pourquoi ne pas "simplement" installer Perl ou Python ou Ruby à la place? C'est inacceptable dans de nombreux cas; les défauts comptent.Aucun des modules de prise en charge des langages de script shell de la famille Bourne .
Le plus proche d'un système de modules dans un script shell est la
.
commande - aliassource
dans les variantes de shell Bourne plus modernes - qui échoue à plusieurs niveaux par rapport à un système de modules approprié, dont le plus fondamental est l' espace de noms .Indépendamment du langage de programmation, la compréhension humaine commence à marquer lorsqu'un fichier unique dans un programme global plus grand dépasse quelques milliers de lignes. La raison même pour laquelle nous structurons de gros programmes en plusieurs fichiers est de pouvoir en résumer le contenu en une phrase ou deux au maximum. Le fichier A est l'analyseur de ligne de commande, le fichier B est la pompe d'E / S réseau, le fichier C est la cale entre la bibliothèque Z et le reste du programme, etc. Lorsque votre seule méthode pour assembler de nombreux fichiers dans un seul programme est l'inclusion textuelle , vous fixez une limite à la taille raisonnable de vos programmes.
À titre de comparaison, ce serait comme si le langage de programmation C n'avait pas de lieur, seulement des
#include
instructions. Un tel dialecte C-lite n'aurait pas besoin de mots clés tels queextern
oustatic
. Ces fonctionnalités existent pour permettre la modularité.POSIX ne définit pas un moyen d'étendre les variables à une seule fonction de script shell, encore moins à un fichier.
Cela rend effectivement toutes les variables globales , ce qui nuit encore à la modularité et à la composabilité.
Il existe des solutions à cela dans les shells post-POSIX - certainement dans
bash
,ksh93
etzsh
au moins - mais cela vous ramène au point 1 ci-dessus.Vous pouvez voir l'effet de cela dans les guides de style sur l'écriture de macro GNU Autoconf, où ils vous recommandent de préfixer les noms de variable avec le nom de la macro elle-même, conduisant à des noms de variable très longs uniquement afin de réduire le risque de collision à un niveau acceptable proche zéro.
Même C est meilleur sur ce point, d'un mile. Non seulement la plupart des programmes C sont écrits principalement avec des variables fonctionnelles locales, mais C prend également en charge la portée des blocs, permettant à plusieurs blocs au sein d'une même fonction de réutiliser les noms de variables sans contamination croisée.
Les langages de programmation Shell n'ont pas de bibliothèque standard.
Il est possible d'argumenter que la bibliothèque standard d'un langage de script shell est le contenu de
PATH
, mais cela dit simplement que pour faire quoi que ce soit de conséquence, un script shell doit appeler un autre programme entier, probablement un écrit dans un langage plus puissant pour commencer avec.Il n'y a pas non plus d'archive largement utilisée de bibliothèques d'utilitaires shell comme avec le CPAN de Perl . Sans une grande bibliothèque disponible de code utilitaire tiers, un programmeur doit écrire plus de code à la main, ce qui le rend moins productif.
Même en ignorant le fait que la plupart des scripts shell s'appuient sur des programmes externes généralement écrits en C pour faire quoi que ce soit d'utile, il y a les frais généraux de toutes ces
pipe()
→fork()
→exec()
chaînes d'appel. Ce modèle est assez efficace sur Unix, par rapport à IPC et au lancement de processus sur d'autres systèmes d'exploitation, mais ici, il remplace efficacement ce que vous feriez avec un appel de sous-programme dans un autre langage de script, ce qui est encore plus efficace. Cela limite sérieusement la limite supérieure de la vitesse d'exécution des scripts shell.Les scripts shell ont peu de capacité intégrée d'augmenter leurs performances via une exécution parallèle.
Shells Bourne ont
&
,wait
et pipelines pour cela, mais c'est en grande partie seulement utile pour la composition de plusieurs programmes, et non pour la réalisation de CPU ou le parallélisme E / S. Il est peu probable que vous puissiez arrimer les cœurs ou saturer une matrice RAID uniquement avec des scripts shell, et si vous le faites, vous pourriez probablement obtenir des performances beaucoup plus élevées dans d'autres langues.Les pipelines en particulier sont de faibles moyens d'augmenter les performances via une exécution parallèle. Il permet uniquement à deux programmes de s'exécuter en parallèle, et l'un des deux sera probablement bloqué sur les E / S vers ou depuis l'autre à un moment donné.
Il existe des moyens modernes de contourner cela, tels que
xargs -P
et GNUparallel
, mais cela revient au point 4 ci-dessus.Avec effectivement aucune capacité intégrée de tirer pleinement parti des systèmes multiprocesseurs, les scripts shell seront toujours plus lents qu'un programme bien écrit dans un langage qui peut utiliser tous les processeurs du système. Pour reprendre cet
configure
exemple de script GNU Autoconf , doubler le nombre de cœurs dans le système n'améliorera pas la vitesse à laquelle il s'exécute.Les langages de script shell n'ont ni pointeurs ni références .
Cela vous empêche de faire un tas de choses faciles à faire dans d'autres langages de programmation.
D'une part, l'incapacité de se référer indirectement à une autre structure de données dans la mémoire du programme signifie que vous êtes limité aux structures de données intégrées . Votre shell peut avoir des tableaux associatifs , mais comment sont-ils implémentés? Il existe plusieurs possibilités, chacune avec des compromis différents: les arbres rouge-noir , les arbres AVL et les tables de hachage sont les plus courants, mais il y en a d'autres. Si vous avez besoin d'un autre ensemble de compromis, vous êtes bloqué, car sans références, vous n'avez pas le moyen de faire rouler à la main de nombreux types de structures de données avancées. Vous êtes coincé avec ce qu'on vous a donné.
Ou, il se peut que vous ayez besoin d'une structure de données qui n'a même pas d'alternative adéquate intégrée à votre interpréteur de script shell, comme un graphe acyclique dirigé , dont vous pourriez avoir besoin pour modéliser un graphe de dépendances . Je programme depuis des décennies, et la seule façon de penser à cela dans un script shell serait d'abuser du système de fichiers , en utilisant des liens symboliques comme de fausses références. C'est le genre de solution que vous obtenez lorsque vous vous fiez uniquement à l'intégralité de Turing, qui ne vous dit pas si la solution est élégante, rapide ou facile à comprendre.
Les structures de données avancées ne sont qu'une utilisation pour les pointeurs et les références. Il y a des tas d'autres applications pour eux , ce qui ne peut tout simplement pas être fait facilement dans un langage de script shell de la famille Bourne.
Je pourrais continuer encore et encore, mais je pense que vous obtenez le point ici. Autrement dit, il existe de nombreux langages de programmation plus puissants pour les systèmes de type Unix.
Bien sûr, et c'est précisément pourquoi GNU Autoconf utilise un sous-ensemble restreint à dessein de la famille Bourne de langages de script shell pour ses
configure
sorties de script: afin que sesconfigure
scripts s'exécutent à peu près partout.Vous ne trouverez probablement pas un plus grand groupe de croyants dans l'utilité d'écrire dans un dialecte shell Bourne hautement portable que les développeurs de GNU Autoconf, mais leur propre création est écrite principalement en Perl, plus certains
m4
, et seulement un peu de shell scénario; seule la sortie d'Autoconf est un script shell Bourne pur. Si cela ne pose pas la question de l'utilité du concept "Bourne partout", je ne sais pas ce qui le fera.Techniquement parlant, non, comme le suggère votre observation d'exhaustivité de Turing.
Mais ce n'est pas la même chose que de dire que les scripts shell de taille arbitraire sont agréables à écrire, faciles à déboguer ou rapides à exécuter.
"Pure" Bash, sans aucun appel aux choses dans le
PATH
? Le compresseur est probablement réalisable à l'aide deecho
séquences d'échappement hexadécimales, mais ce serait assez pénible à faire. Le décompresseur peut être impossible à écrire de cette façon en raison de l' impossibilité de gérer les données binaires dans le shell . Vous finiriez par appelerod
et ainsi de suite pour traduire des données binaires au format texte, la façon native du shell de gérer les données.Une fois que vous commencez à parler de l'utilisation du script shell comme il était prévu, comme colle pour piloter d'autres programmes dans le
PATH
, les portes s'ouvrent, car maintenant vous êtes limité à ce qui peut être fait dans d'autres langages de programmation, c'est-à-dire que vous n'ont pas de limites du tout. Un script shell qui obtient tout de son pouvoir en appelant à d' autres programmes dans lePATH
ne fonctionne pas aussi vite que les programmes monolithiques écrits dans des langages plus puissants, mais il ne fonctionne.Et c'est le point. Si vous avez besoin d'un programme pour fonctionner rapidement, ou s'il doit être puissant en soi plutôt que d'emprunter du pouvoir à d'autres, vous ne l'écrivez pas dans le shell.
Voici Tetris en coquille . D'autres jeux de ce type sont disponibles si vous cherchez.
Je mettrais le support de l'outil de débogage à la 20e place environ de la liste des fonctionnalités nécessaires pour prendre en charge la programmation à grande échelle. Beaucoup de programmeurs dépendent beaucoup plus du
printf()
débogage que des débogueurs appropriés, quelle que soit la langue.En shell, vous avez
echo
etset -x
qui, ensemble, sont suffisants pour déboguer un grand nombre de problèmes.la source
&
vous pouvez exécuter des processus en parallèle. Vous pouvezwait
pour les processus enfants se terminer. Vous pouvez configurer des pipelines et des réseaux de tuyaux plus complexes à l'aide de tuyaux nommés. Plus important encore, il est simple de faire le traitement parallèle de la bonne façon, avec très peu de code passe-partout et en évitant les risques et les difficultés du multi-threading en mémoire partagée.Nous pouvons marcher ou nager n'importe où, alors pourquoi nous embêter avec des vélos, des voitures, des trains, des bateaux, des avions et d'autres véhicules? Bien sûr, marcher ou nager peut être fatigant, mais il y a un énorme avantage à ne pas avoir besoin d'équipement supplémentaire.
D'une part, bien que bash soit complet de Turing, il n'est pas bon pour manipuler des données autres que des entiers (pas trop grands), des chaînes, des tableaux (unidimensionnels) de chaînes et des cartes finies de chaînes en chaînes. Tout autre type de données nécessite un codage gênant, ce qui rend difficile l'écriture du programme et imposerait souvent des performances qui ne sont pas assez bonnes dans la pratique. Par exemple, les opérations en virgule flottante dans bash sont dures et lentes.
De plus, bash a très peu de façons d'interagir avec son environnement. Il peut exécuter des processus, il peut effectuer certains types d'accès aux fichiers (par redirection), et c'est à peu près tout. Bash dispose également d'un client de mise en réseau côté client. Bash peut émettre assez facilement des octets nuls (
printf \\0
) mais pas analyser des octets nuls dans son entrée, ce qui le rend mal adapté à la lecture de données binaires. Bash ne peut pas faire directement d'autres choses: il doit appeler des programmes externes pour cela. Et c'est ok: les shells sont conçus dans le but principal d'exécuter des programmes externes! Les coques sont le langage de la colle pour combiner des programmes. Mais si vous exécutez un programme externe, cela signifie que le programme doit être disponible - et vous réduisez ensuite l'avantage de portabilité:).Bash n'a aucune sorte de fonctionnalité qui facilite l'écriture de programmes robustes, à part
set -e
. Il n'a pas de types (utiles), d'espaces de noms, de modules ou de structures de données imbriquées. Les bogues sont la principale difficulté de programmation; Bien que la facilité d'écriture de programmes sans bogue ne soit pas toujours le facteur décisif dans le choix d'une langue, Bash se classe mal sur ce plan. Bash se classe également mal sur les performances lorsqu'il fait autre chose que de combiner des programmes.Pendant longtemps, bash ne s'est pas exécuté sur Windows, et même aujourd'hui, il n'est pas présent dans une installation Windows par défaut, et il ne s'exécute pas entièrement en natif (même en WSL) dans le sens où il n'a pas d'interface avec Fonctionnalités natives de Windows. Bash ne fonctionne pas sur iOS et n'est pas installé par défaut sur Android. Donc, à moins que vous n'écriviez une application Unix uniquement, bash n'est pas du tout portable.
Exiger un compilateur n'est pas un problème de portabilité. Le compilateur s'exécute sur la machine des développeurs. Exiger un interprète ou des bibliothèques tierces peut être un problème, mais sous Linux, c'est un problème résolu grâce aux packages de distribution, et sous Windows, Android et iOS, les gens regroupent généralement des composants tiers dans leur package d'application. Par conséquent, le type de problèmes de portabilité que vous avez en tête ne sont pas des problèmes pratiques pour les applications courantes.
Ma réponse s'applique aux obus autres que bash. Quelques détails varient d'un shell à l'autre mais l'idée générale est la même.
la source
Quelques raisons de ne pas utiliser de scripts shell pour les gros programmes, juste au-dessus de ma tête:
mkdir
ou engrep
interne.zsh
a un certain support. Cela est également dû au fait que l'interface des programmes externes est principalement basée sur du texte et\0
est utilisée comme séparateur.bash -c ...
oussh -c ...
)la source