Caoua

——————————————-
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 :

  1. 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 ».
  2. printf '\0%.0s' : C’est une feinte de sioux. Pour chaque nombre généré par seq, printf va afficher un octet nul (\0). Le %.0s sert à masquer le nombre lui-même pour ne garder que l’octet vide. On obtient donc une chaîne invisible de $X$ octets nuls.
  3. | tr '\0' "$caractere" : Le « pipe » (|) envoie ces octets nuls à la commande tr (Translate), qui remplace instantanément chaque octet nul par ton caractère (le « A » ou le « X »).
  4. > "$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 :

  1. Sécurité d’intégration : Le fait que _log_message redirige l’affichage console vers >&2 (la sortie d’erreur standard) garantit à 100 % que tes captures de variables type ma_var=$(ma_fonction) ne contiendront jamais de résidus de logs.
  2. Filtre de sévérité (LOG_LEVEL) : En phase de dev, tu le laisses sur DEBUG. Quand ton script est stable et passe en production, tu modifies juste la variable à INFO ou WARN. Les messages de log_debug n’apparaîtront plus à l’écran ni dans le fichier, sans que tu n’aies à supprimer une seule ligne de code.
  3. 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 !