Dans quelle mesure un programme peut-il être écrit en pur Bash? [fermé]

17

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?

Bregalad
la source
2
Le shscript configurequi est utilisé dans le cadre du processus de construction d'un grand nombre de packages un * x n'est pas «relativement simple».
user4556274
@ user4556274 Ce n'est pas le cas, mais ce n'est généralement pas écrit à la main non plus, mais à partir d'un ensemble complet de m4macros.
Kusalananda
2
Il y a un assembleur x86 dans Bash, donc oui, Bash est parfois utilisé pour écrire des programmes complexes. Pourquoi les gens ne font-ils pas cela plus souvent? Peut-être parce que l'interprète est également lent, merdique et sujet à des bugs "intéressants" (voir fi Shellshock ). De plus, les scripts Bash ont tendance à être exponentiellement plus difficiles à maintenir avec la taille. Regardez l'assembleur ci-dessus; pouvez-vous dire à la source si elle suit la syntaxe AT&T ou Intel?
Satō Katsura
configureles 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 ...
ilkkachu
Alors, cette question peut-elle répondre ou non? Ils l'ont mis en attente et disent que c'est sans réponse, mais pourtant j'obtiens quelques bonnes réponses. Ce serait bien d'être cohérent, car je suis nouveau dans cette SE, afin de m'orienter vers le type de questions qui sont et ne sont pas souhaitables sur cette SE.
Bregalad

Réponses:

30

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 configurescripts générés par GNU Autoconf - sont atypiques pour de nombreuses raisons:

  1. 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 exprou bcfaire 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, zshet ksh93tous 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.

  2. 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 sourcedans 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 #includeinstructions. Un tel dialecte C-lite n'aurait pas besoin de mots clés tels que externou static. Ces fonctionnalités existent pour permettre la modularité.

  3. 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, ksh93et zshau 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.

  4. 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.

  5. Les scripts shell ont peu de capacité intégrée d'augmenter leurs performances via une exécution parallèle.

    Shells Bourne ont &, waitet 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 -Pet 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 configureexemple 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.

  6. 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 configuresorties de script: afin que ses configurescripts 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 echosé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 odet 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 PATHne 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 echoet set -xqui, ensemble, sont suffisants pour déboguer un grand nombre de problèmes.

Warren Young
la source
2
"Les scripts shell ont peu de capacité intégrée pour exécuter en parallèle." À mon avis, le shell prend mieux en charge le traitement parallèle que la plupart des autres langues. Avec un seul caractère, &vous pouvez exécuter des processus en parallèle. Vous pouvez waitpour 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.
Sam Watkins
@SamWatkins: J'ai mis à jour le point 5 ci-dessus pour répondre à votre réponse. Bien que moi aussi, je suis un fan de la transmission de messages entre des processus distincts afin d'éviter de nombreux problèmes inhérents au parallélisme de la mémoire partagée, le point que je faisais ici était d'augmenter les performances, pas la composabilité et autres, et que nécessite souvent un parallélisme à mémoire partagée.
Warren Young
Les scripts shell sont bons pour le prototypage - mais finalement un projet devrait passer à un langage de programmation approprié, puis idéalement à un langage compilé. Ensuite, dans les cas extrêmes, l'assemblage, comme vous le verriez avec le projet FFmpeg. Cmake est un bon exemple de ce qui devrait arriver aux Autotools - il est écrit en C et ne nécessite pas Perl ou Texinfo ou M4. Son genre d'embarras vraiment que les Autotools s'appuient toujours si fortement sur les scripts shell après 30 ans wikipedia.org/wiki/GNU_Build_System#Criticism
Steven Penny
9

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.

Gilles 'SO- arrête d'être méchant'
la source
1
Je crois que le mythe de la portabilité a été évoqué assez fréquemment, je ne suis pas sûr d'utiliser cet élément particulier comme négatif car il s'applique également à la plupart des autres langages, y compris Java. Même PHP fonctionnant sur un serveur Windows vs un serveur * nix a quelques petites différences dont vous devez toujours être conscient, si vous êtes assez stupide pour exécuter quoi que ce soit sur un serveur Windows, c'est-à-dire. Beaucoup de choses ne fonctionnent pas sur Android ou iOs, donc vous ne savez pas non plus comment cela pourrait être un commentaire valide.
Lizardx
7

Quelques raisons de ne pas utiliser de scripts shell pour les gros programmes, juste au-dessus de ma tête:

  • La plupart des fonctions sont effectuées en supprimant les commandes externes, ce qui est lent. En revanche, les langages de programmation comme Perl peuvent faire l'équivalent de mkdirou en grepinterne.
  • Il n'y a pas de moyen facile d'accéder aux bibliothèques C ou de faire des appels système directs, ce qui signifie par exemple que le jeu vidéo serait difficile à créer
  • Les langages de programmation appropriés prennent mieux en charge les structures de données complexes. Bien que Bash ait des tableaux et des tableaux associatifs, mais je ne voudrais pas penser à une liste chaînée ou à un arbre.
  • Le shell est fait pour traiter les commandes qui sont faites si du texte. Les données binaires (c'est-à-dire les variables contenant des octets NUL (octets avec une valeur zéro)) sont difficiles à impossible à gérer. Dépend un peu du shell, zsha un certain support. Cela est également dû au fait que l'interface des programmes externes est principalement basée sur du texte et \0est utilisée comme séparateur.
  • De plus, à cause des commandes externes, la séparation entre le code et les données est légèrement difficile. Soyez témoin de tous les problèmes rencontrés lors de la soumission de données à un autre shell (c.-à-d. Lors de l'exécution de bash -c ...ou ssh -c ...)
ilkkachu
la source
C'est l'ensemble de négatifs le plus précis pour moi, en tant que personne qui fait de nombreux scripts bash, ce serait à peu près ce que je citerais également comme négatifs. Cependant, une chose que j'ai trouvée est que Bash n'est en fait pas beaucoup plus lent que les autres langages compilés lors de la comparaison de fonctionnalités similaires. Je soupçonne furtivement que si j'essayais d'écrire certaines des choses les plus compliquées que j'ai en bash en python, la différence de vitesse ne rendrait pas le travail monstrueux impliqué en vaut la peine. Cependant, Bash seul, je l'ai trouvé trop limité, mais Bash + gawk fonctionne bien, gawk est presque réel.
Lizardx