il semble que Bash soit un langage complet de Turing
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.
pourquoi Bash est-il utilisé presque exclusivement pour écrire des scripts relativement simples?
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
ou bc
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
et ksh93
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 - alias source
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 que extern
ou static
. 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
et zsh
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.
C'est un énorme avantage, qui pourrait compenser la médiocrité de la langue elle-même dans certains cas.
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 ses configure
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.
Alors, y a-t-il une limite à la complexité de tels programmes?
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.
Est-il possible d'écrire, disons, un compresseur / décompresseur de fichiers en pure bash?
"Pure" Bash, sans aucun appel aux choses dans le PATH
? Le compresseur est probablement réalisable à l'aide de echo
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 appeler od
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 le PATH
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.
Un jeu vidéo simple?
Voici Tetris en coquille . D'autres jeux de ce type sont disponibles si vous cherchez.
il n'y a que des outils de débogage très limités
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
et set -x
qui, ensemble, sont suffisants pour déboguer un grand nombre de problèmes.
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».