Créer et charger des bibliothèques Bash réutilisables
Organisez les assistants partagés dans des fichiers de bibliothèque .sh pouvant être chargés, avec des gardes d’inclusion et des préfixes de fonctions à espace de noms.
Créer et charger des bibliothèques Bash réutilisables est une leçon DevOps Bootcamp gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage DevOps Bootcamp, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours DevOps Bootcamp comprend 4 leçons au total.
Qu'est-ce qu'une bibliothèque Bash ?
En génie logiciel, une bibliothèque est un ensemble de fonctions réutilisables que plusieurs programmes peuvent partager. Bash prend en charge ce même concept grâce aux scripts d'interpréteur chargeables.
Au lieu de copier-coller des fonctions utilitaires dans chaque script, placez-les dans un fichier .sh dédié et chargez-le avec la commande source (ou sa forme abrégée .). Toutes les fonctions, variables ou alias définis dans ce fichier deviennent disponibles dans la session actuelle de l'interpréteur du script appelant.
- Favorise le principe DRY (ne vous répétez pas)
- Centralise les corrections de bogues : corrigez une fois, tous les appelants en bénéficient
- Rend les scripts individuels plus courts et plus faciles à lire
- Permet une cohérence à l'échelle de l'équipe pour la journalisation, la gestion des erreurs et la logique utilitaire
Un projet Bash bien structuré possède généralement un répertoire lib/ contenant ces fichiers partagés, conformément aux conventions des langages de niveau supérieur.
La commande source et l'opérateur point
Il existe deux manières équivalentes de charger un fichier de bibliothèque dans l'environnement de l'interpréteur actuel :
source /path/to/lib.sh— la forme explicite et lisible. /path/to/lib.sh— la forme abrégée compatible avec POSIX
Les deux exécutent le fichier dans le processus actuel de l'interpréteur, et non dans un sous-interpréteur ; chaque fonction et variable qui y est définie devient donc immédiatement disponible dans l'environnement de votre script après l'appel.
Il est courant de localiser la bibliothèque par rapport au script appelant à l'aide de $BASH_SOURCE, ce qui rend le projet portable quel que soit son emplacement d'installation.
#!/usr/bin/env bash
# main.sh — load a library relative to this script's own location
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib/utils.sh"
echo "Library loaded. Calling greet..."
greet "World"Créer votre premier fichier de bibliothèque
Un fichier de bibliothèque est un simple fichier .sh qui contient uniquement des définitions de fonctions (et parfois des constantes). Il ne doit pas exécuter de code ayant des effets de bord au niveau supérieur : il est destiné à être chargé, et non exécuté directement.
Conventions principales :
- Commencez par un commentaire shebang décrivant le rôle de la bibliothèque
- Définissez uniquement des fonctions : aucune logique
mainau niveau supérieur - Utilisez
returndans les fonctions (jamaisexit, qui arrêterait l'appelant) - Conservez le fichier dans un sous-répertoire
lib/de votre projet
#!/usr/bin/env bash
# lib/utils.sh — General-purpose utility functions
# Print a greeting message
greet() {
local name="${1:-stranger}"
echo "Hello, ${name}!"
}
# Print a timestamped log line to stderr
log_info() {
echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') $*" >&2
}
# Print an error message and return a failure code
log_error() {
echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $*" >&2
return 1
}Gardes d'inclusion : éviter les chargements multiples
Lorsque plusieurs scripts chargent la même bibliothèque, ou lorsqu'une bibliothèque en charge une autre qui est également chargée par le script principal, les fonctions peuvent être définies plusieurs fois. Cela fait perdre du temps et peut provoquer des bogues subtils si le corps d'une fonction est remplacé en cours d'exécution.
La solution est une garde d'inclusion : une variable qui sert de drapeau. Lors du premier chargement, la variable n'est pas définie et le fichier poursuit son exécution. À chaque chargement suivant, la garde est déjà définie et le fichier retourne immédiatement.
C'est l'équivalent Bash de #pragma once en C/C++, ou des modèles if not already imported dans d'autres langages.
#!/usr/bin/env bash
# lib/utils.sh — with include guard
# Guard: if already sourced, do nothing
[[ -n "${_LIB_UTILS_LOADED:-}" ]] && return 0
_LIB_UTILS_LOADED=1
greet() {
local name="${1:-stranger}"
echo "Hello, ${name}!"
}
log_info() {
echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') $*" >&2
}
log_error() {
echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $*" >&2
return 1
}Préfixes de fonctions avec espace de noms
Bash possède un espace de noms global unique pour les fonctions. Si deux bibliothèques définissent chacune une fonction appelée log ou init, la seconde définition écrase silencieusement la première.
La protection standard consiste à utiliser un préfixe d'espace de noms : chaque fonction d'une bibliothèque est préfixée par le nom court de la bibliothèque, suivi de deux deux-points (::) ou d'un trait de soulignement. Par exemple, une bibliothèque d'utilitaires pour les chaînes utilise str::, et une bibliothèque de fichiers utilise file::.
- Les collisions deviennent extrêmement improbables
- Le code appelant s'explique de lui-même :
str::trimindique exactement où se trouve la fonction - La recherche avec Grep est améliorée :
grep 'str::' main.shaffiche instantanément tous les appels à la bibliothèque de chaînes
#!/usr/bin/env bash
# lib/str.sh — String utility library (namespaced)
[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
_LIB_STR_LOADED=1
# Trim leading and trailing whitespace
str::trim() {
local s="$1"
s="${s#"${s%%[![:space:]]*}"}"
s="${s%"${s##*[![:space:]]}"}"
echo "$s"
}
# Convert string to uppercase
str::upper() {
echo "${1^^}"
}
# Convert string to lowercase
str::lower() {
echo "${1,,}"
}
# Check if a string contains a substring
str::contains() {
[[ "$1" == *"$2"* ]]
}Organiser un répertoire lib/
À mesure qu'un projet grandit, un seul fichier utils.sh devient difficile à gérer. Répartissez les responsabilités dans des fichiers de bibliothèque spécialisés, à l'intérieur d'un répertoire lib/ :
lib/log.sh— utilitaires de journalisation (log::info,log::warn,log::error)lib/str.sh— manipulation des chaînes (str::trim,str::upper)lib/fs.sh— utilitaires du système de fichiers (fs::require_dir,fs::safe_rm)lib/net.sh— vérifications réseau (net::wait_for_port,net::is_online)
Un fichier unique de chargement d'initialisation (lib/bootstrap.sh) peut tous les charger dans le bon ordre, de sorte que chaque script n'ait besoin que d'un seul appel à source.
#!/usr/bin/env bash
# lib/bootstrap.sh — Load all project libraries in dependency order
[[ -n "${_LIB_BOOTSTRAP_LOADED:-}" ]] && return 0
_LIB_BOOTSTRAP_LOADED=1
_BOOTSTRAP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${_BOOTSTRAP_DIR}/log.sh"
source "${_BOOTSTRAP_DIR}/str.sh"
source "${_BOOTSTRAP_DIR}/fs.sh"
source "${_BOOTSTRAP_DIR}/net.sh"
log::info "All libraries loaded."Une bibliothèque complète de journalisation
La journalisation est la préoccupation commune la plus fréquente entre les scripts. Une lib/log.sh dédiée centralise le formatage de la sortie, les niveaux de journalisation et les codes de couleur.
Avec une telle bibliothèque, chaque script du projet affiche des messages cohérents, horodatés et colorés, sans dupliquer le moindre code de formatage.
#!/usr/bin/env bash
# lib/log.sh — Coloured, levelled logging library
[[ -n "${_LIB_LOG_LOADED:-}" ]] && return 0
_LIB_LOG_LOADED=1
# Colour codes (disabled when not writing to a terminal)
_LOG_RED=''; _LOG_YEL=''; _LOG_GRN=''; _LOG_RST=''
if [[ -t 2 ]]; then
_LOG_RED='\033[0;31m'
_LOG_YEL='\033[0;33m'
_LOG_GRN='\033[0;32m'
_LOG_RST='\033[0m'
fi
_log::_print() {
local level="$1" colour="$2"; shift 2
printf "%b[%s]%b %s %s\n" \
"$colour" "$level" "$_LOG_RST" \
"$(date '+%H:%M:%S')" "$*" >&2
}
log::info() { _log::_print 'INFO ' "$_LOG_GRN" "$@"; }
log::warn() { _log::_print 'WARN ' "$_LOG_YEL" "$@"; }
log::error() { _log::_print 'ERROR' "$_LOG_RED" "$@"; return 1; }
log::fatal() { _log::_print 'FATAL' "$_LOG_RED" "$@"; exit 1; }Une bibliothèque d'utilitaires pour le système de fichiers
Les scripts qui manipulent des fichiers et des répertoires répètent souvent les mêmes vérifications préventives : ce répertoire existe-t-il ? Ce chemin est-il accessible en écriture ? Suis-je sur le point de supprimer quelque chose d'important ?
Centraliser ces vérifications dans lib/fs.sh rend tous les scripts utilisateurs plus sûrs et plus lisibles. Remarquez que chaque fonction utilise return 1 en cas d'échec plutôt que exit, ce qui permet à l'appelant de gérer l'erreur proprement.
#!/usr/bin/env bash
# lib/fs.sh — Filesystem helper library
[[ -n "${_LIB_FS_LOADED:-}" ]] && return 0
_LIB_FS_LOADED=1
# Ensure a directory exists; create it if not
fs::require_dir() {
local dir="$1"
if [[ ! -d "$dir" ]]; then
mkdir -p "$dir" || { echo "[fs] Cannot create directory: $dir" >&2; return 1; }
fi
}
# Remove a file only if it exists (no error on missing)
fs::safe_rm() {
local target="$1"
[[ -e "$target" ]] && rm -rf -- "$target"
return 0
}
# Assert that a file exists and is readable
fs::require_file() {
local file="$1"
[[ -f "$file" && -r "$file" ]] || {
echo "[fs] Required file missing or unreadable: $file" >&2
return 1
}
}Versionner votre bibliothèque avec une constante
Lorsque vos bibliothèques sont partagées entre plusieurs projets ou distribuées à une équipe, il devient important de savoir quelle version d'une bibliothèque est chargée au moment de l'exécution. Une convention simple consiste à exporter une constante de version depuis chaque bibliothèque.
Les appelants peuvent alors vérifier qu'une version minimale est disponible au démarrage, afin de détecter rapidement les incompatibilités plutôt que de devoir déboguer plus tard des échecs mystérieux. La variable de protection sert également de chaîne de version, regroupant deux responsabilités dans une seule variable.
#!/usr/bin/env bash
# lib/str.sh — versioned example
# Guard doubles as the version identifier
[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
readonly _LIB_STR_LOADED='1.3.0'
# Caller can validate the version
str::version() { echo "$_LIB_STR_LOADED"; }
# ---- Utility functions ----
str::trim() {
local s="$1"
s="${s#"${s%%[![:space:]]*}"}"
s="${s%"${s##*[![:space:]]}"}"
echo "$s"
}
str::repeat() {
local str="$1" count="$2" result=''
for (( i=0; i<count; i++ )); do result+="$str"; done
echo "$result"
}Une démonstration autonome : utiliser plusieurs bibliothèques
Cette scène présente un script réaliste qui charge deux bibliothèques et utilise les fonctions de chacune. Remarquez comme le script principal reste clair : il exprime l'intention, tandis que tous les détails d'implémentation résident dans les bibliothèques.
Le script peut être exécuté comme un fichier autonome, car il définit les bibliothèques directement à l'aide de documents here écrits dans des fichiers temporaires. Dans un vrai projet, chaque bibliothèque résiderait dans son propre fichier sous lib/.
#!/usr/bin/env bash
# Standalone demo: inline libs written to /tmp, then sourced
set -euo pipefail
# --- Create a temporary lib/log.sh ---
TMPDIR_LIBS="$(mktemp -d)"
trap 'rm -rf "$TMPDIR_LIBS"' EXIT
cat > "${TMPDIR_LIBS}/log.sh" <<'LIBEOF'
[[ -n "${_LIB_LOG_LOADED:-}" ]] && return 0
_LIB_LOG_LOADED=1
log::info() { echo "[INFO] $*"; }
log::error() { echo "[ERROR] $*" >&2; return 1; }
LIBEOF
cat > "${TMPDIR_LIBS}/str.sh" <<'LIBEOF'
[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
_LIB_STR_LOADED=1
str::upper() { echo "${1^^}"; }
str::trim() { local s="$1"; s="${s#"${s%%[![:space:]]*}"}";
s="${s%"${s##*[![:space:]]}"}" ; echo "$s"; }
LIBEOF
# --- Source both libraries ---
source "${TMPDIR_LIBS}/log.sh"
source "${TMPDIR_LIBS}/str.sh"
# --- Main logic ---
log::info "Libraries loaded successfully."
raw_input=" hello from bash libraries "
trimmed="$(str::trim "$raw_input")"
log::info "Trimmed: '${trimmed}'"
log::info "Uppercased: '$(str::upper "$trimmed")'"Bonnes pratiques et pièges courants
Avant de distribuer une bibliothèque destinée à être utilisée par une équipe, passez en revue cette liste de contrôle :
- Protection d'inclusion — chaque bibliothèque doit en avoir une ; documentez le nom de la variable de protection en haut du fichier
- Aucun effet de bord au niveau supérieur — n'utilisez jamais
cd,echoet ne modifiez jamais l'état global en dehors du corps d'une fonction - Utilisez
localpour toutes les variables dans les fonctions — sanslocal, chaque affectation se propage dans la portée de l'appelant - Retournez une valeur, ne quittez jamais le shell —
exitdans un fichier chargé met fin à l'ensemble du shell appelant - Validez les entrées — vérifiez les arguments obligatoires et retournez un code d'erreur explicite lorsqu'ils sont absents
- Documentez avec des commentaires — décrivez le rôle de chaque fonction, ses paramètres et sa valeur de retour
- Évitez
set -edans les fichiers de bibliothèque — les appelants peuvent avoir leur propre stratégie de gestion des erreurs ; laissez-les décider
Vérification des connaissances : protections d'inclusion
Considérez un projet dans lequel main.sh charge à la fois lib/bootstrap.sh et lib/log.sh, tandis que lib/bootstrap.sh charge également lib/log.sh en interne. Quel est le rôle principal de la protection d'inclusion dans lib/log.sh ?
Récapitulatif de la leçon : réussir ses bibliothèques Bash
Vous avez découvert toutes les techniques essentielles pour créer et utiliser des bibliothèques Bash réutilisables. Voici les points à retenir :
- Chargez avec
sourceou.— cela charge un fichier dans le shell actuel et rend immédiatement ses fonctions disponibles - Utilisez
$BASH_SOURCEpour résoudre les chemins des bibliothèques par rapport au script appelant, afin de conserver la portabilité des projets - Protections d'inclusion (
[[ -n "${_GUARD:-}" ]] && return 0) — elles empêchent les définitions en double lorsque plusieurs fichiers chargent la même bibliothèque - Préfixes d'espace de noms (
log::,str::,fs::) — ils éliminent les collisions de noms de fonctions entre les bibliothèques - Un répertoire
lib/contenant des fichiers ciblés et à responsabilité unique facilite la maintenance des grands projets - Un chargeur d'initialisation (
lib/bootstrap.sh) fournit à chaque script un appel unique pour charger tout l'écosystème - N'utilisez jamais
exitni d'effets de bord au niveau supérieur dans les fichiers de bibliothèque — seules les définitions de fonctions et les constantes doivent s'y trouver
Appliquez ces modèles de manière cohérente : vos scripts shell seront aussi modulaires et faciles à maintenir que du code écrit dans n'importe quel langage de niveau supérieur.
Questions Fréquemment Posées
La leçon « Créer et charger des bibliothèques Bash réutilisables » est-elle gratuite ?
Oui — le texte complet de « Créer et charger des bibliothèques Bash réutilisables » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours DevOps Bootcamp, passe à CoddyKit PRO. Le cours DevOps Bootcamp comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Créer et charger des bibliothèques Bash réutilisables » ?
Organisez les assistants partagés dans des fichiers de bibliothèque .sh pouvant être chargés, avec des gardes d’inclusion et des préfixes de fonctions à espace de noms. Tu pratiques DevOps Bootcamp avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer DevOps Bootcamp ?
Aucune expérience préalable n'est requise. DevOps Bootcamp sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.
Combien de temps prend la leçon « Créer et charger des bibliothèques Bash réutilisables » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon DevOps Bootcamp ?
Oui. Chaque leçon DevOps Bootcamp inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Concevoir des fonctions avec une portée locale et des codes de retour
- Créer et charger des bibliothèques Bash réutilisables
- Analyser les options et les arguments avec getopts
- Transmettre des tableaux et des cartes associatives entre fonctions