Trop de lignes de shebang (déclaration de script) - un moyen de réduire leur montant?

12

J'ai un projet composé d'environ 20 petits .shfichiers. Je les nomme «petits» car généralement, aucun fichier ne contient plus de 20 lignes de code. J'ai adopté une approche modulaire car je suis donc fidèle à la philosophie Unix et il est plus facile pour moi de maintenir le projet.

Au début de chaque .shfichier, je mets #!/bin/bash.

Autrement dit, je comprends que les déclarations de script ont deux objectifs:

  1. Ils aident l'utilisateur à se rappeler quel shell est nécessaire pour exécuter le fichier (par exemple, après quelques années sans utiliser le fichier).
  2. Ils s'assurent que le script ne s'exécute qu'avec un certain shell (Bash dans ce cas) pour éviter un comportement inattendu au cas où un autre shell serait utilisé.

Quand un projet commence à croître, disons de 5 fichiers à 20 fichiers ou de 20 fichiers à 50 fichiers (pas dans ce cas mais juste pour le démontrer), nous avons 20 lignes ou 50 lignes de déclarations de script. J'avoue, même si cela peut être drôle pour certains, cela me semble un peu redondant d'utiliser 20 ou 50 à la place, disons seulement 1 par projet (peut-être dans le fichier principal du projet).

Existe-t-il un moyen d'éviter cette redondance alléguée de 20 ou 50 ou un nombre beaucoup plus élevé de lignes de déclarations de script en utilisant une déclaration de script "globale", dans un fichier principal?

user9303970
la source
2
Vous ne devriez pas mais pourriez; supprimez-le et exécutez tous vos scripts avec/bin/bash -x "$script"
jesse_b
... Habituellement, au moment où quelque chose arrive à ce point, j'ai déjà conclu qu'il était temps d'arrêter d'utiliser des scripts shell et d'obtenir un véritable langage de script pour faire le travail.
Shadur

Réponses:

19

Même si votre projet peut désormais être composé uniquement de 50 scripts Bash, il commencera tôt ou tard à accumuler des scripts écrits dans d'autres langages tels que Perl ou Python (pour les avantages que ces langages de script ont que Bash n'a pas).

Sans une #!ligne appropriée dans chaque script, il serait extrêmement difficile d'utiliser les différents scripts sans savoir également quel interprète utiliser. Peu importe que chaque script soit exécuté à partir d'autres scripts , cela ne fait que déplacer la difficulté des utilisateurs finaux aux développeurs. Aucun de ces deux groupes de personnes ne devrait avoir besoin de savoir dans quelle langue un script a été écrit pour pouvoir l'utiliser.

Les scripts shell exécutés sans #!ligne et sans interprète explicite sont exécutés de différentes manières selon le shell qui les appelle (voir par exemple la question Quel interpréteur shell exécute un script sans shebang? Et surtout la réponse de Stéphane ), ce qui n'est pas ce que vous voulez dans un environnement de production (vous voulez un comportement cohérent, et peut-être même une portabilité).

Les scripts exécutés avec un interpréteur explicite seront exécutés par cet interprète indépendamment de ce que #!dit la ligne. Cela entraînera des problèmes plus loin si vous décidez de réimplémenter, par exemple, un script Bash en Python ou dans tout autre langage.

Vous devez dépenser ces frappes supplémentaires et toujours ajouter une #!ligne à chaque script.


Dans certains environnements, il existe des textes juridiques passe-partout multi-paragraphes dans chaque script de chaque projet. Soyez très heureux que ce soit seulement une #!ligne qui semble "redondante" dans votre projet.

Kusalananda
la source
La réponse devrait être développée par ceci: Le shell détermine l'interpréteur par la #!ligne dès que le fichier a une autorisation exécutable et n'est pas chargé par un interprète, mais par le shell. Autrement dit, /path/to/myprog.plexécute un programme perl si la #!ligne est présente (pointant vers l'interpréteur perl) et que le fichier a la permission exec.
rexkogitans
11

Vous comprenez mal le point de #!. Il s'agit en fait d'une directive adressée au système d'exploitation pour exécuter ce programme sous l'interpréteur défini.

Ainsi, un script pourrait être #!/usr/bin/perlet le programme résultant peut être exécuté en tant que ./myprogramet l'interpréteur perl pourrait être utilisé. De même #!/usr/bin/python, un programme s'exécuterait sous python.

Ainsi, la ligne #!/bin/bashindique au système d'exploitation d'exécuter ce programme sous bash.

Voici un exemple:

$ 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

Donc, bien que mon shell soit ksh, le programme "x" s'exécute sous bash à cause de la #!ligne, et nous pouvons le voir explicitement dans la liste des processus.

Stephen Harris
la source
ps -auxpeut donner un avertissement,
Weijun Zhou
@WeijunZhou Dites-en plus ...
Kusalananda
Une version de psdonne l'avertissement Warning: bad syntax, perhaps a bogus '-'?.
Weijun Zhou
2
psprend en charge la syntaxe des arguments BSD et UXIX . -auxest faux UNIX Stephen signifie probablement ce auxqui est correct BSD
Jasen
1
Les gens, 2 choses; (1) se concentrer sur le concept d'exemple (qui psmontre l'appel de bash) et non la syntaxe explicite; (2) ps -auxfonctionne parfaitement sur CentOS 7 et Debian 9 sans avertissements; ce cut'n'paste était exactement la sortie de ma machine; toute la discussion sur la -nécessité ou non s'applique aux anciennes versions de Linux procps, pas aux versions actuelles.
Stephen Harris
9

Sur .sh

Ce qui est mauvais, c'est .shà la fin du nom de fichier.

Imaginez que vous réécrivez l'un des scripts en python.

Eh bien maintenant, vous devez changer la première ligne pour que #!/usr/bin/python3ce ne soit pas si mal, car vous avez également dû changer toutes les autres lignes de code dans le fichier. Cependant, vous devez également changer le nom du fichier de prog.shà prog.py. Et puis vous devez trouver partout dans les autres scripts, et tous vos scripts utilisateur (ceux dont vous ne saviez même pas qu'ils existaient), et les changer pour utiliser le nouveau script. sed -e 's/.sh/.py/g'peut aider ceux que vous connaissez. Mais maintenant, votre outil de contrôle des révisions montre un changement dans de nombreux fichiers non liés.

Vous pouvez également le faire à la manière Unix et progne pas nommer le programme prog.sh.

Sur #!

Si vous avez le bon #!, et définissez l'autorisation / mode pour inclure exécuter. Ensuite, vous n'avez pas besoin de connaître l'interprète. L'ordinateur le fera pour vous.

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

Pour les systèmes non GNU, lisez le manuel de chmod(et apprenez l'octal), peut-être le lirez quand même.

ctrl-alt-delor
la source
1
Hmmm, les extensions ne sont pas nécessairement mauvaises, au moins elles aident à communiquer avec d'autres utilisateurs quel est le type de fichier.
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy Mais vous n'avez pas besoin de connaître la langue, il vous suffit de l'exécuter. Même Microsoft essaie de s'éloigner des extensions (malheureusement, je le fais en les cachant). Cependant je suis d'accord qu'ils pourraient être une bonne idée: .imagepour les photos. .audiopour le son, etc. Vous dites ainsi de quoi il s'agit mais pas de l'implémentation / de l'encodage.
ctrl-alt-delor
1
@ ctrl-alt-delor Si le fichier est appelé launch-missilesou que delete-project-and-make-manager-pissedje ne veux pas "simplement l'exécuter" alors que j'étais seulement curieux de connaître la langue :)
Sergiy Kolodyazhnyy
2
si vous êtes curieux de la langue, utilisez la filecommande. il reconnaît la plupart des types de fichiers.
Jasen
2
Je suis d'accord que les extensions sont mauvaises pour les scripts exécutables, pour exactement les raisons données.J'utilise une extension .sh pour un script qui doit provenir d'un autre script shell (c'est-à-dire un script non exécutable) - dans ce scénario, le suffixe est important / valide car il nous indique comment utiliser le fichier.
Laurence Renshaw
8

[Les lignes de hachage] garantissent que le script ne s'exécute qu'avec un certain shell (Bash dans ce cas) pour éviter un comportement inattendu au cas où un autre shell serait utilisé.

Il vaut probablement la peine de souligner que c'est tout à fait faux. La ligne de hachage ne vous empêche en aucun cas d'essayer d'alimenter le fichier vers un interpréteur / interpréteur de commandes incorrect:

$ 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 similaire avec awk -f array.shetc.)


Mais dans tous les cas, une autre approche de la modularité serait de définir des fonctions shell dans les fichiers, une fonction par fichier, si vous voulez, puis sourceles fichiers du programme principal. De cette façon, vous n'auriez pas besoin de hashbangs: ils seraient traités comme tous les autres commentaires, et tout fonctionnerait en utilisant le même shell. L'inconvénient serait que vous auriez besoin sourcedes fichiers avec les fonctions (par exemple for x in functions/*.src ; do source "$x" ; done), et ils s'exécuteraient tous en utilisant le même shell, donc vous ne pourriez pas remplacer directement l'un d'eux par une implémentation Perl.

ilkkachu
la source
+1 par exemple avec les tableaux bash. Les utilisateurs qui ne connaissent pas plusieurs shells ou certaines des définitions de POSIX peuvent supposer que certaines fonctionnalités d'un shell existent dans un autre. Aussi pour les fonctions, cela peut être pertinent: unix.stackexchange.com/q/313256/85039
Sergiy Kolodyazhnyy
4

Existe-t-il un moyen d'éviter cette redondance alléguée de 20 ou 50 ou un nombre beaucoup plus élevé de lignes de déclarations de script en utilisant une déclaration de script "globale", dans un fichier principal?

Il y a - cela s'appelle la portabilité ou la conformité POSIX. Efforcez-vous de toujours écrire des scripts de manière portable et sans shell, source uniquement à partir d'un script qui utilise #!/bin/shou lorsque vous utilisez de manière /bin/shinteractive (ou au moins un shell compatible POSIX tel que ksh).

Cependant, les scripts portables ne vous épargnent pas:

  • besoin d'utiliser les fonctionnalités de l'un des obus ( la réponse d'ilkkachu en est un exemple)
  • besoin d'utiliser des langages de script plus avancés que les shells (Python et Perl)
  • Erreur PEBKAC: votre script tombe entre les mains de l'utilisateur J.Doe qui ne l'exécute pas comme vous le vouliez , par exemple, il exécute le /bin/shscript avec csh.

Si je peux exprimer mon opinion, «éviter la redondance» en éliminant #!est un mauvais objectif. L'objectif doit être des performances cohérentes et en fait un script de travail qui fonctionne et qui considère les cas d'angle, suit certaines règles générales comme ne pas analyser les ls ou toujours citer des variables, sauf si vous avez besoin de séparer les mots .

Ils aident l'utilisateur à se rappeler quel shell est nécessaire pour exécuter le fichier (par exemple, après quelques années sans utiliser le fichier).

Ils ne sont vraiment pas destinés à l'utilisateur - ils sont destinés au système d'exploitation pour exécuter l'interpréteur approprié. "Propre" dans ce cas, signifie que OS trouve ce qui se trouve dans shebang; si le code en dessous pour un mauvais shell - c'est la faute de l'auteur. En ce qui concerne la mémoire utilisateur, je ne pense pas que "l'utilisateur" de programmes tels que Microsoft Word ou Google Chrome ait jamais eu besoin de savoir dans quels programmes linguistiques sont écrits; les auteurs peuvent avoir besoin.

Là encore, les scripts écrits de manière portative peuvent éliminer le besoin de même se souvenir du shell que vous avez utilisé à l'origine, car les scripts portables devraient fonctionner dans n'importe quel shell compatible POSIX.

Ils s'assurent que le script ne s'exécute qu'avec un certain shell (Bash dans ce cas) pour éviter un comportement inattendu au cas où un autre shell serait utilisé.

Ils ne le font pas. Ce qui empêche réellement un comportement inattendu, c'est l'écriture de scripts portables.

Sergiy Kolodyazhnyy
la source
1

La raison pour laquelle vous devez mettre #!/bin/bashen haut de chaque script est que vous l'exécutez comme un processus distinct.

Lorsque vous exécutez un script en tant que nouveau processus, le système d'exploitation voit qu'il s'agit d'un fichier texte (par opposition à un exécutable binaire) et a besoin de savoir quel interpréteur exécuter. Si vous ne le dites pas, le système d'exploitation ne peut que deviner, en utilisant son paramètre par défaut (qu'un administrateur peut modifier). Ainsi, la #!ligne (plus l'ajout d'autorisations exécutables, bien sûr) transforme un simple fichier texte en un exécutable qui peut être exécuté directement, avec la certitude que le système d'exploitation sait quoi en faire.

Si vous souhaitez supprimer la #!ligne, vos options sont les suivantes:

  1. Exécutez le script directement (comme vous le faites maintenant) et espérez que l'interpréteur par défaut correspond au script - vous êtes probablement en sécurité sur la plupart des systèmes Linux, mais pas portable sur d'autres systèmes (par exemple Solaris), et il peut être changé en n'importe quoi (je pourrais même le configurer pour qu'il s'exécute vimsur le fichier!).

  2. Exécutez le script à l'aide de bash myscript.sh. Cela fonctionne bien, mais vous devez spécifier le chemin d'accès au script, et c'est compliqué.

  3. Sourcez le script en utilisant . myscript.sh, qui exécute le script dans le processus d'interpréteur en cours (c'est-à-dire pas en tant que sous-processus). Mais cela nécessitera presque certainement des modifications à vos scripts et les rendra moins modulaires / réutilisables.

Dans les cas 2 et 3, le fichier n'a pas besoin (et ne devrait pas avoir) d'autorisations d'exécution, et lui donner un .sh(ou .bash) suffixe est une bonne idée.

Mais aucune de ces options ne semble bonne pour votre projet, car vous avez dit que vous vouliez garder vos scripts modulaires (=> indépendants et autonomes), vous devriez donc garder votre #!/bin/bashligne en haut des scripts.

Dans votre question, vous avez également dit que vous suiviez la philosophie Unix. Si c'est vrai, alors vous devriez apprendre à quoi #!sert vraiment la ligne et continuer à l'utiliser dans chaque script exécutable!

Laurence Renshaw
la source
Merci pour la bonne réponse. Nous avons tout aimé dans la réponse et de la pensée de upvoting jusqu'à ce: then you should learn what the #! line is really for, and keep using it in every executable script!. On peut suivre la philosophie U. jusqu'à un certain point de connaissance. Après toutes ces réponses, j'ai compris pourquoi il pouvait être inclus dans ce terme. Je suggère de modifier la réponse, en la remplaçant par "Voir le shebang comme une partie interne de la philosophie Unix que vous respectez et adoptez".
user9303970
1
Désolé si j'étais direct, ou si ce commentaire vous paraissait grossier. Vos commentaires suggèrent que vous préférez travailler dans un IDE - avec un «projet» qui vous permet de définir des règles globales pour les scripts de ce projet. C'est une façon valable de développer, mais cela ne convient pas à traiter chaque script comme un programme indépendant et autonome (la philosophie Unix dont vous avez parlé).
Laurence Renshaw