Portabilité Bash
Guide pour écrire des scripts portables compatibles POSIX et comprendre quand tirer parti des fonctionnalités spécifiques à Bash.
Choix du Shebang
Utiliser #!/usr/bin/env bash pour les scripts Bash
#!/usr/bin/env bash
Pourquoi : Recherche bash dans le PATH, fonctionne sur les systèmes où bash peut se trouver dans différents emplacements.
Utiliser #!/bin/sh pour les scripts POSIX
#!/bin/sh
Pourquoi : Portabilité maximale lorsque les fonctionnalités bash ne sont pas nécessaires. Sur de nombreux systèmes, /bin/sh est dash ou un autre shell POSIX.
Chemin direct lorsque requis
#!/bin/bash
À utiliser uniquement lorsque : Les exigences système garantissent l'emplacement de bash, ou la politique de sécurité impose des chemins absolus.
Matrice des fonctionnalités POSIX vs Bash
| Fonctionnalité | POSIX | Bash | Recommandation |
|---|---|---|---|
[[ ]] | Non | Oui | Utiliser [ ] pour POSIX |
(( )) | Non | Oui | Utiliser [ ] avec -eq etc. |
| Tableaux | Non | Oui | Utiliser paramètres positionnels ou fichiers |
local | Partiel | Oui | Généralement sûr |
${var,,} | Non | 4+ | Utiliser tr pour POSIX |
<<< | Non | Oui | Utiliser echo |
=~ regex | Non | Oui | Utiliser grep ou expr |
source | Non | Oui | Utiliser la commande . (point) |
function f() | Non | Oui | Utiliser f() uniquement |
$'...' | Non | Oui | Utiliser printf |
{1..10} | Non | Oui | Utiliser seq ou une boucle while |
Motifs compatibles POSIX
Conditionnelles
# POSIX - utiliser [ ] avec des guillemets appropriés
if [ -f "$file" ]; then
echo "Le fichier existe"
fi
# Comparaison de chaînes
if [ "$var" = "value" ]; then
echo "Correspondance"
fi
# Comparaison numérique
if [ "$num" -gt 10 ]; then
echo "Plus grand"
fi
# Conditions composées
if [ -f "$file" ] && [ -r "$file" ]; then
echo "Fichier lisible"
fi
Conversion de casse (POSIX)
# Minuscules
lower=$(echo "$string" | tr '[:upper:]' '[:lower:]')
# Majuscules
upper=$(echo "$string" | tr '[:lower:]' '[:upper:]')
Opérations sur les sous-chaînes (POSIX)
# Obtenir une sous-chaîne - utiliser expr ou cut
substr=$(expr "$string" : '.\(.\)') # caractères 4-8
substr=$(echo "$string" | cut -c4-8)
# Longueur de chaîne
length=$(expr length "$string")
length=${#string} # Ceci est en fait POSIX
Lecture de fichiers (POSIX)
# Ligne par ligne
while IFS= read -r line; do
echo "$line"
done < "$file"
# Lire tout le fichier (sans cat)
content=$(cat "$file") # cat est POSIX
Substitution de commandes
# Syntaxe moderne (préférée même en POSIX)
result=$(command)
# Syntaxe héritée (à éviter)
result=`command`
# Imbriqué (pourquoi la moderne est meilleure)
result=$(echo $(date)) # Clair
result=`echo \`date\`` # Cauchemar d'échappement
Fonctionnalités Bash intéressantes à utiliser
Lorsque la portabilité n'est pas requise, ces fonctionnalités bash améliorent la qualité du code :
Test étendu [[ ]]
# Correspondance de motifs
[[ "$file" == *.txt ]]
# Correspondance d'expressions régulières
[[ "$input" =~ ^[0-9]+$ ]]
# Pas de souci de découpage de mots
[[ -f $file ]] # Guillemets facultatifs (mais toujours recommandés)
# Opérateurs logiques à l'intérieur
[[ -f "$file" && -r "$file" ]]
Tableaux
# Tableaux indicés
declare -a files=()
files+=("one.txt")
files+=("two.txt")
for f in "${files[@]}"; do
process "$f"
done
# Tableaux associatifs (Bash 4+)
declare -A config
config[host]="localhost"
config[port]="8080"
Expansion de paramètres
# Valeur par défaut
"${var:-default}"
# Conversion de casse (Bash 4+)
"${var,,}" # minuscules
"${var^^}" # majuscules
# Sous-chaîne
"${var:0:10}" # 10 premiers caractères
"${var: -5}" # 5 derniers caractères
# Recherche/remplacement
"${var//old/new}"
Here Strings
# Bash
read -r var <<< "chaîne d'entrée"
# Équivalent POSIX
var=$(echo "chaîne d'entrée")
Substitution de processus
# Bash - comparer deux sorties de commandes
diff <(sort fichier1) <(sort fichier2)
# Équivalent POSIX (avec fichiers temporaires)
sort fichier1 > /tmp/trie1
sort fichier2 > /tmp/trie2
diff /tmp/trie1 /tmp/trie2
Détection du type de shell
# Vérifier si on tourne dans bash
if [ -n "${BASH_VERSION:-}" ]; then
echo "Tourne dans Bash"
fi
# Vérifier la version de bash pour les fonctionnalités
if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then
echo "Bash 4+ disponible"
fi
# Détection générique de shell
case "${SHELL##*/}" in
bash) echo "bash" ;;
zsh) echo "zsh" ;;
*) echo "autre" ;;
esac
Fonctions utilitaires portables
Guide de décision pour la portabilité
Utiliser POSIX lorsque :
- Le script tourne sur des systèmes minimalistes (conteneurs, embarqué)
- La cible inclut dash, ash ou busybox sh
- Une compatibilité maximale est requise
- Le script fait partie de l'initialisation du système
Utiliser Bash lorsque :
- Les systèmes cibles ont garanti la présence de bash
- Besoin de tableaux, tableaux associatifs ou regex
- Manipulation complexe de chaînes requise
- La clarté du code est significativement améliorée
- Des fonctionnalités interactives sont nécessaires
Pièges de portabilité courants
echo vs printf
# Problématique - comportement variable
echo -n "pas de nouvelle ligne"
echo -e "avec\tdes tabulations"
# Portable
printf '%s' "pas de nouvelle ligne"
printf 'avec\tdes tabulations\n'
Affectation de variable
# Fonctionne partout
var="value"
# Peut échouer sur certains shells
var = "value" # Les espaces autour de = sont incorrects
Export avec affectation
# POSIX - commandes séparées
var="value"
export var
# Bash/modern - combiné (fonctionne presque partout)
export var="value"
Opérations de type tableau sans tableaux
# Utiliser les paramètres positionnels
set -- "item1" "item2" "item3"
for item in "$@"; do
echo "$item"
done
# Ou découpage basé sur IFS
items="item1:item2:item3"
IFS=':' read -r item1 item2 item3 <<EOF
$items
EOF