Est-il courant de diviser un script plus volumineux en plusieurs scripts et de les source dans le script principal?

23

En ce moment, je développe un script Bash plus grand (c'est un projet Open Source à moi) et ça commence à devenir un gâchis. J'ai divisé la logique en fonctions, j'utilise des variables locales où je peux et n'ai déclaré qu'une poignée de variables globales. Pourtant, il devient assez difficile à maintenir.

J'ai pensé à diviser le script en plusieurs scripts et à les source dans mon script principal (similaire aux importations dans d'autres langues).

Mais je me demande si c'est une approche réalisable. Premièrement, l'approvisionnement de plusieurs scripts pourrait ralentir considérablement le temps d'exécution du script, et deuxièmement, cela rend la distribution plus difficile.

Alors, est-ce une bonne approche, et d'autres projets (Open Source) le font-ils de la même manière?

méthode d'aide
la source
4
Je suis allé chercher un script shell vraiment long et à 3500 lignes et 125 Ko, je ne voudrais pas essayer de le maintenir. Le shell est vraiment convivial lors du raccordement de programmes, mais lorsqu'il essaie de faire du calcul, il devient assez moche. Je sais que le code que vous avez principalement fonctionne et que les coûts de portage sont élevés, mais vous voudrez peut-être envisager autre chose à l'avenir.
msw
1
J'ai rencontré le même problème. J'ai un projet bash modérément important sourceforge.net/projects/duplexpr . Actuellement, chaque script est autonome, mais je pense à déplacer toutes les fonctions communes vers un ou plusieurs fichiers distincts et à les inclure si nécessaire pour éviter d'avoir à les mettre à jour à plusieurs endroits. En général, je pense qu'il serait préférable d'appeler simplement chaque script successif plutôt que de le source. Il rend possible le fonctionnement indépendant des pièces. Ensuite, vous devez passer des variables sous forme d'arguments ou dans des fichiers de paramètres (ou vous pouvez simplement les exporter si vous ne voulez pas être autonomes.)
Joe

Réponses:

13

Oui, c'est une pratique courante. Par exemple, dans les premiers jours d'Unix le code shell chargé de guider le système à travers ses phases de démarrage à un fonctionnement multi - utilisateur était un seul fichier, /etc/rc. Aujourd'hui, le processus de démarrage est contrôlé par de nombreux scripts shell, répartis par fonction, avec des fonctions et des variables communes provenant au besoin d'emplacements centraux. Les distributions Linux, Mac, BSD, ont toutes adopté cette approche à des degrés divers.

Kyle Jones
la source
2
mais dans ce cas, c'est plus pour la réutilisabilité. Différents scripts utilisent une bibliothèque commune de fonctions shell.
Stéphane Chazelas
16

Shell est-il le bon outil pour le travail à ce stade? En tant que développeur qui est tombé sur des problèmes de dépassement de code, je peux vous dire qu'une réécriture ne devrait pas être envisagée mais plutôt envisager de séparer les morceaux en quelque chose de mieux adapté à l'échelle que vous cherchez à développer votre application - peut-être python ou ruby ​​ou même perl ?

Shell est un langage utilitaire - c'est un langage de script - et il sera donc difficile de le développer à ces tailles.

JasonG
la source
C'est exactement ce que j'allais publier.
zwol
d'autant plus que même les systèmes d'exploitation plus anciens comme Solaris sont désormais livrés avec perl, python, etc. l'une des raisons pour lesquelles les systèmes plus anciens utilisaient des scripts shell était que le shell était garanti d'être toujours disponible, mais les Unices plus grands, comme HP-UX et Solaris et AIX, ne pouvaient pas être garantis pour inclure d'autres outils.
Tim Kennedy
7

Si cela facilite votre entretien, vous pouvez avoir les deux. Divisez-le en parties logiques afin de pouvoir le maintenir facilement, puis écrivez (par exemple,) un Makefile pour tout remettre ensemble pour distribution Vous pouvez écrire quelques scripts rapides pour copier les fonctions du fichier include dans le fichier de sortie à la place de la sourceligne ou tout simplement faire quelque chose de trivial comme ça (vous devrez re-tabifier cela, comme le makenécessitent les onglets):

all: myscript

myscript: includes/* body/*
    cat $^ > "$@" || (rm -f "$@"; exit 1)

Vous avez alors une version "source" (utilisée pour l'édition) et une version "binaire" (utilisée pour une installation triviale).

derobert
la source
1
Ah! Quelqu'un d'autre qui utilise l'approche du chat simple :)
Clayton Stanley
4

Un script peut être divisé comme vous le décrivez - à peu près tout peut être fait. Je dirais que la «bonne approche» serait de compartimenter votre gros script, de déterminer où certaines parties de celui-ci pourraient être exécutées en tant que processus séparé, en communiquant via des mécanismes IPC.

Au-delà de cela, pour un script shell, je le conditionnerais en un seul fichier. Comme vous le dites, cela rend la distribution plus difficile: vous devez soit savoir où se trouvent les scripts de la «bibliothèque» - il n'y a pas de norme agréable pour les scripts shell - soit compter sur l'utilisateur pour définir correctement leur chemin.

Vous pouvez distribuer un programme d'installation qui gère tout cela pour vous, extraire les fichiers, les mettre au bon endroit, dire à l'utilisateur d'ajouter quelque chose comme export PROGRAMDIR=$HOME/lib/PROGRAMau fichier ~ / .bashrc. Ensuite, le programme principal pourrait échouer s'il $PROGRAMDIRn'est pas défini ou ne contient pas les fichiers que vous attendez.

Je ne m'inquiéterais pas autant de la surcharge de chargement des autres scripts. La surcharge est vraiment juste l'ouverture d'un fichier; le traitement du texte est le même, surtout s'il s'agit de définitions de fonctions.

Arcege
la source
1

Pratique courante ou non, je ne pense pas que se procurer autre chose qu'un ensemble d'exportations soit une bonne idée. Je trouve que l'exécution de code en l'approvisionnant est simplement déroutante et limite la réutilisation, car les paramètres environnementaux et autres paramètres variables rendent le code source très dépendant du code d'approvisionnement.

Il est préférable de diviser votre application en scripts plus petits et autonomes, puis de les exécuter sous la forme d'une série de commandes. Cela facilitera le débogage, car vous pouvez exécuter chaque script autonome dans un shell interactif, en examinant les fichiers, les journaux, etc. entre chaque appel de commande. Votre script d'application unique et volumineux se transforme en un script de contrôle plus simple qui exécute simplement une série de commandes après avoir terminé le débogage.

Un groupe de scripts autonomes plus petits rencontre le problème le plus difficile à installer.

Bruce Ediger
la source
1

Une alternative au sourcing des scripts consiste simplement à les appeler avec des arguments. Si vous avez déjà divisé la plupart de vos fonctionnalités en fonctions shell, vous êtes probablement tout près de pouvoir le faire déjà. L'extrait de Bash suivant permet à n'importe quelle fonction déclarée dans un script d'être utilisée comme sous-commande:

if [[ ${1:-} ]] && declare -F | cut -d' ' -f3 | fgrep -qx -- "${1:-}"
then "$@"
else main "$@" # Try the main function if args don't match a declaration.
fi

La raison de ne pas le faire sourceest d'éviter la pollution de l'environnement et des options.

solidsnack
la source
0

Voici mon exemple de la façon dont un grand script bash peut être divisé en plusieurs fichiers, puis intégré dans un script résultant: https://github.com/zinovyev/bash-project

J'utilise un Makefileà cet effet:

TARGET_FILE = "target.sh"
PRJ_SRC = "${PWD}/src/main.sh"
PRJ_LIB = $(shell ls -d ${PWD}/lib/*) # All files from ./lib

export PRJ_LIB

SHELL := /bin/env bash
all: define_main add_dependencies invoke_main

define_main:
    echo -e "#!/usr/bin/env bash\n" > ${TARGET_FILE}
    echo -e "function main() {\n" >> ${TARGET_FILE}
    cat "${PRJ_SRC}" | sed -e 's/^/  /g' >> ${TARGET_FILE}
    echo -e "\n}\n" >> ${TARGET_FILE}

invoke_main:
    echo "main \$$@" >> ${TARGET_FILE}

add_dependencies:
    for filename in $${PRJ_LIB[*]}; do cat $${filename} >> ${TARGET_FILE}; echo >> ${TARGET_FILE}; done
zinovyev
la source