——————————————-
Gestion « classique » des tableaux avec bash
——————————————-
En Bash, la gestion des tableaux est un peu particulière par rapport à d’autres langages (comme JavaScript ou Python), mais elle est très puissante une fois qu’on a pigé la syntaxe.
Voici le guide complet pour déclarer, manipuler et parcourir des tableaux en Bash.
1. Déclarer un tableau
Il existe deux types de tableaux en Bash : les tableaux indexés (avec des numéros 0, 1, 2...) et les tableaux associatifs (avec des clés textuelles, comme un dictionnaire).
Tableaux indexés (Classiques)
On utilise des parenthèses () et les éléments sont séparés par des espaces (pas de virgules !).
Bash
# Méthode 1 : Déclaration directe
mon_tableau=("pomme" "banane" "orange")
# Méthode 2 : Élément par élément
autre_tableau[0]="voiture"
autre_tableau[1]="moto"
Tableaux associatifs (Clés / Valeurs)
Disponible à partir de Bash 4. Il faut impérativement le déclarer explicitement avec declare -A.
Bash
declare -A capitales
capitales[France]="Paris"
capitales[Espagne]="Madrid"
capitales[Italie]="Rome"
2. Lire les valeurs (La syntaxe essentielle)
C’est là que Bash devient un peu verbeux. Pour accéder au contenu, il faut toujours utiliser des accolades {} et le symbole $.
Accéder à un élément précis
Bash
echo "${mon_tableau[0]}" # Affiche : pomme
echo "${capitales[France]}" # Affiche : Paris
Afficher TOUT le tableau
On utilise le symbole @ ou *.
Bash
echo "${mon_tableau[@]}" # Affiche : pomme banane orange
Connaître la taille (le nombre d’éléments)
On ajoute un hashtag # devant le nom du tableau.
Bash
echo "${#mon_tableau[@]}" # Affiche : 3
3. Ajouter ou modifier des éléments
- Modifier un élément existant : On réassigne simplement la valeur à l’index voulu.
- Ajouter à la fin d’un tableau indexé : On utilise l’opérateur
+=.
Bash
# Modifier le deuxième élément (index 1)
mon_tableau[1]="fraise"
# Ajouter un élément à la fin
mon_tableau+=("mangue")
echo "${mon_tableau[@]}" # Affiche : pomme fraise orange mangue
4. Parcourir un tableau (Boucles)
C’est l’utilisation la plus fréquente. On utilise une boucle for.
Boucler sur les VALEURS
Bash
fruits=("pomme" "banane" "orange")
for fruit in "${fruits[@]}"; do
echo "J'aime les ${fruit}s"
done
Boucler sur les CLÉS / INDEX
On ajoute un point d’exclamation ! devant le nom du tableau pour récupérer les index (ou les clés d’un tableau associatif).
Bash
declare -A capitales=([France]="Paris" [Italie]="Rome")
for pays in "${!capitales[@]}"; do
echo "La capitale de : $pays est ${capitales[$pays]}"
done
5. Supprimer des éléments
On utilise la commande unset.
Bash
# Supprimer un élément précis (attention, cela crée un "trou" dans les index)
unset mon_tableau[1]
# Supprimer TOUT le tableau
unset mon_tableau
⚠️ Piège classique en Bash : Si tu oublies les guillemets autour de
"${mon_tableau[@]}", Bash va découper tes éléments s’ils contiennent des espaces. Prends la bonne habitude de toujours mettre des guillemets doubles !
——————————————-
les faux tableaux en 3D avec BASH
——————————————-
Bash ne supporte pas nativement les tableaux multidimensionnels (2D, 3D, etc.). Un tableau en Bash est strictement à une seule dimension (une liste simple).
Cependant, les codeurs ont de la ressource. Pour simuler un tableau en 3 dimensions (par exemple pour stocker des coordonnées $X, Y, Z$ ou une matrice), on utilise une astuce : les tableaux associatifs avec des clés combinées.
Voici comment faire pour simuler et gérer de la 3D en Bash.
1. Déclarer et remplir le tableau 3D
Imaginons que l’on veuille stocker la température de plusieurs pièces d’un bâtiment à différents étages.
- Dimension 1 : Le bâtiment (BatA, BatB)
- Dimension 2 : L’étage (Etage0, Etage1)
- Dimension 3 : La pièce (Room1, Room2)
Bash
#!/bin/bash
# 1. On déclare un tableau associatif
declare -A matrice3D
# 2. On remplit en combinant les 3 dimensions dans la clé, séparées par un caractère (ici un haricot/pipe '|')
matrice3D["BatA|Etage0|Room1"]=19
matrice3D["BatA|Etage0|Room2"]=21
matrice3D["BatA|Etage1|Room1"]=20
matrice3D["BatB|Etage0|Room1"]=17
# 3. Accéder à une valeur précise
echo "Température BatA, Etage 1, Room 1 : ${matrice3D["BatA|Etage1|Room1"]}°C"
2. Parcourir ce tableau « 3D »
Pour lire toutes les données, on va boucler sur les clés, puis utiliser la commande cut (ou les expressions régulières de Bash) pour « séparer » nos trois dimensions.
Bash
for cle in "${!matrice3D[@]}"; do
# On extrait chaque dimension grâce au séparateur "|"
batiment=$(echo "$cle" | cut -d'|' -f1)
etage=$(echo "$cle" | cut -d'|' -f2)
piece=$(echo "$cle" | cut -d'|' -f3)
valeur=${matrice3D[$cle]}
echo "Au $batiment, à l'$etage, la $piece a une température de $valeur°C."
done
Alternative : La boucle imbriquée classique
Si tu veux créer un cube de données (par exemple de taille 3x3x3 u peux utiliser trois boucles for imbriquées pour générer tes clés automatiquement :
Bash
declare -A cube
# Remplissage automatique du cube 3D (3x3x3)
for x in {1..3}; do
for y in {1..3}; do
for z in {1..3}; do
# On stocke une valeur (ici la somme des coordonnées)
cube["$x,$y,$z"]=$((x + y + z))
done
done
done
# Lire une coordonnée spécifique
echo "Valeur en position (2,3,1) : ${cube["2,3,1"]}"
————————————–
Fonction avec 3 paramètres : nom du fichier à créer, caractère à remplir, nombre d’octets
—————————————
Bash
create_filler_file() {
# 1. Récupération et validation des paramètres
local fichier="$1"
local caractere="$2"
local taille="$3"
# Petite sécurité : on vérifie que les 3 paramètres sont là
if [ -z "$fichier" ] || [ -z "$caractere" ] || [ -z "$taille" ]; then
echo "Erreur : Paramètres manquants."
echo "Usage: create_filler_file <nom_fichier> <caractère> <taille_en_octets>"
return 1
fi
# 2. Génération du fichier
# printf va générer une suite de zéros (\0) de la taille demandée
# tr va remplacer tous ces zéros par le caractère choisi
printf '\0%.0s' $(seq 1 "$taille") | tr '\0' "$caractere" > "$fichier"
echo "Fichier '$fichier' ($taille octets remplis de '$caractere') créé."
}
Comment l’utiliser ?
Une fois la fonction chargée dans ton terminal (ou dans ton script), tu l’appelles exactement dans l’ordre que tu as demandé :
Bash
# Crée un fichier "test.txt" de 10 octets rempli de 'A'
create_filler_file "test.txt" "A" 10
# Crée un fichier de 2 Mo (2097152 octets) rempli de 'X'
create_filler_file "gros_test.dat" "X" 2097152
Comment ça fonctionne sous le capot ?
L’astuce réside dans la ligne centrale :
seq 1 "$taille": Génère une liste de nombres de 1 jusqu’à la taille demandée (par exemple 10). Cela sert juste de « compteur ».printf '\0%.0s': C’est une feinte de sioux. Pour chaque nombre généré parseq,printfva afficher un octet nul (\0). Le%.0ssert à masquer le nombre lui-même pour ne garder que l’octet vide. On obtient donc une chaîne invisible de $X$ octets nuls.| tr '\0' "$caractere": Le « pipe » (|) envoie ces octets nuls à la commandetr(Translate), qui remplace instantanément chaque octet nul par ton caractère (le « A » ou le « X »).> "$fichier": Redirige le résultat final dans le fichier.
C’est très propre, et contrairement à d’autres méthodes, ça n’ajoute pas de saut de ligne (\n) invisible à la fin du fichier. Le fichier fera exactement le nombre d’octets demandé.
Class UPLOAD
Proposition de structure (Refactoring)
Voici comment tu pourrais réorganiser ta classe pour qu’elle soit propre, évolutive et sécurisée :
PHP
<?php
class UploadFiles
{
private string $destination;
private array $allowedMimeTypes = ['image/jpeg', 'image/png', 'application/pdf'];
// Le constructeur configure l'outil
public function __construct(string $destinationDir = 'uploaded')
{
$this->setDestination($destinationDir);
}
// Le setter valide et prépare le terrain
public final function setDestination(string $dir): void
{
$this->destination = dirname(__DIR__) . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR;
}
// La méthode principale qui FAIT le travail
public function upload(array $fileRequest): string
{
// 1. Vérification des erreurs de base PHP
if ($fileRequest['error'] !== UPLOAD_ERR_OK) {
throw new Exception($this->getErrorMessage($fileRequest['error']));
}
// 2. Vérification de la sécurité (Mime Type)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$realMimeType = $finfo->file($fileRequest['tmp_name']);
if (!in_array($realMimeType, $this->allowedMimeTypes)) {
throw new Exception("Type de fichier non autorisé : " . $realMimeType);
}
// 3. Génération d'un nom sécurisé
$extension = pathinfo($fileRequest['name'], PATHINFO_EXTENSION);
$secureName = bin2hex(random_bytes(16)) . '.' . $extension;
$finalPath = $this->destination . $secureName;
// 4. Déplacement du fichier temporaire
if (!move_uploaded_file($fileRequest['tmp_name'], $finalPath)) {
throw new Exception("Impossible de déplacer le fichier vers la destination finale.");
}
return $secureName; // On retourne le nouveau nom pour l'enregistrer en BDD par exemple
}
private function getErrorMessage(int $errorCode): string
{
$errors = [
1 => 'Le fichier dépasse upload_max_filesize.',
2 => 'Le fichier dépasse MAX_FILE_SIZE.',
3 => 'Fichier partiellement téléchargé.',
4 => 'Aucun fichier n\'a été téléchargé.',
6 => 'Dossier temporaire manquant.',
7 => 'Échec de l\'écriture sur le disque.',
8 => 'Une extension PHP a bloqué le téléchargement.',
];
return $errors[$errorCode] ?? 'Erreur de téléchargement inconnue.';
}
}
Comment tu l’utilises ensuite dans ton code :
PHP
// Au moment de traiter ton formulaire :
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Protection contre le piège du post_max_size
if (empty($_FILES) && empty($_POST) && $_SERVER['CONTENT_LENGTH'] > 0) {
die('La taille totale des données envoyées dépasse la limite du serveur (post_max_size).');
}
try {
$uploader = new UploadFiles('mes_fichiers'); // Va pointer vers /parent/mes_fichiers/
$nomFichierSauvegarde = $uploader->upload($_FILES['userfile']);
echo "Super ! Fichier enregistré sous le nom : " . $nomFichierSauvegarde;
} catch (Exception $e) {
echo "Erreur lors de l'upload : " . $e->getMessage();
}
}
GESTION PROPRE ET PRO DES LOGS
Voici une solution clé en main, propre, compatible avec set -euo pipefail.
1. La brique réutilisable : logger.sh
Crée un fichier nommé logger.sh (dans un dossier partagé ou à la racine de ton projet) :
Crée un fichier nommé logger.sh (dans un dossier partagé ou à la racine de ton projet) :
Bash
#!/bin/bash
# Configuration par défaut (peut être surchargée avant de sourcer le script)
LOG_DIR="${LOG_DIR:-${PWD}/logs}"
LOG_FILE="${LOG_FILE:-${LOG_DIR}/project.log}"
LOG_MAX_SIZE_KB="${LOG_MAX_SIZE_KB:-1024}" # 1 Mo par défaut
LOG_LEVEL="${LOG_LEVEL:-DEBUG}" # DEBUG, INFO, WARN, ERROR
# Création du dossier de log si inexistant
mkdir -p "${LOG_DIR}"
# --- Fonction interne de rotation automatique ---
_rotate_logs_if_needed() {
if [ -f "${LOG_FILE}" ]; then
local size_kb
size_kb=$(du -k "${LOG_FILE}" | cut -f1)
if [ "${size_kb}" -ge "${LOG_MAX_SIZE_KB}" ]; then
# Archive l'ancien log avec un timestamp et compresse
mv "${LOG_FILE}" "${LOG_FILE}.$(date +%Y%m%d%H%M%S).old"
touch "${LOG_FILE}"
# Optionnel : tu peux ajouter une ligne ici pour supprimer les archives de plus de X jours
fi
fi
}
# --- Fonction maîtresse de log ---
_log_message() {
local -r level="$1"
local -r message="$2"
local -r color_code="$3"
# Gestion des priorités (représentation numérique pour filtrer)
local -A levels_weight=([DEBUG]=1 [INFO]=2 [WARN]=3 [ERROR]=4)
# Si le niveau du message est inférieur au niveau global configuré, on ignore
if [ "${levels_weight[${level}]}" -lt "${levels_weight[${LOG_LEVEL}]}" ]; then
return 0
fi
# Vérification de la taille du fichier avant d'écrire
_rotate_logs_if_needed
local -r timestamp=$(date +'%Y-%m-%d %H:%M:%S')
# 1. Écriture dans le fichier (sans couleurs ANSI)
printf '[%s] [%s] %s\n' "${timestamp}" "${level}" "${message}" >> "${LOG_FILE}"
# 2. Affichage sur la sortie d'erreur (stderr) avec des couleurs pour l'humain
# On utilise >&2 pour ne JAMAIS polluer les captures de fonctions $(...)
printf '%b[%s] [%s] %s%b\n' "${color_code}" "${timestamp}" "${level}" "${message}" '\033[0m' >&2
}
# --- API Publique (les fonctions que tu vas appeler) ---
log_debug() { _log_message "DEBUG" "$1" '\033[0;36m'; } # Cyan
log_info() { _log_message "INFO" "$1" '\033[0;32m'; } # Vert
log_warn() { _log_message "WARN" "$1" '\033[0;33m'; } # Jaune
log_error() { _log_message "ERROR" "$1" '\033[0;31m'; } # Rouge
2. Comment l’utiliser dans ton script de backup ?
C’est là que ça devient magique. Tu n’as qu’à configurer tes variables de log (si tu veux changer les valeurs par défaut) et sourcer le fichier.
Voici comment ton script initial s’interface avec :
Bash
#!/bin/bash
set -euo pipefail
# --- Configuration des Logs (Optionnel, avant le source) ---
LOG_DIR="${PWD}/history_files_dev/logs"
LOG_FILE="${LOG_DIR}/bkup.log"
LOG_LEVEL="DEBUG" # Passe à "INFO" en production pour masquer les détails
# --- Importation du Logger ---
# On vérifie que le fichier existe avant de le charger
if [ -f "./logger.sh" ]; then
source "./logger.sh"
else
echo "[Erreur critique] Impossible de charger logger.sh" >&2
exit 1
fi
# --- Tes variables ---
declare -r fichier_a_bkuper=${1:-""}
if [ -z "${fichier_a_bkuper}" ]; then
log_error "Aucun fichier fourni en argument."
exit 1
fi
# --- Ta fonction de formatage ---
function formatage_du_nom_pour_le_bkup() {
local -r fichier_tiret=${fichier_a_bkuper/\//-}
# Ce log va écrire dans le fichier ET sur stderr en Cyan.
# Il ne perturbe PAS la capture du echo ci-dessous !
log_debug "Formatage en cours pour : ${fichier_tiret}"
local -r nom_fichier=${fichier_tiret%%.*}
local -r extension=${fichier_a_bkuper#*.}
echo "${nom_fichier}.${extension}"
}
# Capture propre
log_info "Démarrage du processus de sauvegarde..."
declare nom_formate=$(formatage_du_nom_pour_le_bkup)
log_info "Nom généré : ${nom_formate}"
Les forces de cette solution pour tous tes projets :
- Sécurité d’intégration : Le fait que
_log_messageredirige l’affichage console vers>&2(la sortie d’erreur standard) garantit à 100 % que tes captures de variables typema_var=$(ma_fonction)ne contiendront jamais de résidus de logs. - Filtre de sévérité (
LOG_LEVEL) : En phase de dev, tu le laisses surDEBUG. Quand ton script est stable et passe en production, tu modifies juste la variable àINFOouWARN. Les messages delog_debugn’apparaîtront plus à l’écran ni dans le fichier, sans que tu n’aies à supprimer une seule ligne de code. - Poids maîtrisé : Grâce à
_rotate_logs_if_needed, ton script vérifie sa propre taille. S’il dépasse 1 Mo, il s’archive tout seul. Ton serveur te dira merci !
