Skip to content

Bash Aide-mémoire

Shell Unix et langage de commande pour le scripting et l'automatisation.

01

Variables et chaînes

Variables et assignation

Les variables Bash sont des chaînes non typées par défaut ; l'arithmétique fonctionne si la valeur est numérique. NE JAMAIS mettre d'espaces autour de = dans les assignations — « name = Alice » essaie d'exécuter une commande appelée name. Utilisez $(...) pour la substitution de commande (préféré aux backticks). export rend une variable disponible aux processus enfants ; readonly la rend immuable. Les variables d'environnement (exportées) sont héritées par les scripts et programmes que vous lancez.

bash
# no spaces around = in assignments
name="Alice"
age=30
PI=3.14
active=true

# use variables with $ prefix
echo "Hello, $name!"
echo "Age: $age"

# command substitution: capture command output
today=$(date +%Y-%m-%d)
files=$(ls | wc -l)
echo "Today is $today, $files files"

# readonly and environment variables
readonly MAX=100
export PATH="$PATH:/opt/bin"
env | grep PATH

Expansion des paramètres de chaîne

L'expansion des paramètres est l'une des fonctionnalités les plus puissantes de Bash, encapsulée dans ${...}. ${#var} donne la longueur ; ${var:offset:longueur} extrait des sous-chaînes (offset négatif compte depuis la fin — notez l'espace avant -). ,, et ^^ convertissent la casse (Bash 4+). :- fournit des valeurs par défaut sans définir la variable ; := la définit en effet de bord ; :? avorte avec une erreur — génial pour les vérifications d'arguments requis dans les scripts.

bash
s="Hello World"
echo ${#s}                # length: 11
echo ${s:0:5}             # substring: Hello
echo ${s:6}               # from index 6: World
echo ${s: -5}             # last 5 chars: World

# case conversion (Bash 4+)
echo ${s,,}               # lowercase: hello world
echo ${s^^}               # uppercase: HELLO WORLD
echo ${s^l}               # capitalize first if 'l': Hello World

# default values
echo ${name:-Guest}       # use Guest if name unset
echo ${name:=Guest}       # assign Guest if unset, then use
echo ${name:?required}    # error and exit if unset

Recherche et remplacement dans les chaînes

Le motif / substitue ; // substitue tout ; # correspond au début ; % correspond à la fin. ## et %% sont gourmands (correspondance la plus longue) tandis que # et % sont non gourmands. Ils sont essentiels pour la manipulation de chemins : ${path##*/} extrait le basename (comme la commande basename), et ${path%/*} extrait le répertoire (comme dirname). Maîtriser cela évite de nombreux appels à des commandes externes comme sed et cut.

bash
s="Hello World Hello"

# replace first occurrence
echo ${s/Hello/Hi}        # Hi World Hello

# replace all occurrences
echo ${s//Hello/Hi}       # Hi World Hi

# replace at beginning / end
echo ${s/#Hello/Hi}       # Hi World Hello (prefix)
echo ${s/%Hello/Hi}       # Hello World Hello (no suffix match)

# delete patterns (replace with empty)
echo ${s//Hello/}         #  World 
path="/usr/local/bin/app"
echo ${path##*/}          # app (longest prefix)
echo ${path%/*}           # /usr/local/bin (shortest suffix)

# extract filename and extension
file="report.tar.gz"
echo ${file%.*}           # report.tar (remove ext)
echo ${file##*.}          # gz (keep ext)

Quoting et caractères spéciaux

Les simples quotes préservent tout littéralement — pas d'expansion de variable, pas de traitement d'échappement (vous ne pouvez même pas inclure une simple quote dans des simples quotes). Les doubles quotes permettent l'expansion de $, backtick et \ tout en préservant les espaces et caractères spéciaux — préférez presque toujours les doubles quotes autour des variables pour éviter les bugs de word-splitting et de globbing. Utilisez $'...' pour les échappements ANSI-C comme \n et \t.

bash
# single quotes: literal, no expansion
echo 'Hello $USER'         # Hello $USER

# double quotes: expansion happens
echo "Hello $USER"         # Hello alice

# escape special chars with backslash
echo "Price: \\$5.00"      # Price: $5.00
echo 'It\'s a test'       # It's a test (escape inside single)

# concatenate strings
a="Hello"
b="World"
c="$a, $b!"
echo $c                    # Hello, World!

# multi-line string
msg="Line 1
Line 2
Line 3"
echo "$msg"

Read et entrée utilisateur

read capture l'entrée utilisateur. -p affiche un prompt, -t définit un timeout (retourne non-zéro), -a lit dans un tableau. À la lecture de fichiers ligne par ligne, utilisez toujours « IFS= read -r » — IFS= préserve les espaces de début/fin, et -r désactive l'échappement backslash pour que les backslashes soient préservés littéralement. C'est le motif sûr canonique pour traiter les fichiers ligne par ligne.

bash
# read a single value
echo -n "Enter your name: "
read name
echo "Hi, $name!"

# read multiple values
read -p "First and last: " first last

# read with timeout (seconds)
read -t 5 -p "Quick! " answer || echo "too slow"

# read into an array
echo "Enter colors:"
read -a colors
echo "First: ${colors[0]}"

# read line by line from a file
while IFS= read -r line; do
    echo ">>> $line"
done < file.txt
02

Tableaux

Tableaux indexés

Les tableaux Bash sont indexés depuis zéro. @ et * s'étendent à tous les éléments — quotez-les toujours (« ${arr[@]} ») pour gérer correctement les éléments avec espaces. ${#arr[@]} donne le compte. += ajoute. unset laisse un trou dans les indices ; pour réindexer, utilisez arr=("${arr[@]}"). Le slicing avec ${arr[@]:start:count} retourne un sous-ensemble.

bash
# create and populate
fruits=("apple" "banana" "cherry")
fruits[3]="date"

# access elements
echo ${fruits[0]}         # apple
echo ${fruits[@]}         # all: apple banana cherry date
echo ${fruits[*]}         # same as @ in most contexts

# count and slice
echo ${#fruits[@]}        # count: 4
echo ${fruits[@]:1:2}     # elements 1-2: banana cherry

# append
fruits+=("elderberry")

# iterate
for f in "${fruits[@]}"; do
    echo "- $f"
done

# delete an element
unset fruits[1]            # removes "banana" (gap remains)

Tableaux associatifs (maps)

Les tableaux associatifs (declare -A) mappent des clés chaînes vers des valeurs, comme les dictionnaires dans d'autres langages — disponibles dans Bash 4+. Utilisez ${!arr[@]} pour obtenir les clés et ${arr[@]} pour les valeurs. Le test -v vérifie si une clé existe. Contrairement aux tableaux indexés, les clés sont des chaînes arbitraires, donc quoter les clés avec espaces est essentiel. Ils nécessitent Bash 4+ (macOS livre Bash 3 par défaut).

bash
# must declare before use (Bash 4+)
declare -A ages

# key-value assignment
ages[alice]=30
ages[bob]=25
ages["carol smith"]=28

# access by key
echo ${ages[alice]}       # 30
echo ${ages[bob]}         # 25

# all keys and values
echo ${!ages[@]}          # keys: alice bob carol smith
echo ${ages[@]}           # values: 30 25 28
echo ${#ages[@]}          # count: 3

# iterate keys
for name in "${!ages[@]}"; do
    echo "$name is ${ages[$name]}"
done

# check if key exists
if [[ -v ages[alice] ]]; then
    echo "alice exists"
fi

Motifs d'itération de tableau

Il y a trois styles d'itération principaux : par valeur (for x in "${arr[@]}"), par index (for i in "${!arr[@]}"), et style C. La forme par index est utile quand vous avez besoin de la position. Quotez toujours « ${arr[@]} » pour que les éléments contenant des espaces soient préservés comme éléments uniques. Construire des tableaux avec += dans une boucle est la façon idiomatique d'accumuler des résultats.

bash
arr=(one two three four five)

# iterate by value
for item in "${arr[@]}"; do
    echo "$item"
done

# iterate by index
for i in "${!arr[@]}"; do
    echo "[$i] = ${arr[$i]}"
done

# C-style indexed loop
for ((i=0; i<${#arr[@]}; i++)); do
    echo "${arr[$i]}"
done

# map/transform: build a new array
upper=()
for w in "${arr[@]}"; do
    upper+=("$(echo $w | tr a-z A-Z)")
done
echo "${upper[@]}"

Tableau depuis une sortie de commande

mapfile/readarray (Bash 4+) lit les lignes dans un tableau d'un coup — beaucoup plus rapide qu'une boucle while pour les gros fichiers. Pour diviser une chaîne délimitée, définissez IFS et utilisez read -ra. Pour les noms de fichiers (qui peuvent contenir des espaces ou newlines), utilisez toujours find -print0 avec read -d '' pour diviser sur des octets nuls — le seul séparateur sûr pour les noms de fichiers. La substitution de processus < <(...) alimente la boucle sans subshell.

bash
# split a string into an array
csv="a,b,c,d"
IFS=',' read -ra parts <<< "$csv"
echo ${parts[2]}          # c

# read lines of a file into an array
mapfile -t lines < file.txt
echo "${lines[0]}"        # first line
echo "${#lines[@]}"       # line count

# alternative: readarray (same as mapfile)
readarray -t lines2 < file.txt

# from command output (words split on IFS)
files=($(ls *.txt))
echo "${files[@]}"

# safer: read null-delimited output
while IFS= read -r -d '' f; do
    files+=("$f")
done < <(find . -name "*.txt" -print0)

Variables spéciales

Ces variables spéciales sont essentielles pour le scripting. $0 est le chemin du script ; $1-$9 sont les arguments positionnels (utilisez ${10} pour deux chiffres). Quotez toujours « $@ » pour préserver les arguments avec espaces — « $* » les joint en une seule chaîne. $? donne le statut de sortie (0 = succès) et est la base de toute vérification d'erreur. $$ et $! sont utiles pour les fichiers PID et la gestion de processus. shift jette $1 et décale le reste vers le bas.

bash
# positional parameters
echo $0       # script name
echo $1       # first argument
echo $#       # argument count
echo $@       # all arguments (separate words)
echo "$@"     # all arguments (preserves quoting)
echo $*       # all arguments (single string)

# process and shell info
echo $$       # current shell PID
echo $!       # PID of last background command
echo $?       # exit status of last command (0 = success)
echo $-       # current shell option flags
echo $_       # last argument of previous command

# shift arguments
echo "$1 $2"  # a b
shift
echo "$1 $2"  # b c

# iterate all args safely
for arg in "$@"; do
    echo "arg: $arg"
done
03

Flux de contrôle et tests

If / Elif / Else

[ ] est la commande de test POSIX (portable mais limitée) ; [[ ]] est la version améliorée de Bash supportant le pattern matching (== avec wildcards), les regex (=~), les opérateurs && / ||, et pas besoin de quoter les variables. Préférez [[ ]] dans les scripts Bash. Quotez toujours les variables dans [ ] pour éviter les erreurs de syntaxe avec des valeurs vides ou contenant des espaces. Les tests -f, -r, -d, -e vérifient l'existence et les attributs de fichiers.

bash
# basic if
if [ "$age" -ge 18 ]; then
    echo "adult"
elif [ "$age" -ge 13 ]; then
    echo "teen"
else
    echo "child"
fi

# modern [[ ]] test (Bash-specific, safer)
if [[ $name == "Alice" ]]; then
    echo "hi Alice"
fi

# pattern matching in [[ ]]
if [[ $file == *.txt ]]; then
    echo "text file"
fi

# regex matching
if [[ $email =~ ^[a-z]+@[a-z]+.[a-z]+$ ]]; then
    echo "valid email"
fi

# multiple conditions
if [[ -f file.txt && -r file.txt ]]; then
    echo "readable file"
fi

Opérateurs de test

Les tests de fichiers (-f, -d, -r, etc.) vérifient les attributs du système de fichiers. Les tests de chaînes utilisent = et != (ou == dans [[ ]]) ; -z vérifie le vide, -n le non-vide. Les comparaisons d'entiers utilisent -eq, -ne, -lt, -gt, -le, -ge — N'utilisez PAS < > pour les entiers dans [ ] (ils redirigent !). Dans [[ ]] et (( )) vous pouvez utiliser les opérateurs familiers < > <= >=. Mémoriser ces opérateurs est essentiel pour écrire des conditionnelles correctes.

bash
# file tests
[ -f file ]       # exists and is regular file
[ -d dir ]        # exists and is directory
[ -e path ]       # exists (any type)
[ -r file ]       # readable
[ -w file ]       # writable
[ -x file ]       # executable
[ -s file ]       # exists and is non-empty
[ file1 -nt file2 ]  # newer than
[ file1 -ot file2 ]  # older than

# string tests
[ -z "$s" ]       # empty string
[ -n "$s" ]       # non-empty string
[ "$a" = "$b" ]   # equal (use == in [[ ]])
[ "$a" != "$b" ]  # not equal

# integer tests
[ $n -eq 5 ]      # equal
[ $n -ne 5 ]      # not equal
[ $n -lt 5 ]      # less than
[ $n -gt 5 ]      # greater than
[ $n -le 5 ]      # less or equal
[ $n -ge 5 ]      # greater or equal

Instruction case

case est l'équivalent Bash du switch — il correspond une valeur contre des motifs glob. | sépare les alternatives. ;; termine une branche (comme break). Le motif *) est le défaut. Les motifs supportent les wildcards (*, ?, [abc]) mais pas les regex complètes. case est plus propre que les longues chaînes if-elif pour dispatcher sur une seule valeur, et c'est la façon standard de construire des sous-commandes en ligne de commande (start/stop/restart).

bash
case $1 in
    start)
        echo "Starting service..."
        systemctl start app
        ;;
    stop)
        echo "Stopping service..."
        systemctl stop app
        ;;
    restart)
        $0 stop
        $0 start
        ;;
    status)
        systemctl status app
        ;;
    --help|-h)
        echo "Usage: $0 {start|stop|restart|status}"
        ;;
    *)
        echo "Unknown command: $1" >&2
        exit 1
        ;;
esac

# pattern matching in case
case $file in
    *.jpg|*.png)    echo "image" ;;
    *.mp3|*.wav)    echo "audio" ;;
    *.txt)          echo "text" ;;
    *)              echo "other" ;;
esac

Arithmétique et (( ))

$(( )) évalue des expressions arithmétiques et retourne le résultat. À l'intérieur de (( )), les noms de variables n'ont pas besoin de $ — utilisez juste le nom. (( )) comme commande retourne le statut de sortie 0 (vrai) si le résultat est non-zéro, ce qui la rend idéale pour les conditions numériques. Bash ne fait que de l'arithmétique entière ; pour le flottant utilisez bc ou awk. Opérateurs : + - * / % ** et les opérateurs de comparaison et bit à bit style C fonctionnent tous.

bash
# arithmetic expansion
x=5
y=$((x + 3))
echo $y              # 8
echo $((x * 2))      # 10
echo $((2 ** 10))    # 1024 (exponent)
echo $((17 % 5))     # 2 (modulo)
echo $((17 / 5))     # 3 (integer division)

# (( )) condition: no $ needed, returns exit status
x=10
if (( x > 5 && x < 20 )); then
    echo "in range"
fi

# increment / decrement
((x++))             # post-increment
((x--))             # post-decrement
((++x))             # pre-increment

# bitwise operators
echo $((5 & 3))     # 1 (AND)
echo $((5 | 2))     # 7 (OR)
echo $((5 << 1))    # 10 (left shift)

Court-circuit et ternaire

&& et || sont des opérateurs de court-circuit qui servent aussi de flux de contrôle concis : « cmd1 && cmd2 » exécute cmd2 seulement si cmd1 réussit ; « cmd1 || cmd2 » exécute cmd2 seulement si cmd1 échoue. Cela remplace un simple if-else en une ligne. Le motif « A && B || C » imite un ternaire mais est subtilement bogué si B peut échouer — pour du code robuste, utilisez une vraie instruction if. Le deux-points (:) est une commande no-op utile pour les branches vides.

bash
# && and || as short-circuit control flow
[[ -f file.txt ]] && echo "exists"
[[ -f file.txt ]] || echo "missing"

# combined: do this OR fail
mkdir -p build && cd build && make || exit 1

# ternary-like (Bash doesn't have a real ternary)
result=$([[ $x -gt 0 ]] && echo "positive" || echo "non-positive")

# default value via &&
echo ${name:-"anonymous"}

# run command, fallback on failure
ping -c1 host && echo "up" || echo "down"

# null command as no-op
if condition; then
    :    # do nothing
fi
04

Boucles et itération

Boucles for

La boucle for itère sur une liste de mots. L'expansion d'accolades {1..5} génère des séquences (avec un pas optionnel {start..end..step}). Le for style C for ((init; cond; update)) est le meilleur pour les compteurs numériques. À l'itération de fichiers avec *.txt, les globs non quotés s'étendent sûrement, mais si aucun fichier ne correspond vous obtenez le littéral « *.txt » — activez shopt -s nullglob pour obtenir une liste vide à la place.

bash
# iterate a list
for fruit in apple banana cherry; do
    echo "$fruit"
done

# iterate an array
colors=("red" "green" "blue")
for c in "${colors[@]}"; do
    echo "Color: $c"
done

# range with brace expansion
for i in {1..5}; do
    echo "Number: $i"
done

# range with step
for i in {0..20..2}; do echo $i; done    # 0 2 4 ... 20

# C-style for loop
for ((i=0; i<5; i++)); do
    echo "Iteration $i"
done

# iterate command output
for file in *.txt; do
    echo "Processing $file"
done

Boucles while et until

while s'exécute tant que la condition réussit (exit 0) ; until s'exécute jusqu'à ce qu'elle réussisse — ce sont des opposées. Le motif « while read » est la façon canonique de traiter les fichiers ligne par ligne ; utilisez toujours « IFS= read -r » pour la sécurité. Notez que piper dans une boucle while l'exécute dans un subshell, donc les changements de variables ne persistent pas — utilisez la substitution de processus « < <(cmd) » à la place quand vous avez besoin de conserver les variables.

bash
# while: runs while condition is true
count=0
while [ $count -lt 5 ]; do
    echo "Count: $count"
    ((count++))
done

# read lines from a file
while IFS= read -r line; do
    echo "Line: $line"
done < input.txt

# infinite loop with break
while true; do
    read -p "Quit? (y/n) " ans
    [[ $ans == "y" ]] && break
done

# until: runs until condition is true (opposite of while)
n=0
until [ $n -ge 3 ]; do
    echo "n=$n"
    ((n++))
done

# while reading from a pipeline
seq 1 5 | while read n; do
    echo "got $n"
done

Break, continue et select

break sort de la boucle (break N sort N boucles imbriquées) ; continue saute à l'itération suivante. select crée un menu numéroté interactif depuis une liste — il boucle jusqu'à être cassé. Ces instructions de contrôle fonctionnent dans les boucles for, while et until. La forme break N est essentielle pour s'échapper de boucles profondément imbriquées mais devrait être utilisée avec parcimonie car elle peut rendre le code plus difficile à suivre.

bash
# break exits the loop
for i in {1..10}; do
    [[ $i -eq 5 ]] && break
    echo $i       # prints 1 2 3 4
done

# continue skips to next iteration
for i in {1..6}; do
    (( i % 2 == 0 )) && continue
    echo $i       # prints 1 3 5 (odd only)
done

# break out of nested loops
for i in 1 2 3; do
    for j in a b c; do
        [[ $j == "b" ]] && break 2   # break both loops
        echo "$i$j"
    done
done

# select menu
select opt in "Start" "Stop" "Quit"; do
    case $opt in
        Start) echo "starting" ;;
        Stop)  echo "stopping" ;;
        Quit)  break ;;
        *)     echo "invalid" ;;
    esac
done

Boucler sur des fichiers sûrement

Analyser la sortie de ls est un bug classique — les noms de fichiers avec espaces, newlines ou caractères spéciaux le cassent. Utilisez les globs shell (*.txt) directement dans les boucles for, et quotez la variable. Pour les recherches récursives ou complexes, utilisez find -print0 pipé vers « read -d '' » — les octets nuls sont le seul séparateur sûr pour les noms de fichiers. Activez nullglob pour que les globs non correspondants produisent une liste vide plutôt que le motif littéral.

bash
# BAD: breaks on filenames with spaces
for f in $(ls *.txt); do
    cat "$f"
done

# GOOD: glob expands safely
for f in *.txt; do
    [[ -f "$f" ]] || continue
    cat "$f"
done

# BEST: handle spaces, newlines, and no matches
shopt -s nullglob dotglob
for f in *.txt; do
    echo "Processing: $f"
done

# find with -print0 and while read -d ''
while IFS= read -r -d '' f; do
    echo "Found: $f"
done < <(find . -type f -name "*.txt" -print0)

# process files modified today
for f in $(find . -mtime -1 -print); do
    echo "Recent: $f"
done

Plages et génération de séquences

L'expansion d'accolades {a..b} génère des séquences au moment de l'analyse — rapide et intégré. Elle supporte les lettres, les nombres, le zero-padding et les pas ({start..end..step}). seq est une commande externe avec plus d'options de formatage (-f pour le style printf). L'expansion d'accolades crée aussi des arguments multiples : echo file{1..3}.{txt,log} génère 6 noms de fichiers. Préférez l'expansion d'accolades pour les plages simples ; utilisez seq quand vous avez besoin d'un formatage personnalisé.

bash
# brace expansion (not a loop, but generates lists)
echo {1..10}                 # 1 2 3 4 5 6 7 8 9 10
echo {a..e}                  # a b c d e
echo {01..10}                # zero-padded: 01 02 ... 10
echo file{1..3}.txt          # file1.txt file2.txt file3.txt

# seq command
seq 1 5                      # 1 2 3 4 5
seq 1 2 10                   # 1 3 5 7 9 (step 2)
seq -f "%03d" 1 5            # 001 002 003 004 005

# use seq in a for loop
for i in $(seq 1 5); do
    echo $i
done

# generate a list of dates
for d in $(seq -f "2024-01-%02g" 1 31); do
    echo "$d"
done
05

Fonctions

Définir et appeler des fonctions

Les fonctions dans Bash sont définies avec name() ou function name. Appelez-les par nom avec des arguments séparés par des espaces (pas de parenthèses). UTILISEZ TOUJOURS « local » pour les variables dans les fonctions pour éviter de polluer la portée globale — sans cela, les assignations fuient. Les fonctions Bash ne peuvent pas retourner de valeurs directement (return est un statut de sortie 0-255) ; pour retourner une chaîne, echoz-la et capturez avec $().

bash
# two equivalent syntaxes
greet() {
    echo "Hello, $1!"
}
function greet2() {
    echo "Hi, $1!"
}

# call with arguments (no parens)
greet "Alice"               # Hello, Alice!
greet2 "Bob"                # Hi, Bob!

# local variables (function-scoped)
counter() {
    local count=0
    ((count++))
    echo $count
}

# return a value via echo + command substitution
add() {
    echo $(($1 + $2))
}
result=$(add 3 4)           # 7

Arguments et paramètres

À l'intérieur d'une fonction, $1, $@, $# font référence aux arguments de la fonction, pas à ceux du script — ils masquent les paramètres au niveau du script. $FUNCNAME contient le nom de la fonction (utile pour le débogage). shift à l'intérieur d'une fonction n'affecte que les paramètres de la fonction. Pour passer les arguments du script à une fonction, utilisez func "$@". Ce masquage est la raison pour laquelle les fonctions sont des blocs réutilisables.

bash
# all positional params work inside functions
show_args() {
    echo "Function name: $FUNCNAME"
    echo "First arg:  $1"
    echo "Second arg: $2"
    echo "All args:   $@"
    echo "Arg count:  $#"
}

show_args a b c
# Function name: show_args
# First arg:  a
# Second arg: b
# All args:   a b c
# Arg count:  3

# pass all script args to a function
process "$@"

# shift within a function (local to it)
parse() {
    while (($#)); do
        echo "arg: $1"
        shift
    done
}

Valeurs de retour et statut de sortie

Le « return » de Bash ne définit qu'un statut de sortie (0-255), donc les fonctions servent aussi de tests booléens. Pour retourner une chaîne, echoz-la et capturez avec $(). Pour plusieurs valeurs, utilisez une nameref (local -n) — Bash 4.3+ — qui permet à la fonction d'assigner à une variable de l'appelant par nom. C'est la façon la plus propre de retourner des données complexes. Évitez d'utiliser des variables globales pour les valeurs de retour car cela rend les fonctions non-réentrantes.

bash
# return sets exit status (0-255), not a value
is_even() {
    if (($1 % 2 == 0)); then
        return 0    # true/success
    else
        return 1    # false/failure
    fi
}

# use as a condition
if is_even 4; then
    echo "4 is even"
fi

# chain with && and ||
is_even 4 && echo "yes" || echo "no"

# return a string via echo (capture with $())
to_upper() {
    echo "${1^^}"
}
UP=$(to_upper hello)        # HELLO

# return multiple values via global or nameref
set_pair() {
    local -n _r=$1
    _r=(10 20)
}
set_pair result
echo "${result[@]}"       # 10 20

Portée des variables

Les variables dans Bash sont globales par défaut — même assignées à l'intérieur d'une fonction ! C'est une source courante de bugs. Utilisez toujours « local » pour les variables internes aux fonctions. « declare -g » crée explicitement une globale depuis une fonction. La récursion fonctionne mais est lente dans Bash (chaque appel fork un subshell pour $()) ; utilisez-la avec parcimonie. L'exemple factoriel montre le motif : variables locales + appel récursif + arithmétique.

bash
# global by default
x=1
modify() {
    x=2          # changes the GLOBAL x!
    local y=3    # local to function
    echo "inside: x=$x y=$y"
}

modify
echo "outside: x=$x"       # x=2 (modified!)
echo "outside: y=$y"       # empty (y is local)

# declare -g for global inside a function
set_global() {
    declare -g NEW_VAR=42
}
set_global
echo $NEW_VAR              # 42

# recursion with local
factorial() {
    local n=$1
    if (( n <= 1 )); then
        echo 1
    else
        local prev=$(factorial $((n - 1)))
        echo $((n * prev))
    fi
}
echo $(factorial 5)        # 120

Bibliothèques et sourcing

source (ou .) exécute un fichier dans le shell courant, donc ses fonctions et variables deviennent disponibles — c'est ainsi que vous construisez des bibliothèques réutilisables. Un motif courant est un utils.sh avec des fonctions utilitaires que les scripts sourcent. Contrairement à l'exécution d'un script (qui tourne dans un subshell), le code sourcé peut modifier l'environnement de l'appelant. C'est aussi comment .bashrc et .bash_profile fonctionnent — ils sont sourcés au démarrage du shell.

bash
# utils.sh — a reusable library
#!/bin/bash
log() {
    echo "[$(date +%H:%M:%S)] $*" >&2
}

die() {
    log "ERROR: $*"
    exit 1
}

confirm() {
    read -p "$1 [y/N] " ans
    [[ $ans =~ ^[Yy]$ ]]
}

# main.sh — use the library
source ./utils.sh        # or . ./utils.sh

log "Starting script"
confirm "Continue?" || die "aborted"
log "Done"

# source vs execute
# source: runs in current shell (shares variables)
# ./script.sh: runs in a subshell (isolated)
06

Traitement de texte

grep — recherche de motif

grep trouve les lignes correspondant à un motif. -i ignore la casse, -v inverse, -n montre les numéros de ligne, -r récursion, -E utilise les regex étendues (comme +, |, {}). -A/-B/-C montrent les lignes de contexte autour des correspondances — inestimable pour comprendre les erreurs. Utilisez --include pour filtrer les types de fichiers pendant la recherche récursive. grep retourne le statut de sortie 0 si une correspondance est trouvée, ce qui le rend utile dans les conditions : if grep -q pattern file; then...

bash
# basic search
grep "error" app.log
grep -i "error" app.log          # case-insensitive
grep -v "debug" app.log          # invert (non-matching lines)
grep -c "error" app.log          # count matches
grep -n "error" app.log          # line numbers
grep -w "error" app.log          # whole word only

# recursive search
grep -rn "TODO" src/             # recursive + line numbers
grep -rl "TODO" src/             # files with matches only

# extended regex (or use egrep)
grep -E "error|warn|fatal" log
grep -E "^[0-9]{4}-" log         # lines starting with date

# context lines
grep -A 2 "error" log            # 2 lines after
grep -B 2 "error" log            # 2 lines before
grep -C 2 "error" log            # 2 lines before and after

# search multiple files
grep "function" *.js --include="*.js" -r .

sed — éditeur de flux

sed édite des flux de texte non interactivement. La commande s substitue ; g la rend globale. -i édite en place (testez toujours sans -i d'abord !). Le délimiteur peut être n'importe quel caractère — utilisez | ou # pour les chemins afin d'éviter d'échapper les slashes. sed traite ligne par ligne, donc les opérations multi-lignes nécessitent la commande N ou l'espace de hold. Pour les éditions complexes, awk ou perl peuvent être plus clairs. Quotez toujours les scripts sed pour empêcher l'expansion shell.

bash
# substitute (replace)
sed 's/old/new/' file.txt           # first occurrence per line
sed 's/old/new/g' file.txt          # all occurrences (global)
sed 's/old/new/3' file.txt          # 3rd occurrence only
sed 's/old/new/gi' file.txt         # global + case-insensitive

# in-place editing
sed -i 's/foo/bar/g' file.txt       # edit file in place
sed -i.bak 's/foo/bar/g' file.txt   # keep a backup

# delete lines
sed '/^#/d' file.txt                # delete comment lines
sed '/^$/d' file.txt                # delete blank lines
sed '5d' file.txt                   # delete line 5
sed '5,10d' file.txt                # delete lines 5-10

# print specific lines
sed -n '10,20p' file.txt            # print lines 10-20
sed -n '/pattern/p' file.txt        # print matching lines

# use a different delimiter for paths
sed 's|/usr/local|/opt|g' paths.txt

# multiple commands
sed -e 's/a/A/g' -e 's/b/B/g' file.txt

awk — traitement de colonnes

awk est un mini langage de programmation pour les données en colonnes. $1, $2... sont les champs ; $0 est la ligne entière ; $NF est le dernier champ. -F définit le séparateur d'entrée ; OFS définit la sortie. BEGIN s'exécute avant le traitement, END après. NR est le numéro d'enregistrement (ligne) ; NF est le compte de champs. awk est idéal pour le traitement CSV/TSV, l'analyse de logs et la génération de rapports — bien plus puissant que cut pour tout ce qui dépasse l'extraction simple.

bash
# print columns (default separator: whitespace)
awk '{print $1}' file.txt           # first column
awk '{print $1, $3}' file.txt       # columns 1 and 3
awk '{print $NF}' file.txt          # last column
awk -F: '{print $1}' /etc/passwd    # split on :

# filter and print
awk '$3 > 100' file.txt             # lines where col 3 > 100
awk '$1 == "ERROR" {print $2}' log  # col 2 of ERROR lines
awk 'NR == 5' file.txt              # 5th line only
awk 'NR >= 10 && NR <= 20' file.txt # lines 10-20

# BEGIN and END blocks
awk 'BEGIN {print "Start"} {sum += $1} END {print sum}' nums.txt

# field separator and output
awk -F, 'BEGIN {OFS="|"} {print $1, $2}' csv.txt

# count lines matching a pattern
awk '/error/' file.txt              # like grep
awk '/error/ {count++} END {print count}' log

# built-in variables: NR (row), NF (fields), FS, OFS
awk '{print NR, NF, $0}' file.txt

cut, tr, sort et uniq

cut est une extraction simple de champ/caractère — rapide mais limitée (pas de support de quoting). tr traduit les caractères (génial pour la conversion de casse ou l'échange de délimiteur) et -d supprime. sort ordonne les lignes (-n numérique, -r inverse, -k champ). uniq ne supprime que les doublons ADJACENTS, donc pipez toujours à travers sort d'abord. uniq -c avec sort -rn est le motif classique pour l'analyse de fréquence : « sort | uniq -c | sort -rn » montre les lignes les plus courantes.

bash
# cut: extract fields or characters
cut -d: -f1 /etc/passwd            # field 1, delimiter :
cut -c1-5 file.txt                 # characters 1-5
cut -d, -f2,3 data.csv             # fields 2 and 3

# tr: translate or delete characters
echo "Hello" | tr 'a-z' 'A-Z'      # HELLO
echo "a,b,c" | tr ',' ' '          # a b c
echo "hello" | tr -d 'l'           # heo (delete)
tr -s ' ' < file.txt               # squeeze repeated spaces

# sort lines
sort file.txt                      # alphabetical
sort -n nums.txt                   # numeric
sort -rn nums.txt                  # reverse numeric
sort -t: -k3 -n /etc/passwd        # by 3rd field, numeric
sort -u file.txt                   # sort + unique

# uniq: filter or count adjacent duplicates
sort file.txt | uniq               # unique lines
sort file.txt | uniq -c            # count occurrences
sort file.txt | uniq -d            # only duplicates
sort file.txt | uniq -u            # only unique lines

Pipes et redirection

Les pipes connectent stdout à stdin, construisant des pipelines puissants. > redirige stdout (>> ajoute), 2> redirige stderr, &> capture les deux. Les here-docs (<<EOF) alimentent des chaînes multi-lignes ; quoter le délimiteur (« EOF ») désactive l'expansion de variables. La substitution de processus <(cmd) traite la sortie d'une commande comme un fichier temporaire — essentiel pour alimenter des boucles while sans subshell, et pour des commandes comme diff qui attendent des fichiers.

bash
# pipe: chain commands (stdout of one -> stdin of next)
cat file | grep "x" | sort | uniq -c | sort -rn | head

# redirect stdout
echo "hello" > file.txt            # overwrite
echo "world" >> file.txt           # append

# redirect stderr
command 2> error.log               # stderr to file
command 2>&1                       # stderr to stdout
command > all.log 2>&1             # both to same file
command &> all.log                 # both (Bash shortcut)

# stdin from file
command < input.txt

# here-string
grep "x" <<< "some text with x"

# here-document (multi-line input)
cat << EOF
Line 1
Line 2 with $VAR expansion
EOF

# quoted delimiter disables expansion
cat << 'EOF'
Literal $VAR no expansion
EOF

# process substitution
diff <(ls dir1) <(ls dir2)         # compare outputs
while read line; do echo "$line"; done < <(grep x file)
07

Opérations sur fichiers et répertoires

find — recherche de fichiers

find est l'outil de recherche de fichiers le plus puissant. -name correspond aux globs (utilisez -iname pour insensible à la casse) ; -type filtre par f/d/l ; -mtime/-mmin filtrent par temps de modification (- = dans, + = plus ancien que) ; -size filtre par taille. -exec exécute une commande sur chaque résultat ; {} est le nom du fichier, \; exécute par fichier, + les groupe. -delete est plus sûr que -exec rm pour la suppression. Testez toujours find sans -delete d'abord !

bash
# search by name
find . -name "*.js"
find . -iname "*.JS"               # case-insensitive
find / -name "*.conf" 2>/dev/null  # suppress permission errors

# search by type
find . -type f -name "*.txt"       # files only
find . -type d -name src           # directories only
find . -type l                     # symlinks

# search by time and size
find . -mtime -7                   # modified < 7 days ago
find . -mtime +30                  # modified > 30 days ago
find . -mmin -60                   # modified < 60 min ago
find . -size +10M                  # larger than 10MB
find . -size -1k                   # smaller than 1KB
find . -empty                      # empty files/dirs

# act on results (-exec)
find . -name "*.log" -exec rm {} \;
find . -name "*.js" -exec wc -l {} +
find . -type f -exec chmod 644 {} \;

# safe deletion with -delete
find /tmp -type f -mtime +7 -delete

Permissions et propriété

Les permissions sont trois triplets : propriétaire, groupe, autres. Chaque chiffre est r(4)+w(2)+x(1), donc 755 = rwxr-xr-x. chmod change les permissions ; chown change le propriétaire/groupe. La notation symbolique (u+x, g-w) est plus claire pour les changements incrémentaux. umask définit les permissions par défaut pour les nouveaux fichiers (soustrait de 666 pour les fichiers, 777 pour les répertoires). Pour les serveurs web, 755 pour les répertoires et 644 pour les fichiers est standard ; n'utilisez jamais 777 en production.

bash
# view permissions
ls -l file.txt
# -rw-r--r-- 1 user group 1024 Jan 1 10:00 file.txt
#  ^^^ ^^^ ^^^
#  owner group others

# chmod: change permissions
chmod 755 script.sh        # rwxr-xr-x
chmod 644 file.txt         # rw-r--r--
chmod +x script.sh         # add execute for all
chmod u+x,g-w file.txt     # symbolic: user +x, group -w
chmod -R 755 directory/    # recursive

# chown: change owner
chown alice file.txt
chown alice:developers file.txt
chown -R alice:developers project/

# umask: default permissions for new files
umask                      # show current (e.g. 022)
umask 077                  # new files: 600, dirs: 700

# numeric permission reference:
# 7=rwx 6=rw 5=rx 4=r 3=wx 2=w 1=x 0=none

Copier, déplacer, supprimer et lier

cp -r copie les répertoires récursivement ; -i demande avant d'écraser (plus sûr) ; -p préserve les timestamps et permissions. mv est à la fois déplacement et renommage. rm -rf est dangereux — il supprime récursivement sans demander ; vérifiez toujours le chemin. Les hard links pointent vers le même inode (même fichier, ne peut pas traverser les systèmes de fichiers ou lier des répertoires) ; les symlinks sont des références de chemin (peuvent lier n'importe quoi, mais cassent si la cible bouge). Utilisez ln -s pour les symlinks.

bash
# copy
cp file.txt backup.txt
cp -r src/ dest/            # recursive (directories)
cp -i file.txt dest/        # interactive (prompt before overwrite)
cp -u file.txt dest/        # update (only if newer)
cp -p file.txt dest/        # preserve attributes

# move / rename
mv old.txt new.txt
mv file.txt /other/dir/
mv -i file.txt dest/        # prompt before overwrite

# remove
rm file.txt
rm -r directory/            # recursive
rm -f file.txt              # force (no error if missing)
rm -rf node_modules/        # force + recursive (DANGER)

# links
ln target hardlink          # hard link (same inode)
ln -s target symlink        # symbolic link (path reference)
ln -sf target symlink       # force recreate symlink

# view link target
readlink symlink
readlink -f symlink         # canonical absolute path

Archives et compression

tar regroupe les fichiers en une archive ; gzip/bzip2/xz la compressent. Les options : c=create, x=extract, t=list, f=file, z=gzip, j=bzip2, J=xz, v=verbose. .tar.gz est le standard Unix ; .zip est courant sur Windows. bzip2 compresse mieux que gzip mais plus lentement ; xz est le meilleur mais le plus lent. Utilisez -C pour extraire vers un répertoire spécifique. L'option -k préserve l'original à la compression.

bash
# tar + gzip (most common)
tar -czf archive.tar.gz dir/      # create
tar -xzf archive.tar.gz           # extract
tar -xzf archive.tar.gz -C /opt/  # extract to /opt
tar -tzf archive.tar.gz           # list contents

# tar + bzip2 (better compression)
tar -cjf archive.tar.bz2 dir/
tar -xjf archive.tar.bz2

# zip / unzip
zip -r archive.zip dir/
unzip archive.zip
unzip archive.zip -d /target

# gzip / gunzip (single files)
gzip file.txt           # -> file.txt.gz
gunzip file.txt.gz      # -> file.txt
gzip -k file.txt        # keep original

# xz (best compression)
tar -cJf archive.tar.xz dir/
tar -xJf archive.tar.xz

# mnemonic: eXtract, Create, List, File, gZip, bZip2, xz

Contenu de fichier et comparaison

cat vide tout le fichier ; head/tail montrent les extrémités ; tail -f streame les mises à jour en direct (essentiel pour les logs). less est un pager interactif avec recherche (/) et navigation. diff compare les fichiers ; -u produit le format unifié utilisé par patch. comm compare deux fichiers triés et montre les lignes uniques à chacun ou communes aux deux — utile pour comparer des listes. Triez toujours les entrées avant comm, car il nécessite des fichiers triés.

bash
# view file contents
cat file.txt               # entire file
head -n 20 file.txt        # first 20 lines
tail -n 20 file.txt        # last 20 lines
tail -f app.log            # follow (live updates)
less file.txt              # pager (q to quit, / to search)

# line/word/char count
wc file.txt                # lines words bytes
wc -l file.txt             # lines only
wc -w file.txt             # words only

# compare files
diff file1.txt file2.txt
diff -u file1.txt file2.txt    # unified format (for patches)
diff -r dir1/ dir2/            # recursive

# patch
diff -u old new > change.patch
patch < change.patch
patch -R < change.patch        # reverse

# find duplicate lines across files
comm -12 <(sort a) <(sort b)   # lines in both
comm -23 <(sort a) <(sort b)   # only in a
comm -13 <(sort a) <(sort b)   # only in b
08

Gestion de processus et signaux

Jobs en arrière-plan et contrôle de jobs

Ajouter & exécute une commande en arrière-plan, retournant immédiatement. jobs liste les jobs actifs ; fg/bg les déplacent entre premier plan et arrière-plan. Ctrl+Z suspend le job de premier plan (envoie SIGTSTP). wait bloque jusqu'à ce que les jobs en arrière-plan terminent — essentiel pour les scripts qui lancent du travail parallèle. disown retire un job de la table de jobs du shell pour qu'il continue à tourner après votre déconnexion (contrairement à nohup, il fonctionne sur des jobs déjà en cours).

bash
# run in background (append &)
sleep 100 &
# [1] 12345   (job 1, PID 12345)

# list jobs
jobs
jobs -l            # with PIDs

# bring to foreground / send to background
fg                 # foreground the current job
fg %1              # foreground job 1
bg %2              # background job 2 (continue running)

# suspend a running foreground job: Ctrl+Z
# then resume in background:
bg

# wait for background jobs
sleep 5 &
wait                # wait for all background jobs
wait $!             # wait for the last background job
wait 12345          # wait for specific PID

# disown: detach from shell (survives logout)
long_task &
disown %1

kill et signaux

kill envoie des signaux aux processus. SIGTERM (par défaut) demande au processus de sortir proprement — il peut nettoyer. SIGKILL (-9) est forcé et immédiat ; le processus ne peut pas l'attraper ou l'ignorer, donc il peut laisser des ressources dans un mauvais état. Essayez toujours SIGTERM d'abord, attendez, puis SIGKILL seulement si nécessaire. killall/pkill tuent par nom. pkill -f correspond à la ligne de commande complète (plus flexible). Utilisez kill -l pour lister tous les noms de signaux.

bash
# send signals to processes
kill 12345              # SIGTERM (graceful, default)
kill -15 12345          # SIGTERM explicitly
kill -9 12345           # SIGKILL (force, cannot be caught)
kill -HUP 12345         # SIGHUP (reload config)

# kill by name
killall nginx           # kill all nginx processes
pkill -f "python app.py"  # match full command line

# common signals
# SIGTERM (15) - graceful termination (default)
# SIGKILL (9)  - force kill (cannot be caught/ignored)
# SIGINT (2)   - interrupt (Ctrl+C)
# SIGHUP (1)   - hangup (often reload config)
# SIGSTOP (19) - pause (cannot be caught)
# SIGCONT (18) - resume
# SIGTSTP (20) - terminal stop (Ctrl+Z)

# list all signals
kill -l

# priority: try SIGTERM first, SIGKILL as last resort
kill 12345; sleep 2; kill -9 12345 2>/dev/null

trap — gestion des signaux

trap enregistre des commandes à exécuter quand le script reçoit un signal — essentiel pour le nettoyage. Le pseudo-signal EXIT se déclenche à toute sortie (normale, erreur, ou tué), ce qui le rend parfait pour supprimer les fichiers temporaires. Définissez toujours les traps avant de créer les ressources qu'ils nettoient. Motif courant : trap cleanup EXIT INT TERM. trap '' SIGNAL l'ignore ; trap - SIGNAL restaure le défaut. C'est ainsi que les scripts robustes garantissent le nettoyage même interrompus.

bash
# clean up on exit (EXIT signal)
trap 'rm -f $TMPFILE' EXIT
TMPFILE=$(mktemp)
echo "working with $TMPFILE"
# when script exits (normally or via error), TMPFILE is removed

# handle Ctrl+C (SIGINT)
trap 'echo "Caught Ctrl+C, exiting"; exit 130' INT

# handle multiple signals
cleanup() {
    echo "Cleaning up..."
    rm -rf "$WORKDIR"
    exit
}
trap cleanup INT TERM EXIT

# ignore a signal
trap '' INT              # ignore Ctrl+C
trap - INT               # restore default behavior

# reload on SIGHUP
trap 'echo "reloading config"; source config.sh' HUP

# list current traps
trap -p

Inspection de processus

ps montre un instantané des processus ; top/htop en temps réel. pstree affiche la hiérarchie parent-enfant. pgrep trouve les PIDs par nom (plus sûr que ps|grep). lsof -i :PORT trouve quel processus utilise un port — essentiel pour déboguer les erreurs « port en utilisation ». ss (statistiques de socket) est le remplaçant moderne de netstat. Le motif ps aux | grep est omniprésent mais pgrep est plus propre. Utilisez --sort pour trouver les gourmands en ressources.

bash
# list processes
ps aux                   # all processes (BSD style)
ps -ef                   # all processes (System V style)
ps -ef | grep nginx      # find nginx processes

# interactive viewer (like Task Manager)
top                      # real-time, all processes
htop                     # nicer interactive viewer (install separately)

# process tree
pstree                   # tree view
pstree -p                # with PIDs

# find process by name/port
pgrep -f "node app"      # print PIDs matching
pgrep -fl "node"         # full command line

# who's listening on a port
lsof -i :8080            # process using port 8080
ss -tlnp                 # all listening TCP ports
netstat -tlnp            # legacy alternative

# process resource usage
top -b -n 1 | head       # snapshot
ps aux --sort=-%mem | head  # top memory users

nohup, disown et tmux

nohup fait qu'un processus ignore SIGHUP pour qu'il survive à la déconnexion — la sortie va vers nohup.out. disown réalise la même chose pour un job en arrière-plan déjà en cours. setsid démarre un processus dans une nouvelle session, le détachant complètement. Pour le travail interactif long, tmux ou screen sont meilleurs : ils gardent une session terminal complète en vie à laquelle vous pouvez vous rattacher plus tard, donc même les éditeurs de texte survivent à la déconnexion. C'est ainsi que les sysadmins gèrent les serveurs distants.

bash
# nohup: run immune to hangups (survives logout)
nohup ./long_script.sh &
# output goes to nohup.out by default
nohup ./server.sh > server.log 2>&1 &

# disown: detach an already-running job
./long_task &
disown %1

# setsid: start in a new session
setsid ./daemon.sh &

# tmux: terminal multiplexer (persistent sessions)
tmux                    # start new session
tmux new -s work        # named session
tmux ls                 # list sessions
tmux attach -t work     # reattach
# inside tmux: Ctrl+B then D to detach
# processes keep running after detach

# screen: alternative to tmux
screen -S work
screen -ls
screen -r work

# check if a process is running
pgrep -x nginx && echo "running" || echo "stopped"
09

Scripting et sujets avancés

Shebang et structure de script

Le shebang (#!) indique au noyau quel interpréteur utiliser. #!/usr/bin/env bash est le plus portable. Un script bien structuré commence par « set -euo pipefail » pour la sécurité, définit usage(), et analyse les arguments avec getopts (pour les flags courts) ou une boucle manuelle (pour les flags longs). OPTIND suit le prochain argument ; shift passé les options analysées pour que $1 soit le premier argument positionnel. Cette structure rend les scripts robustes et conviviaux.

bash
#!/usr/bin/env bash
# ^ portable shebang (finds bash via PATH)
# #!/bin/bash          # absolute path (common)
# #!/usr/bin/bash       # on some systems
# #!/bin/sh             # POSIX sh (most portable)

set -euo pipefail        # strict mode (see below)

# script metadata
readonly PROGNAME=$(basename "$0")
readonly VERSION="1.0.0"

usage() {
    cat << EOF
Usage: $PROGNAME [OPTIONS] FILE
Options:
  -h, --help     Show this help
  -v, --version  Show version
  -o OUTPUT      Output file
EOF
}

# parse options with getopts
while getopts "hvo:" opt; do
    case $opt in
        h) usage; exit 0 ;;
        v) echo $VERSION; exit 0 ;;
        o) output=$OPTARG ;;
        *) usage; exit 1 ;;
    esac
done
shift $((OPTIND - 1))

Mode strict (set -euo pipefail)

set -e sort immédiatement si une commande échoue (retourne non-zéro) — attrape les erreurs tôt. set -u traite les références à des variables non définies comme des erreurs. pipefail fait qu'un pipeline retourne non-zéro si N'IMPORTE QUELLE commande échoue (par défaut, seul le statut de la dernière commande compte). Ensemble ils attrapent la plupart des bugs. Utilisez « cmd || true » pour autoriser les échecs attendus, et « if ! cmd » pour les vérifications explicites. Certaines commandes (grep, test) retournent non-zéro légitimement, donc enveloppez-les.

bash
# the 'strict mode' trio
set -e             # exit on error (any non-zero status)
set -u             # error on unset variable
set -o pipefail    # pipeline fails if any command fails
# combined: set -euo pipefail

# example: without -e, errors are silently ignored
mkdir /root/nope   # fails (permission denied)
echo "still running"  # this runs anyway!

# with set -e, the script exits at the failure
set -e
mkdir /root/nope   # script exits here
echo "never reached"

# handle expected failures
set -e
if ! grep -q "x" file; then
    echo "not found"   # grep returning 1 won't kill script
fi

# temporarily disable -e
set +e
risky_command
set -e

# || true to ignore a command's failure
mkdir -p dir || true

Techniques de débogage

set -x (xtrace) imprime chaque commande avant exécution — l'outil de débogage n°1. PS4 personnalise le prompt (ajouter les numéros de ligne aide à localiser les problèmes). bash -n vérifie la syntaxe sans exécuter. trap ERR exécute une commande quand une erreur survient, parfait pour logger où les choses ont mal tourné. Pour les scripts complexes, enveloppez les fonctions suspectes avec set -x/+x pour tracer juste cette partie. Combinez avec set -e pour vous arrêter à la première erreur et inspecter.

bash
# trace execution: print each command before running
set -x              # turn on
set +x              # turn off
bash -x script.sh   # run with tracing from the start

# verbose: print input lines as read
set -v

# combine: -xv for full detail
bash -xv script.sh

# print commands with line numbers
export PS4='+ ${BASH_SOURCE}:${LINENO}: '
set -x

# check syntax without executing
bash -n script.sh   # syntax check only

# step through interactively
bash -x script.sh 2>&1 | less

# debug a specific function
debug_func() {
    set -x
    actual_func "$@"
    set +x
}

# use trap ERR to catch errors
trap 'echo "Error on line $LINENO" >&2' ERR

Arithmétique et maths

Bash ne fait que de l'arithmétique entière avec $(( )). Pour le flottant, pipez les expressions à bc (scale=N définit les décimales) ou utilisez awk. bc -l charge la bibliothèque math (sqrt, sin, cos, etc.). $RANDOM donne un entier pseudo-aléatoire 0-32767 ; pour de l'aléatoire cryptographique utilisez /dev/urandom. La commande (( )) supporte +=, -=, *=, /=, %= et ++/-- comme en C. Pour des maths sérieuses, Python ou awk sont de meilleurs choix.

bash
# integer arithmetic
echo $((5 + 3))         # 8
echo $((10 / 3))        # 3 (integer division)
echo $((10 % 3))        # 1 (modulo)
echo $((2 ** 8))        # 256 (exponent)

# floating point with bc
echo "scale=2; 10/3" | bc       # 3.33
echo "3.14 * 2" | bc            # 6.28
result=$(echo "scale=4; $a / $b" | bc)

# floating point with awk
awk 'BEGIN {printf "%.2f\n", 10/3}'   # 3.33
awk "BEGIN {print $a * $b}"

# math functions with bc -l
echo "scale=4; s(0)" | bc -l    # 0 (sine, radians)
echo "scale=4; sqrt(16)" | bc -l # 4.0000

# random numbers
echo $RANDOM              # 0-32767
echo $((RANDOM % 100))    # 0-99
head -c 4 /dev/urandom | od -An -tu4   # 32-bit random

# increment/decrement
n=5
((n++)); echo $n          # 6
((n+=10)); echo $n        # 16

Regex et pattern matching

Bash a trois systèmes de motifs : les globs (*.txt, ?, [abc]) pour les noms de fichiers, les globs étendus (extglob : !(), @(), *()) pour la correspondance plus complexe, et les regex ERE (=~ dans [[ ]]). =~ capture les groupes dans BASH_REMATCH (index 0 = correspondance complète, 1+ = groupes). Les regex utilisent la syntaxe ERE (comme grep -E). Activez extglob pour la négation et l'alternance puissantes dans les motifs de noms de fichiers. Les regex sont l'une des fonctionnalités les plus utiles de Bash pour la validation d'entrée.

bash
# glob patterns (filename matching)
*.txt          # any .txt file
file?.txt      # single char: file1.txt, fileA.txt
file[0-9].txt  # digit: file0.txt ... file9.txt
file[!0-9].txt # NOT a digit

# extended globs (shopt -s extglob)
shopt -s extglob
echo !(*.bak)       # everything except .bak files
echo @(*.jpg|*.png) # jpg or png
echo *(foo)         # zero or more "foo"

# regex with =~ in [[ ]]
if [[ $email =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
    echo "valid email"
fi

# capture groups with BASH_REMATCH
if [[ $date =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})$ ]]; then
    echo "Year: ${BASH_REMATCH[1]}"
    echo "Month: ${BASH_REMATCH[2]}"
    echo "Day: ${BASH_REMATCH[3]}"
fi

# case-insensitive regex
shopt -s nocasematch
if [[ $s =~ ^hello ]]; then echo "starts with hello"; fi
10

Plongée dans grep

Motifs grep de base

grep cherche du texte en utilisant des motifs. -i ignore la casse ; -w correspond aux mots entiers (empêche de correspondre à « errors » en cherchant « error ») ; -v inverse (lignes non correspondantes) ; -c compte ; -n montre les numéros de ligne ; -l liste uniquement les noms de fichiers avec correspondances ; -h supprime le préfixe de nom de fichier lors de la recherche de plusieurs fichiers. Par défaut, grep utilise les Expressions Régulières de Base (BRE) où les métacaractères nécessitent un échappement backslash. Utilisez -E pour les Regex Étendues (syntaxe plus propre) ou -F pour les chaînes fixes (pas de regex, plus rapide pour les recherches littérales).

bash
# Basic search (literal string)
grep "error" logfile.txt

# Case-insensitive
grep -i "error" logfile.txt

# Whole word match
grep -w "error" logfile.txt

# Invert match (lines NOT matching)
grep -v "debug" logfile.txt

# Count matches
grep -c "error" logfile.txt

# Show line numbers
grep -n "error" logfile.txt

# Multiple files with filename prefix
grep "error" *.log

# Suppress filename prefix (single file behavior)
grep -h "error" *.log

# Only show filenames with matches
grep -l "error" *.log

grep avec regex

grep -E (ou egrep) utilise les Expressions Régulières Étendues avec une syntaxe plus propre : +, ?, |, () fonctionnent sans échappement. ^ et $ ancrent au début/fin de ligne. [] définit les classes de caractères ; {} spécifie la répétition. -P active les regex compatibles Perl (PCRE) avec des fonctionnalités comme \d, \w, les lookaheads — mais c'est spécifique à GNU et non portable. Pour les regex complexes, envisagez ripgrep (rg) qui est plus rapide et utilise une syntaxe type PCRE par défaut. Quotez toujours les motifs pour empêcher l'interprétation shell des caractères spéciaux comme $ et *.

bash
# Extended regex (-E or egrep)
grep -E "error|warning|fatal" logfile.txt

# Anchors: ^ start, $ end
grep -E "^ERROR:" logfile.txt      # lines starting with ERROR:
grep -E "completed$" logfile.txt   # lines ending with completed

# Character classes
grep -E "[0-9]{4}-[0-9]{2}-[0-9]{2}" dates.txt  # date format
grep -E "[A-Z][a-z]+" names.txt                  # Capitalized words

# Quantifiers
grep -E "ab+c" file.txt    # one or more b's
grep -E "ab*c" file.txt    # zero or more b's
grep -E "ab?c" file.txt    # zero or one b

# Alternation and grouping
grep -E "(cat|dog) food" file.txt

# PCRE (-P, GNU grep only)
grep -P "\d{3}-\d{4}" phones.txt  # \d for digits

Contexte et contrôle de sortie grep

Les options de contexte (-B, -A, -C) montrent les lignes environnantes, essentielles pour comprendre les entrées de log. -o ne sort que la portion correspondante (utile pour extraire les URLs, nombres, etc.). --color surligne les correspondances dans le terminal. -r cherche récursivement (exclut les symlinks) ; -R suit les symlinks. --include/--exclude/--exclude-dir filtrent quels fichiers chercher — inestimable pour les grandes bases de code (excluez toujours node_modules, .git, vendor). -a force les fichiers binaires à être traités comme du texte. Pour la recherche de code, ripgrep (rg) est une alternative moderne et plus rapide avec des défauts sensés.

bash
# Show context: lines before/after match
grep -B 2 "error" log.txt    # 2 lines Before
grep -A 3 "error" log.txt    # 3 lines After
grep -C 2 "error" log.txt    # 2 lines Context (both)

# Only show matched part (not full line)
grep -o "https?://[^ ]+" urls.txt

# Color highlight matches
grep --color=auto "error" log.txt

# Recursive search
grep -r "TODO" ./src/
grep -R "TODO" ./src/        # follows symlinks

# Include/exclude file patterns
grep -r --include="*.py" "import" .
grep -r --exclude="*.test.js" "TODO" .
grep -r --exclude-dir=node_modules "TODO" .

# Binary files
grep -a "pattern" binary_file  # treat as text

grep avec stdin et pipelines

grep est plus puissant dans les pipelines. L'astuce [n]ginx empêche grep de correspondre à son propre processus : le crochet fait que le motif ne correspond pas à la chaîne littérale « grep » dans la liste des processus. zgrep cherche les fichiers compressés sans décompression manuelle. pgrep est un chercheur de processus spécialisé (mieux que ps | grep). grep -rl liste les fichiers contenant un motif ; piper vers xargs grep cherche dans ces fichiers un autre motif — une technique courante d'archéologie de code. Pour la recherche de code interactive, utilisez ripgrep ou ack qui sont conçus pour le code source.

bash
# Search command output
ps aux | grep nginx
ps aux | grep "[n]ginx"  # trick: prevents matching grep itself

# Search compressed logs
zcat log.gz | grep "error"
zgrep "error" log.gz     # direct

# Chain multiple greps
cat log.txt | grep "error" | grep -v "timeout" | grep -c

# Extract and filter
grep -oE "[0-9.]+" response.txt | sort -n | uniq

# Find processes excluding grep
pgrep -f "node server.js"  # better than ps | grep

# Search command history
history | grep "git rebase"

# Find files containing pattern, then search more
grep -rl "config" . | xargs grep "database"

Statut de sortie grep et scripting

Le statut de sortie de grep le rend idéal pour le scripting : 0 (correspondance trouvée), 1 (pas de correspondance), 2 (erreur). -q (silencieux) supprime la sortie pour les vérifications conditionnelles pures. Dans les scripts set -e, grep retournant 1 (pas de correspondance) sortirait le script — utilisez « || true » pour empêcher cela. Le motif while-read traite les lignes correspondantes une à la fois. grep -c retourne un compte (0 si pas de correspondance), que vous pouvez comparer numériquement. Cette capacité de scripting fait de grep un bloc de construction pour la surveillance de logs, les scripts de validation et les vérifications CI/CD.

bash
# grep returns exit codes for scripting
# 0 = match found, 1 = no match, 2 = error

if grep -q "error" /var/log/syslog; then
    echo "Errors found!"
    # send alert, etc.
fi

# Silent check (-q), no output
grep -q "^$" file.txt && echo "has blank lines"

# Use in while loop
grep "pattern" file.txt | while read -r line; do
    echo "Found: $line"
done

# Count and branch
errors=$(grep -c "ERROR" log.txt)
if [ "$errors" -gt 10 ]; then
    echo "Too many errors: $errors"
fi

# grep with set -e (exits on non-zero)
set -e
grep "missing" file.txt || true  # prevent exit
11

Plongée dans sed

Substitution sed

sed (éditeur de flux) transforme le texte ligne par ligne. La commande s substitue le texte : s/pattern/remplacement/options. g (global) remplace toutes les occurrences par ligne ; sans lui, seule la première correspondance est remplacée. Vous pouvez restreindre les opérations à des lignes spécifiques (par numéro, plage, ou motif). -i édite les fichiers en place (dangereux — testez toujours sans -i d'abord, ou utilisez -i.bak pour une sauvegarde). sed traite chaque ligne indépendamment. Le délimiteur n'est pas obligé d'être / — utilisez s|old|new|g quand les motifs contiennent des slashes (par ex., chemins de fichiers).

bash
# Basic substitution (first occurrence per line)
sed 's/old/new/' file.txt

# Replace all occurrences
sed 's/old/new/g' file.txt

# Case-insensitive (GNU sed)
sed 's/old/new/gi' file.txt

# Replace Nth occurrence
sed 's/old/new/2' file.txt  # 2nd occurrence only

# Only on lines matching pattern
sed '/error/s/old/new/' file.txt

# Only on specific line numbers
sed '5s/old/new/' file.txt      # line 5 only
sed '5,10s/old/new/g' file.txt  # lines 5-10
sed '$s/old/new/' file.txt      # last line

# In-place edit (modifies file)
sed -i 's/old/new/g' file.txt
sed -i.bak 's/old/new/g' file.txt  # keeps backup

sed avec regex et groupes de capture

sed -E (ou -r) utilise les Expressions Régulières Étendues avec une syntaxe plus propre (pas de backslash avant parenthèses/accolades). Les groupes de capture sont référencés comme \1, \2, etc. dans le remplacement. & représente la correspondance complète. L'exemple de reformatage de date montre la puissance : capturer année, mois, jour séparément et réarranger. Plusieurs commandes peuvent être chaînées avec des points-virgules (s/.../.../;s/.../.../). Échappez toujours les caractères spéciaux dans les motifs (., *, [, etc.) et échappez / dans les remplacements (ou utilisez un autre délimiteur).

bash
# Extended regex (-E or -r)
sed -E 's/[0-9]+/NUMBER/g' file.txt

# Capture groups with \1, \2 (BRE)
sed 's/\(foo\)\(bar\)/\2\1/' file.txt  # swap: foobar -> barfoo

# Capture groups with () (ERE, cleaner)
sed -E 's/(foo)(bar)/\2\1/' file.txt

# Reformat date: 2025-01-15 -> 15/01/2025
sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3\/\2\/\1/' file.txt

# Add prefix to lines
sed 's/^/>> /' file.txt

# Add suffix
sed 's/$/ <<</' file.txt

# Remove leading/trailing whitespace
sed 's/^[ \t]*//;s/[ \t]*$//' file.txt

# Use & for full match
sed 's/[0-9]\{3\}/[&]/g' file.txt  # wrap 3 digits in brackets

sed delete et print

La commande d supprime les lignes ; p imprime les lignes. Avec -n (pas d'auto-impression), seules les commandes p explicites produisent une sortie — cela transforme sed en imprimante sélective (comme grep). Supprimer les lignes vides (sed '/^$/d') est un nettoyage courant. sed -n '5,10p' est équivalent à sed -n '5,10p' ou aux combinaisons head/tail. La syntaxe ~ (0~3p) imprime chaque 3e ligne (extension GNU). Rappelez-vous : sans -n, sed imprime chaque ligne (éventuellement modifiée) ; avec -n, il n'imprime rien sauf si vous utilisez p. Cette dualité fait de sed à la fois un éditeur et un filtre.

bash
# Delete lines
sed '5d' file.txt              # delete line 5
sed '5,10d' file.txt           # delete lines 5-10
sed '/pattern/d' file.txt      # delete matching lines
sed '/^$/d' file.txt           # delete blank lines
sed '/^[ \t]*$/d' file.txt    # delete whitespace-only lines
sed '$d' file.txt              # delete last line

# Print only specific lines (like head/tail)
sed -n '5p' file.txt           # print line 5 only
sed -n '5,10p' file.txt        # print lines 5-10
sed -n '/pattern/p' file.txt   # print matching lines (like grep)
sed -n '5,${/pattern/p}' file.txt  # from line 5 to end

# Print with line numbers
sed '=' file.txt | sed 'N;s/\n/\t/'

# Print every Nth line
sed -n '0~3p' file.txt  # every 3rd line (GNU)

sed multi-ligne et espace de hold

L'espace de hold de sed permet les opérations multi-lignes — l'espace de motif (ligne courante) peut être sauvegardé dans l'espace de hold et récupéré plus tard. h/H copie/ajoute vers hold ; g/G copie/ajoute depuis hold ; x les échange. L'astuce de jointure de lignes (:a;N;$!ba;s/\n/ /g) lit tout le fichier dans l'espace de motif puis remplace les newlines. N ajoute la ligne suivante à l'espace de motif. Ces fonctionnalités avancées rendent sed Turing-complet mais aussi très cryptique. Pour les transformations multi-lignes complexes, awk ou Perl sont plus lisibles. Utilisez sed pour les éditions simples basées sur les lignes ; utilisez awk pour la logique basée sur les champs ou multi-lignes.

bash
# Join lines (replace newline with space)
sed ':a;N;$!ba;s/\n/ /g' file.txt

# Join every 2 lines
sed 'N;s/\n/ /' file.txt

# Print paragraph (blocks separated by blank lines)
sed '/./{H;d;};x;s/\n/ /g' file.txt

# Reverse line order (tac alternative)
sed '1!G;h;$!d' file.txt

# Double-space a file
sed 'G' file.txt

# Remove last line of each paragraph
sed -n '/^$/{p;h;};/./{H;};/^$/{x;s/\n.*//;p;}' file.txt

# Hold space (h,H,g,G,x) stores lines between cycles
# h: copy pattern space to hold
# H: append pattern space to hold
# g: copy hold to pattern space
# G: append hold to pattern space
# x: exchange pattern and hold

sed dans les pipelines et scripts

sed excelle dans les pipelines pour la transformation de texte. À la substitution de variables, échappez les caractères spéciaux (& et \) pour empêcher l'interprétation. Plusieurs options -e appliquent plusieurs commandes en séquence. -f lit les commandes depuis un fichier (utile pour les scripts complexes). La conversion CSV-to-TSV montre un usage pratique. Pour l'édition de fichiers de config dans les scripts, validez toujours le changement (grep après sed) et envisagez d'utiliser des outils dédiés (jq pour JSON, yq pour YAML). La force de sed est les éditions simples, rapides, basées sur les lignes — c'est un pilier du traitement de texte Unix aux côtés de grep et awk.

bash
# Clean up output in pipeline
cat messy.txt | sed 's/[ \t]*$//' | sed '/^$/d' | sort

# Extract and transform
echo "name=John;age=30" | sed 's/;/\n/g' | sed 's/=/: /'

# Modify config files in scripts
sed -i "s/PORT=8080/PORT=$NEW_PORT/" config.env

# Escape special chars for safe substitution
ESCAPED=$(printf '%s\n' "$URL" | sed 's/[&\]/\\&/g')
sed "s|URL|$ESCAPED|g" template.txt

# Multiple substitutions
sed -e 's/foo/bar/g' -e 's/baz/qux/g' file.txt

# Read commands from file
sed -f script.sed input.txt

# Convert CSV to TSV
sed 's/,/\t/g' data.csv
12

Plongée dans awk

Bases d'awk et champs

awk divise automatiquement chaque ligne en champs ($1, $2, ..., $NF). -F définit le séparateur de champ d'entrée ; OFS définit le séparateur de sortie. $0 est la ligne entière. NR (Number of Records) est le numéro de ligne ; NF (Number of Fields) est le compte de champs pour la ligne courante. awk traite chaque ligne via des paires motif-action : motif { action }. Sans motif, l'action s'exécute pour chaque ligne. Sans action, la ligne est imprimée. Cela rend awk idéal pour l'extraction de données basée sur les colonnes depuis CSV, TSV, /etc/passwd et les fichiers de log.

bash
# Print specific fields (default delimiter: whitespace)
awk '{print $1}' file.txt        # first field
awk '{print $1, $3}' file.txt    # first and third
awk '{print $NF}' file.txt       # last field
awk '{print $(NF-1)}' file.txt   # second-to-last

# Custom field separator
awk -F: '{print $1, $7}' /etc/passwd   # colon-separated
awk -F',' '{print $2}' data.csv        # CSV
awk -F'\t' '{print $1}' data.tsv      # TSV

# Print entire line ($0)
awk '{print}' file.txt
awk '{print NR, $0}' file.txt  # with line number

# NR = record (line) number
# NF = number of fields in current record
# NR and NF are built-in variables

# Change output separator
awk 'BEGIN{OFS="|"} {print $1, $2, $3}' file.txt

Motifs et conditions awk

Les motifs awk peuvent être des regex (/pattern/), des comparaisons ($3 > 100), des numéros de ligne (NR == 5), ou des plages (/start/,/end/). ~ et !~ appliquent les regex à des champs spécifiques. Les conditions peuvent être combinées avec &&, ||, et !. Cela rend awk un filtre puissant — plus expressif que grep car vous pouvez comparer des valeurs de champs numériquement ou comme chaînes. Le motif de plage (/start/,/end/) imprime toutes les lignes entre deux marqueurs (inclus). awk évalue les conditions par ligne ; si vrai, l'action s'exécute. Sans action, le défaut est {print}.

bash
# Pattern: only process matching lines
awk '/error/ {print}' log.txt          # like grep
awk '$3 > 100' data.txt                # 3rd field > 100
awk 'NR == 5' file.txt                 # line 5 only
awk 'NR >= 10 && NR <= 20' file.txt    # lines 10-20
awk 'NF == 0' file.txt                 # empty lines
awk '$1 == "ERROR"' log.txt            # first field equals ERROR

# Range pattern: from start to end
awk '/start/,/end/' file.txt

# Combine conditions
awk '$3 > 50 && $3 < 100 {print $1, $3}' data.txt
awk '$1 == "GET" || $1 == "POST" {print}' access.log

# Negation
awk '!/debug/ {print}' log.txt  # lines NOT containing debug

# Field comparison with regex
awk '$2 ~ /^[0-9]+$/' file.txt   # 2nd field is numeric
awk '$2 !~ /pattern/' file.txt   # 2nd field doesn't match

awk BEGIN/END et variables

BEGIN s'exécute avant qu'une entrée ne soit lue (idéal pour l'initialisation, en-têtes, configuration de variables). END s'exécute après que toute l'entrée soit traitée (idéal pour les résumés, totaux). Les variables utilisateur n'ont pas besoin de déclaration — elles défautent à 0 (numérique) ou vide (chaîne). -v passe des variables externes dans awk. Cela rend awk un mini langage de programmation pour le traitement de données : somme, moyenne, min, max, compte — tout en un seul passage. Le motif « NR == 1 {max = $1} » initialise max depuis la première ligne, puis les lignes suivantes le mettent à jour. C'est bien plus efficace que de multiples pipelines grep/sort/cut.

bash
# BEGIN: runs before processing (setup)
# END: runs after processing (summary)
awk 'BEGIN {print "Total:"} {sum += $1} END {print sum}' nums.txt

# Count lines
awk 'END {print NR " lines"}' file.txt

# Sum a column
awk '{sum += $2} END {print "Total:", sum}' sales.csv

# Average
awk '{sum += $2; count++} END {print sum/count}' data.txt

# Find max value
awk 'NR == 1 {max = $1} $1 > max {max = $1} END {print max}' nums.txt

# User-defined variables (no declaration needed)
awk '{total += $1; if ($1 > max) max = $1}
     END {print "Sum:", total, "Max:", max, "Avg:", total/NR}'

# Pass variables with -v
awk -v threshold=50 '$1 > threshold {print}' data.txt
awk -v date="$(date +%Y-%m-%d)" '{print date, $0}' file.txt

Flux de contrôle awk

awk a un flux de contrôle complet : if/else, for, while, et des tableaux associatifs (clé-valeur). Cela en fait un langage complet de traitement de données. L'exemple de compte de mots (words[$1]++) est le cas d'usage classique d'awk — compter les occurrences de chaque valeur dans une colonne, puis trier par fréquence. Les tableaux dans awk sont associatifs (comme des dictionnaires), indexés par chaîne ou nombre. for (key in array) itère les clés (non ordonné — pipez à travers sort). awk peut remplacer des pipelines entiers de cut, sort, uniq et wc avec un seul passage efficace. Pour le traitement de données complexe, awk est souvent plus clair que des commandes Unix chaînées.

bash
# if-else
awk '{
  if ($3 >= 90) grade = "A"
  else if ($3 >= 80) grade = "B"
  else if ($3 >= 70) grade = "C"
  else grade = "F"
  print $1, grade
}' students.txt

# for loop
awk 'BEGIN {
  for (i = 1; i <= 5; i++) print i, i*i
}'

# for-in (iterate array)
awk '{count[$1]++} END {
  for (key in count) print key, count[key]
}' access.log | sort -rnk2

# while loop
awk '{
  i = 1
  while (i <= NF) {
    print i, $i
    i++
  }
}' file.txt

# Arrays (associative)
awk '{words[$1]++} END {
  for (w in words) print words[w], w
}' file.txt | sort -rn | head

Exemples pratiques awk

Ces exemples pratiques montrent la puissance réelle d'awk. Le compte de fréquence d'IP est essentiel pour l'analyse de logs. Le résumé de taille de fichier par extension utilise split() pour extraire les extensions. Le filtrage CSV par valeur de colonne remplace des pipelines complexes grep/sed. Le calcul de percentile démontre les capacités mathématiques d'awk (asorti trie les indices de tableau). Le reformatage de colonnes (changement de délimiteur et sélection de champs) est une tâche ETL courante. awk est l'outil de référence pour le traitement de texte structuré — quand les données ont des colonnes/champs, awk est presque toujours le bon choix. Pour JSON, utilisez jq ; pour CSV, awk ou csvkit.

bash
# Top 10 IP addresses from access log
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head
# Or entirely in awk:
awk '{ip[$1]++} END {for (i in ip) print ip[i], i}' access.log | sort -rn | head

# Sum file sizes by extension
ls -l | awk '{split($NF, a, "."); ext=a[2]; size[ext]+=$5}
  END {for (e in size) print e, size[e]}' | sort -rnk2

# Filter CSV by column value
awk -F',' '$3 == "active" {print $1, $2}' users.csv

# Calculate response time percentiles from log
awk '{times[NR] = $2} END {
  n = asorti(times, sorted)
  print "p50:", sorted[int(n*0.5)]
  print "p95:", sorted[int(n*0.95)]
  print "p99:", sorted[int(n*0.99)]
}' response_times.txt

# Reformat columns
awk -F',' 'BEGIN{OFS="|"} {print $1, $2, $4}' data.csv
13

find et xargs

find par nom et type

find cherche dans le système de fichiers selon divers critères. -name correspond au nom de fichier (sensible à la casse) ; -iname est insensible à la casse. -type f/d/l filtre par type de fichier. -path correspond au chemin complet ; -regex utilise les regex de chemin complet. -o (ou) combine les conditions ; utilisez \( \) pour le regroupement. -maxdepth limite la profondeur de récursion (important pour la performance sur les grands systèmes de fichiers). find sort les chemins ; combinez avec -exec ou xargs pour agir sur les résultats. Quotez toujours les motifs pour empêcher l'expansion glob du shell. Pour la recherche de code, ripgrep (rg) est plus rapide mais find est plus flexible pour les opérations de système de fichiers.

bash
# Find by name (exact)
find . -name "config.txt"

# Case-insensitive name
find . -iname "config.txt"

# Wildcard pattern
find . -name "*.py"
find . -name "test_*"

# Find by type
find . -type f -name "*.js"   # files only
find . -type d -name "node_modules"  # directories
find . -type l                # symlinks

# Find by path (full path match)
find . -path "*/src/*.test.js"

# Find by regex
find . -regex ".*\.\(py\|js\)$"

# Multiple name patterns
find . \( -name "*.py" -o -name "*.js" \)

# Max depth (don't recurse too deep)
find . -maxdepth 2 -name "*.txt"

find par temps et taille

La recherche temporelle de find est essentielle pour le nettoyage et l'audit. -mtime (modification), -atime (accès), -ctime (changement de métadonnées) utilisent des jours ; -mmin/-amin/-cmin utilisent des minutes. - (moins que) et + (plus que) préfixent la valeur. La taille utilise des suffixes : c (octets), k (Ko), M (Mo), G (Go). -empty trouve les fichiers de longueur zéro ou les répertoires vides. -perm vérifie les permissions : correspondance exacte (644), tous les bits définis (-u+x), ou n'importe quel bit défini (/4000). Trouver les fichiers SUID (/4000) est une technique d'audit de sécurité. Ces critères peuvent être combinés avec -a (et, défaut) et -o (ou).

bash
# Find by modification time
find . -mtime -1    # modified in last 24 hours
find . -mtime +7    # modified more than 7 days ago
find . -mtime 7     # modified exactly 7 days ago

# Access time (-atime) and change time (-ctime)
find . -atime -1    # accessed in last 24h
find . -ctime -1    # status changed in last 24h

# Minutes instead of days
find . -mmin -30    # modified in last 30 minutes

# Find by size
find . -size +100M   # larger than 100MB
find . -size -1k     # smaller than 1KB
find . -size 10M     # exactly 10MB

# Find empty files/directories
find . -empty -type f
find . -empty -type d

# Find by permissions
find . -perm 644     # exactly 644
find . -perm -u+x    # has execute for user
find / -perm /4000   # SUID files (security audit)

find -exec et -delete

-exec exécute une commande sur chaque résultat. {} est l'emplacement pour le nom du fichier ; \; termine la commande (s'exécute une fois par fichier) ; + groupe les fichiers (s'exécute une fois avec tous les fichiers — plus efficace). -delete supprime les fichiers correspondants (plus rapide que -exec rm, mais testez d'abord avec -print). Le motif chmod (répertoires 755, fichiers 644) est une configuration courante de serveur web. -exec avec + est préféré pour les commandes qui acceptent plusieurs fichiers (grep, ls, wc). Pour la suppression, exécutez toujours avec -print d'abord pour vérifier ce qui sera supprimé, puis remplacez par -delete. -ok au lieu de -exec demande confirmation par fichier.

bash
# Execute command on each result
find . -name "*.log" -exec ls -lh {} \;

# Execute with + (batch, more efficient)
find . -name "*.log" -exec ls -lh {} +

# Delete files (DANGEROUS - test first!)
find /tmp -name "*.tmp" -type f -delete
# Safer: use -exec rm
find /tmp -name "*.tmp" -type f -exec rm -i {} \;

# Execute a complex command
find . -name "*.py" -exec wc -l {} \; | sort -rn | head

# Change permissions
find . -type d -exec chmod 755 {} \;  # dirs to 755
find . -type f -exec chmod 644 {} \;  # files to 644

# Find and grep
find . -name "*.py" -exec grep "TODO" {} +

# Print before deleting (confirmation)
find . -name "*.bak" -print -delete

Bases de xargs

xargs convertit stdin en arguments de commande. C'est le pont entre la sortie de find et les commandes qui ne lisent pas stdin. -n limite les arguments par commande ; -I {} définit un emplacement pour le positionnement personnalisé. -0 (avec find -print0) gère les noms de fichiers avec espaces/newlines correctement — utilisez toujours cette paire pour la sécurité. -P active l'exécution parallèle (génial pour les tâches CPU-bound comme la conversion d'images). -t (trace) montre les commandes avant exécution ; -p demande confirmation. xargs est essentiel quand -exec est trop lent (un processus par fichier) — xargs groupe les arguments efficacement.

bash
# Basic: take stdin and pass as arguments
echo "file1 file2 file3" | xargs rm

# One argument per line
find . -name "*.bak" | xargs rm

# Control arguments per command (-n)
echo "1 2 3 4 5" | xargs -n 2 echo  # echo 1 2; echo 3 4; echo 5

# Show commands before running (-t, verbose)
find . -name "*.log" | xargs -t rm

# Prompt before each command (-p)
find . -name "*.tmp" | xargs -p rm

# Handle filenames with spaces (-0 with find -print0)
find . -name "*.txt" -print0 | xargs -0 grep "pattern"

# Replace string (-I)
find . -name "*.py" | xargs -I {} cp {} /backup/

# Parallel execution (-P)
find . -name "*.png" | xargs -P 4 -I {} convert {} {}.thumb.png

# Limit with -L (lines per command)
cat urls.txt | xargs -L 1 curl -O

Motifs find + xargs

find + xargs est le motif Unix classique pour les opérations de fichiers en masse. -print0 | xargs -0 est la combinaison sûre pour les noms de fichiers avec espaces ou caractères spéciaux. Le motif grep en masse est beaucoup plus rapide que -exec grep (un processus grep contre plusieurs). xargs parallèle (-P N) accélère considérablement les tâches CPU-bound comme la conversion audio/vidéo. Le motif d'archive (trouver les vieux logs, les tarer) est une technique courante de rotation de logs. Utilisez toujours -print0/-0 pour la robustesse — sans cela, les noms de fichiers avec espaces, quotes, ou newlines casseront le pipeline. Cette combinaison est fondamentale à l'administration système Unix.

bash
# Find and bulk grep (efficient)
find . -name "*.py" -print0 | xargs -0 grep -l "import django"

# Find large files and show sizes
find . -type f -size +100M -print0 | xargs -0 ls -lhS

# Find and archive
find . -name "*.log" -mtime +30 -print0 |
  xargs -0 tar -czf old_logs.tar.gz

# Find and batch rename
find . -name "*.JPG" | xargs -I {} mv {} {}.bak
# Or with rename command:
find . -name "*.JPG" -exec rename 's/\.JPG$/.jpg/' {} +

# Find empty directories and remove
find . -type d -empty -print0 | xargs -0 rmdir

# Find recently modified files and copy
find . -name "*.py" -mtime -1 -print0 |
  xargs -0 -I {} cp {} /tmp/recent/

# Parallel processing
find . -name "*.wav" -print0 |
  xargs -0 -P 8 -I {} ffmpeg -i {} {}.mp3
14

Techniques Bash avancées

Here document et here string

Les here documents (<< DELIMITER) alimentent du texte multi-lignes comme stdin à une commande — utiles pour générer des fichiers de config, des requêtes SQL, ou n'importe quelle entrée multi-lignes. Le délimiteur non quoté autorise l'expansion de variables ; quoté (« EOF ») traite le contenu littéralement. Les here strings (<<<) alimentent une seule chaîne comme stdin — plus propre que echo | commande pour les cas simples. Le délimiteur peut être n'importe quel mot (EOF, END, DONE) ; la convention est majuscule. Les here docs sont essentiels pour les scripts qui génèrent des fichiers ou interagissent avec des programmes interactifs (mysql, psql, ssh). Les délimiteurs indentés (<<-) suppriment les tabs de début pour la lisibilité.

bash
# Here document: multi-line input to a command
cat << EOF
Line 1
Line 2 with $VARIABLE expansion
Line 3
EOF

# Quoted delimiter: no variable expansion
cat << 'EOF'
This $won't expand
\n stays literal
EOF

# Write to file
cat > config.txt << EOF
host=localhost
port=8080
EOF

# Here string: single string as stdin
grep "pattern" <<< "search this text"
read -r line <<< "hello world"

# With variables
name="Alice"
greeting=$(cat <<< "Hello, $name!")

# Pipe multi-line to command
mysql << EOF
USE mydb;
SELECT * FROM users;
EOF

Expansion des paramètres

L'expansion des paramètres est la manipulation de chaînes intégrée de Bash — pas besoin de sed/awk/cut pour les opérations simples. :- fournit des valeurs par défaut ; # et ## suppriment les préfixes ; % et %% suppriment les suffixes. Les exemples d'extension de fichier sont extrêmement courants : ${file##*.} obtient l'extension, ${file%.*} obtient le basename sans extension. / remplace la première correspondance ; // remplace tout. ^^ et ,, convertissent la casse (Bash 4+). Ces opérations sont plus rapides que de spawn des commandes externes. Maîtrisez l'expansion des paramètres pour écrire des scripts Bash efficaces et lisibles sans subshells inutiles.

bash
# Default values
echo ${name:-"default"}    # use "default" if name unset/empty
echo ${name:="default"}    # set name to "default" if unset/empty

# String length
echo ${#var}

# Substring extraction
var="Hello World"
echo ${var:0:5}    # Hello (start:length)
echo ${var:6}      # World (start to end)

# Remove from beginning (# shortest, ## longest)
file="archive.tar.gz"
echo ${file#*.}     # tar.gz (remove up to first .)
echo ${file##*.}    # gz (remove up to last .)

# Remove from end (% shortest, %% longest)
echo ${file%.*}     # archive.tar (remove from last .)
echo ${file%%.*}    # archive (remove from first .)

# Replace
echo ${var/World/Bash}    # Hello Bash (first match)
echo ${var//l/L}          # HeLLo WorLd (all matches)

# Case conversion (Bash 4+)
echo ${var^^}   # HELLO WORLD (uppercase)
echo ${var,,}   # hello world (lowercase)

trap et gestion des signaux

trap enregistre des gestionnaires pour les signaux et événements. EXIT se déclenche à la terminaison du script (normale, erreur, ou tué) — idéal pour le nettoyage (fichiers temporaires, verrous). INT (Ctrl-C), TERM (kill), HUP (terminal fermé) sont des signaux courants. Le motif de fichier temporaire (trap 'rm -f' EXIT) garantit le nettoyage même si le script échoue. DEBUG se déclenche avant chaque commande — utile pour le traçage. Quotez toujours la commande trap (les simples quotes empêchent l'expansion immédiate). trap est essentiel pour les scripts robustes : sans lui, les fichiers temporaires s'accumulent et les verrous peuvent ne pas se libérer en cas d'échec. Nettoyez toujours après vous.

bash
# Clean up on exit (any reason)
cleanup() {
  echo "Cleaning up..."
  rm -f /tmp/myapp.lock
  exit 0
}
trap cleanup EXIT

# Handle specific signals
trap 'echo "Interrupted!"; cleanup' INT TERM
trap 'echo "Ctrl-C pressed"' INT

# Ignore a signal
trap '' INT  # ignore Ctrl-C

# Reset trap
trap - INT  # restore default behavior

# Common pattern: temp file cleanup
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
echo "data" > "$TMPFILE"
# ... use TMPFILE ...
# File is removed when script exits (even on error)

# Debug trap (trace execution)
trap 'echo "DEBUG: $BASH_COMMAND"' DEBUG

Débogage de scripts Bash

set -x trace l'exécution (imprime chaque commande avec préfixe +) — l'outil de débogage principal. set -euo pipefail (mode strict) attrape les erreurs tôt : -e sort sur non-zéro, -u attrape les fautes de frappe dans les noms de variables, pipefail fait échouer les pipes correctement (sans lui, seul le code de sortie de la dernière commande compte). bash -n vérifie la syntaxe sans exécuter. PS4 personnalise le préfixe de trace (montrer fichier:ligne est très utile). trap ERR se déclenche à toute erreur (avec -e), vous donnant le contexte de numéro de ligne. Pour les scripts complexes, ajoutez set -x aux sections suspectes plutôt que globalement. Utilisez toujours le mode strict dans les scripts de production.

bash
# Trace execution: print each command before running
bash -x script.sh
# Or within script:
set -x   # enable tracing
set +x   # disable tracing

# Verbose: print input lines as read
bash -v script.sh

# Strict mode (recommended for all scripts)
set -euo pipefail
# -e: exit on error
# -u: error on undefined variable
# -o pipefail: pipe fails if any command fails
# -o pipefail: pipe fails if any command fails

# Print specific debug info
echo "DEBUG: var=$var" >&2  # to stderr

# Check syntax without executing
bash -n script.sh

# PS4: customize trace prompt
export PS4='+${BASH_SOURCE}:${LINENO}: '
bash -x script.sh

# Trap ERR for error handling
trap 'echo "Error on line $LINENO"' ERR

curl et wget

curl et wget sont des clients HTTP essentiels. curl est pour les tests d'API (supporte toutes les méthodes HTTP, en-têtes, JSON). -d envoie des données POST ; -H définit les en-têtes ; -X spécifie la méthode ; -L suit les redirections ; -O sauvegarde avec le nom distant ; -s est silencieux (pour le scripting). wget est optimisé pour le téléchargement (reprend avec -c, récursif avec -r). Pour les tests d'API REST, curl avec -H « Content-Type: application/json » et -d pour le corps JSON est le standard. -w « %{http_code} » extrait juste le code de statut pour le scripting. Pour les tests d'API complexes, envisagez httpie (syntaxe plus simple) ou postman.

bash
# Basic HTTP request
curl https://api.example.com/data
curl -O https://example.com/file.zip  # save to file

# HTTP methods
curl -X POST https://api.example.com/users
curl -X PUT -d '{"name":"Alice"}' https://api.example.com/users/1
curl -X DELETE https://api.example.com/users/1

# Headers and JSON
curl -H "Content-Type: application/json" \
     -H "Authorization: Bearer TOKEN" \
     -d '{"key":"value"}' \
     https://api.example.com/data

# Download with wget
wget https://example.com/file.zip
wget -q https://example.com/file.zip  # quiet
wget -c https://example.com/file.zip  # continue/resume

# Follow redirects
curl -L https://short.url/abc

# Save response, show headers
curl -s -o response.json -w "%{http_code}" https://api.example.com

# Download with progress bar
curl --progress-bar -O https://example.com/largefile.iso

# POST form data
curl -d "name=Alice&[email protected]" https://api.example.com/form
15

Signaux et traps

Gestionnaires de trap cleanup

trap enregistre des gestionnaires pour les signaux. EXIT est spécial — il se déclenche à la sortie du shell pour n'importe quelle raison, ce qui le rend parfait pour le nettoyage. Nettoyez toujours les fichiers de verrou, répertoires temporaires et processus enfants dans un trap. Signaux courants à trapper : INT (Ctrl-C), TERM (kill par défaut), HUP (terminal fermé), EXIT.

bash
cleanup() {
  echo "Cleaning up..."
  rm -f /tmp/myapp.lock
  exit 0
}

# catch INT (Ctrl-C), TERM, EXIT
trap cleanup INT TERM EXIT

# normal exit also triggers EXIT trap
echo "Working..."
sleep 30
# cleanup runs automatically

# remove trap
trap - EXIT                      # disable
trap - INT TERM EXIT             # disable all

fichiers temporaires avec nettoyage

Ne créez jamais de noms de fichiers temporaires à la main avec $$ ou $RANDOM — des chemins prévisibles invitent aux attaques par symlink. mktemp crée un fichier/répertoire au nom unique atomiquement. Associez-le à un trap sur EXIT pour garantir le nettoyage même en cas d'erreur ou d'interruption. Définissez la variable d'environnement TMPDIR pour contrôler l'emplacement.

bash
TMPDIR=$(mktemp -d)
TMPFILE=$(mktemp)

cleanup() {
  rm -rf "$TMPDIR" "$TMPFILE"
}
trap cleanup EXIT

# use temp files safely
echo "data" > "$TMPFILE"
process_data < "$TMPFILE"

# mktemp is safer than $RANDOM — avoids symlink attacks
# -d creates a directory
# -p /var/tmp specifies parent
# mktemp -t myapp.XXXXXX   # use TMPDIR env

gestion des signaux dans les scripts

Pour les scripts longs, utilisez une variable de flag basculée par le gestionnaire de signal pour sortir de la boucle principale proprement. Cela vous laisse terminer l'itération courante, persister l'état, et sortir proprement. Évitez de faire du travail lourd à l'intérieur du gestionnaire de trap lui-même — contentez-vous de définir des flags.

bash
#!/bin/bash
# graceful shutdown for a long-running script

RUNNING=1
stop() {
  echo "Stopping..."
  RUNNING=0
}
trap stop INT TERM

while [ $RUNNING -eq 1 ]; do
  echo "Tick $(date)"
  sleep 1
done

echo "Done."

# send signals: kill -TERM <pid> or Ctrl-C

timeout et watch

timeout exécute une commande et la tue (SIGTERM par défaut) après une durée. Le code de sortie 124 signifie qu'elle a expiré. Utilisez --kill-after pour escalader vers SIGKILL si le processus ignore SIGTERM. Combinez avec des boucles pour construire des health checks. watch est pour l'affichage périodique humain, pas pour les scripts.

bash
# timeout kills a command after N seconds
timeout 30 slow_command
timeout 5s curl https://slow.example.com
timeout --signal=KILL 60 build.sh  # escalate to SIGKILL
timeout -k 5 30 command            # kill -9 after 5s grace

# exit status: 124 if timed out
timeout 2 sleep 10
echo $?                            # 124

# watch reruns a command
watch -n 1 date
watch -n 5 'kubectl get pods'

# tlimit-style loops
while true; do
  timeout 5 curl -s https://api.example.com/health || echo "down"
  sleep 60
done

ignorer et re-lever

trap '' SIGNAL ignore un signal (pratique pour les sections critiques ininterruptibles). Pour transmettre les signaux à un enfant, capturez son PID avec $! et re-envoyez. Le trap ERR se déclenche à toute erreur de commande quand set -e est activé — utile pour logger le numéro de ligne défaillant via $LINENO.

bash
# ignore a signal entirely
trap '' INT                       # ignore Ctrl-C
trap - INT                        # restore default

# re-raise / propagate to children
trap 'kill -TERM $childpid; wait $childpid' TERM

# forward signals to a subprocess
childpid=
run_child() {
  some_server &
  childpid=$!
  wait $childpid
}
trap 'kill -TERM $childpid' TERM INT
run_child

# exit on any error
set -e
trap 'echo "Failed at line $LINENO"' ERR
16

Fonctions et bibliothèques

définir des fonctions

Les fonctions regroupent des commandes réutilisables. Deux syntaxes : name() {...} et function name {...}. Les arguments à l'intérieur des fonctions sont $1, $2, etc. — $0 est toujours le nom du script. $@ s'étend à tous les args (quotez toujours comme "$@" pour préserver les args avec espaces). $# est le compte.

bash
# two equivalent syntaxes
greet() {
  echo "Hello, $1!"
}

function greet {                  # also valid
  echo "Hello, $1!"
}

greet Alice                       # Hello, Alice!
greet "Bob Smith"                 # quoted args

# arguments: $1, $2, ... $@, $#, $*
echo_args() {
  echo "Count: $#"
  echo "All: $@"
  echo "First: $1"
  for arg in "$@"; do echo "- $arg"; done
}

valeurs de retour et locales

return définit le statut de sortie (0=succès, 1-255=échec) — il ne retourne PAS de valeur. Pour récupérer une valeur, echoz-la et capturez avec $(). Utilisez local pour les variables à l'intérieur des fonctions pour éviter de polluer la portée globale. local est essentiel dans les fonctions récursives.

bash
# return is exit status (0-255), not a value
is_even() {
  if (( $1 % 2 == 0 )); then return 0; else return 1; fi
}

if is_even 4; then echo "even"; fi

# capture stdout as a value
get_date() { date +%Y-%m-%d; }
today=$(get_date)

# local variables
counter() {
  local count=0                  # scoped to function
  ((count++))
  echo $count
}

sourcer des bibliothèques

source (ou .) exécute un fichier dans le shell courant — donc les fonctions, variables et alias deviennent disponibles. C'est ainsi que vous construisez des bibliothèques réutilisables. BASH_SOURCE[0] vs $0 distingue sourcé vs exécuté — pratique pour les fichiers qui sont à la fois une bibliothèque et un script exécutable.

bash
# library file: lib/utils.sh
log() {
  echo "[$(date +%H:%M:%S)] $*" >&2
}

CONFIG_PATH="/etc/myapp"

# main script
source lib/utils.sh              # or: . lib/utils.sh
log "Starting..."
echo "Config: $CONFIG_PATH"

# check if sourced vs executed
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  echo "Running directly"
else
  echo "Sourced"
fi

fonctions récursives

Bash supporte la récursion, mais c'est lent (chaque appel spawn des subshells pour $()) et a une profondeur de pile limitée (~1000s). Pour le travail de calcul intensif, préférez awk, Python ou des outils externes. Utilisez toujours des variables locales dans les fonctions récursives — sinon elles s'écrasent mutuellement à travers les appels.

bash
# factorial
factorial() {
  local n=$1
  if (( n <= 1 )); then
    echo 1
  else
    local prev=$(factorial $((n - 1)))
    echo $((n * prev))
  fi
}
factorial 5                      # 120

# fibonacci
fib() {
  if (( $1 < 2 )); then echo $1; return; fi
  echo $(( $(fib $(($1 - 1))) + $(fib $(($1 - 2))) ))
}

# caution: bash recursion is slow and limited depth
# for heavy work, use awk, python, or external tools

arguments par défaut et optionnels

${1:-default} substitue un défaut si $1 est non défini ou vide. ${1:?message} sort avec une erreur si manquant — génial pour les args requis. shift dépile $1 de la liste d'arguments. Combiner case + shift est la façon idiomatique de parser les flags et args positionnels dans les fonctions bash.

bash
# default values
greet() {
  local name="${1:-World}"        # default if unset or empty
  local greeting="${2:-Hello}"
  echo "$greeting, $name!"
}
greet                            # Hello, World!
greet Alice                      # Hello, Alice!
greet Alice Hi                   # Hi, Alice!

# required argument
require_arg() {
  : "${1:?Usage: require_arg <name>}"   # exits with message if missing
  echo "Got: $1"
}

# shift through args
process() {
  while (( $# )); do
    case "$1" in
      -v) verbose=1; shift ;;
      *)  files+=("$1"); shift ;;
    esac
  done
}
17

Tableaux et tableaux associatifs

tableaux indexés

Les tableaux indexés utilisent des clés entières basées sur 0. Quotez toujours « ${arr[@]} » à l'itération pour gérer correctement les éléments avec espaces. ${#arr[@]} est le compte. ${!arr[@]} donne les indices (utile pour les tableaux creux). unset 'arr[i]' (quoté) supprime un élément.

bash
# declare and assign
fruits=("apple" "banana" "cherry")
fruits+=("date")                 # append
fruits[10]="kiwi"                # sparse

# access
echo "${fruits[0]}"             # first
echo "${fruits[@]}"             # all values
echo "${#fruits[@]}"            # count
echo "${!fruits[@]}"            # indices

# iterate
for f in "${fruits[@]}"; do
  echo "$f"
done

# slice
echo "${fruits[@]:1:2}"         # 2 items starting at index 1

# delete
unset 'fruits[1]'

tableaux associatifs (maps)

Les tableaux associatifs (declare -A) sont une fonctionnalité bash 4+ (macOS livre bash 3 par défaut — utilisez brew install bash). Les clés sont des chaînes. Le test -v vérifie si une clé existe. Itérez les clés avec ${!arr[@]}. Le /bin/bash par défaut de macOS est 3.2 — les scripts utilisant -A ont besoin de #!/usr/bin/env bash pointant vers un bash plus récent.

bash
# requires bash 4+
declare -A ages
ages["alice"]=30
ages["bob"]=25
ages["carol"]=42

# access
echo "${ages["alice"]}"
echo "${ages[@]}"               # all values
echo "${!ages[@]}"              # all keys

# iterate keys
for name in "${!ages[@]}"; do
  echo "$name => ${ages[$name]}"
done

# check key exists
if [[ -v ages["alice"] ]]; then echo "yes"; fi

# count
echo "${#ages[@]}"

# delete
unset 'ages[bob]'

lire dans des tableaux

mapfile (alias readarray) est la façon la plus rapide d'aspirer des lignes dans un tableau — utilisez toujours -t pour supprimer les newlines de fin. La boucle while-read est portable mais plus lente. Pour diviser une chaîne délimitée, définissez IFS et utilisez read -ra. La substitution de processus < <(cmd) évite les problèmes de portée de variable de subshell.

bash
# read lines into array
mapfile -t lines < file.txt      # bash 4+
echo "${lines[0]}"

# alternative (portable)
lines=()
while IFS= read -r line; do
  lines+=("$line")
done < file.txt

# split string into array
IFS=',' read -ra csv <<< "a,b,c,d"
echo "${csv[2]}"                # c

# from command
mapfile -t files < <(find . -name "*.js")

# process each
for f in "${files[@]}"; do
  echo "Processing $f"
done

opérations de tableau

Les tableaux Bash manquent de nombreuses opérations intégrées (pas de .indexOf, .reverse, .unique). Pour celles-ci, écrivez des boucles ou pipez à travers sort/uniq. L'astuce de tableau unique utilise un tableau associatif comme ensemble. Pour la manipulation complexe de tableaux, envisagez awk ou un vrai langage de programmation — les tableaux bash sont meilleurs pour les listes simples.

bash
# append
arr=(a b c)
arr+=(d e)                       # (a b c d e)

# length
echo "${#arr[@]}"

# slice
echo "${arr[@]:1:2}"            # (b c)

# find index (no built-in; loop)
idx=-1
for i in "${!arr[@]}"; do
  if [[ "${arr[$i]}" == "b" ]]; then idx=$i; break; fi
done

# reverse
n=${#arr[@]}
for ((i=n-1; i>=0; i--)); do
  echo "${arr[$i]}"
done

# unique (preserve order)
declare -A seen
unique=()
for x in "${arr[@]}"; do
  [[ -v seen[$x] ]] && continue
  seen[$x]=1
  unique+=("$x")
done

données imbriquées avec des tableaux

Bash n'a pas de tableaux imbriqués natifs. Contournements : (1) stockez les enregistrements comme des chaînes jointes par délimiteur et divisez avec IFS, (2) utilisez un tableau associatif avec des clés composites comme « r,c », (3) utilisez l'indirection de variable avec ${!name}. Pour de vraies données imbriquées, passez à jq + JSON ou un vrai langage.

bash
# array of "records" (delimiter-based)
users=("alice:30:admin" "bob:25:user")
for u in "${users[@]}"; do
  IFS=':' read -r name age role <<< "$u"
  echo "$name ($age) - $role"
done

# simulate 2D with name-mangled vars
declare -A grid
grid["0,0"]=1; grid["0,1"]=2
grid["1,0"]=3; grid["1,1"]=4
for key in "${!grid[@]}"; do
  IFS=',' read -r r c <<< "$key"
  echo "[$r,$c] = ${grid[$key]}"
done

# arrays of arrays (not directly supported)
# workaround: use indirection
rows=("row0" "row1")
row0=(a b c)
row1=(d e f)
ref="${rows[0]}[@]"
echo "${!ref}"                   # a b c
18

Manipulation de chaînes

expansion des paramètres

L'expansion des paramètres est l'outil de chaîne le plus puissant de bash. # et ## suppriment depuis le début (correspondance la plus courte/la plus longue) ; % et %% suppriment depuis la fin. / remplace la première, // remplace tout. Cela évite de spawn des subshells — beaucoup plus rapide que de piper à travers sed pour les opérations simples.

bash
path="/var/log/app.log"

# length
echo "${#path}"                 # 16

# substring (offset:length)
echo "${path:5}"                # log/app.log
echo "${path:5:3}"              # log

# remove from front (shortest/longest)
echo "${path#*/}"               # var/log/app.log
echo "${path##*/}"              # app.log (basename)

# remove from end
echo "${path%/*}"               # /var/log (dirname)
echo "${path%%.*}"              # /var/log/app

# replace
echo "${path/log/LOG}"          # /var/LOG/app.log (first)
echo "${path//log/LOG}"         # all occurrences

conversion de casse

Bash 4+ a ajouté ,, (minuscule), ^^ (majuscule), ^ (capitaliser premier caractère), , (minuscule premier caractère). declare -u/-l convertit automatiquement les assignations. Pour les vieux bash (défaut macOS), utilisez tr ou awk. La conversion de casse est courante à la normalisation d'entrée utilisateur ou à la construction d'identifiants.

bash
s="Hello World"

# bash 4+
echo "${s,,}"                   # hello world (lower)
echo "${s^^}"                   # HELLO WORLD (upper)
echo "${s^}"                    # Hello World (capitalize first)
echo "${s,}"                    # hello World (lowercase first)

# pattern-based
echo "${s^^[aeiou]}"            # hEllO wOrld (vowels upper)

# portable (pre-bash 4)
echo "$s" | tr 'a-z' 'A-Z'
echo "$s" | awk '{print toupper($0)}'

# declare typed variables
declare -u UPPER="hello"         # HELLO
declare -l LOWER="WORLD"         # world

division et jointure

Division : définissez IFS et utilisez read -ra. Jointure : ${arr[*]} avec IFS défini fonctionne mais est capricieux — la fonction join_by est plus fiable. L'astuce printf '%s' "$d%s" prépend le délimiteur à chaque argument restant, produisant une jointure propre. tr peut convertir entre délimiteurs et newlines.

bash
# split on delimiter
csv="a,b,c,d"
IFS=',' read -ra parts <<< "$csv"
echo "${parts[2]}"              # c
for p in "${parts[@]}"; do echo "$p"; done

# join with delimiter (bash 4+)
arr=(a b c d)
joined=$(IFS=,; echo "${arr[*]}")
echo "$joined"                   # a,b,c,d

# custom joiner
join_by() {
  local d=$1; shift
  local first=$1; shift
  printf '%s' "$first"
  printf '%s' "$d%s" "$@"
}
join_by " | " a b c              # a | b | c

# split into lines
tr ',' '\n' <<< "a,b,c"

recherche et test

Correspondance de motifs : == avec wildcards (* ? [..]) fait du glob matching. =~ fait des regex étendus ; les groupes de capture atterrissent dans BASH_REMATCH. shopt -s nocasematch rend les comparaisons [[ ]] insensibles à la casse. expr est hérité — préférez [[ ]] pour le nouveau code.

bash
s="Hello World"

# substring search
if [[ "$s" == *"World"* ]]; then echo "contains"; fi
if [[ "$s" == Hello* ]]; then echo "starts with"; fi
if [[ "$s" == *World ]]; then echo "ends with"; fi

# regex match
if [[ "$s" =~ ^H[a-z]+\sW[a-z]+$ ]]; then echo "match"; fi
echo "${BASH_REMATCH[0]}"       # whole match
echo "${BASH_REMATCH[1]}"       # first group

# case-insensitive
shopt -s nocasematch
[[ "$s" == *world* ]] && echo "yes"
shopt -u nocasematch

# index (no built-in; use expr)
expr "$s" : ".*World"            # length up to match

formatage printf

printf est plus puissant qu'echo — supporte les spécificateurs de format style C (%s, %d, %f, %x), largeur/précision et alignement. %()T formate le temps sans spawn la commande date. printf -v stocke le résultat dans une variable. printf '=%.0s' {1..40} est une astuce soigne pour répéter un caractère N fois.

bash
# basic
printf '%s\n' "hello" "world"   # one per line
printf '%s = %d\n' "count" 42

# width and padding
printf '%10s\n' "right"         # right-aligned, width 10
printf '%-10s|\n' "left"        # left-aligned
printf '%05d\n' 42              # 00042 (zero-padded)
printf '%.2f\n' 3.14159         # 3.14

# date formatting
printf 'Today: %(%Y-%m-%d)T\n' -1

# to variable
printf -v today '%(%Y%m%d)T' -1
echo "$today"                    # 20260621

# repeat
printf '=%.0s' {1..40}; echo     # 40 equals signs
19

Expressions régulières

regex bash avec =~

=~ utilise ERE (regex étendu). Les groupes de capture peuplent BASH_REMATCH (index 0 = correspondance entière, 1+ = groupes). Quotez la chaîne mais PAS la regex (quoter la regex la rend littérale). Pour la correspondance insensible à la casse, utilisez shopt -s nocasematch. Les regex sont POSIX ERE — pas de \d, \w ; utilisez [0-9], [A-Za-z0-9_].

bash
s="user_42"

if [[ "$s" =~ ^([a-z]+)_([0-9]+)$ ]]; then
  echo "Match!"
  echo "Name: ${BASH_REMATCH[1]}"   # user
  echo "ID: ${BASH_REMATCH[2]}"     # 42
fi

# email-ish check
email="[email protected]"
if [[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
  echo "valid"
fi

# case-insensitive
shopt -s nocasematch
[[ "$s" =~ USER ]] && echo "yes"
shopt -u nocasematch

motifs regex grep

grep défaute à BRE (regex basique) où +, ?, |, () nécessitent des backslashes. -E active ERE (syntaxe plus propre). -P active PCRE avec \d, \w, \b, lookarounds — plus puissant mais moins portable (GNU grep uniquement). Pour les scripts qui doivent tourner sur macOS/BSD, préférez -E.

bash
# basic (BRE) — default
grep 'foo' file
grep 'a\.' file                 # literal dot (escape in BRE)
grep 'a*b' file                  # zero or more a

# extended (ERE) — -E
grep -E 'a+b' file               # one or more
grep -E 'a|b' file               # alternation
grep -E '(ab)+' file             # grouping
grep -E '[0-9]{3}' file          # quantifier

# perl (PCRE) — -P
grep -P '\d{3}' file            # digits
grep -P '\bword\b' file        # word boundary
grep -P '(?<=foo)bar' file       # lookbehind

# anchors
grep -E '^Error' file            # line start
grep -E 'done$' file             # line end
grep -E '^Error.*timeout$' file

substitution regex sed

sed utilise BRE par défaut (les groupes de capture nécessitent \( \), backrefs \1). -E bascule vers ERE (() et +, ?, | plus propres). Le flag I rend la substitution insensible à la casse (GNU sed). Utilisez un autre délimiteur (s|...|...|) quand le motif contient des slashes. [[:space:]] est portable pour les espaces blancs.

bash
# BRE (default)
sed 's/foo/bar/' file
sed 's/a\.b/x/' file            # literal dot
sed 's/a\(b\)c/\1/' file      # capture with backrefs

# ERE with -E
sed -E 's/a+b/x/' file
sed -E 's/(foo|bar)/X/g' file
sed -E 's/([0-9]{4})/Year: \1/' file

# case-insensitive
sed 's/foo/bar/I' file
sed 's/[fF][oO][oO]/bar/g' file

# common recipes
sed -E 's/^[[:space:]]+//' file  # trim leading whitespace
sed -E 's/[[:space:]]+$//' file  # trim trailing
sed -E 's/ +/ /g' file           # collapse spaces
sed -E 's#https?://[^ ]+##g' file # strip URLs

regex awk

awk utilise ERE. L'opérateur ~ teste une chaîne contre une regex. /pat1/, /pat2/ est un motif de plage (correspond de pat1 à pat2 inclus). gsub fait une substitution globale en place. match() de gawk avec un troisième argument capture les groupes dans un tableau — pratique mais non portable vers mawk/POSIX awk.

bash
# match operator ~
awk '$1 ~ /^[0-9]+/' file        # field 1 is numeric
awk '$0 !~ /debug/' file         # line doesn't contain
awk '/^Error/,/^$/' file         # from Error to blank line

# case-insensitive
awk 'BEGIN { IGNORECASE=1 } /error/' file
awk 'tolower($0) ~ /error/' file

# gsub (global substitute)
awk '{ gsub(/foo/, "bar"); print }' file
awk '{ gsub(/[0-9]+/, "N"); print }' file

# match with capture (gawk)
awk 'match($0, /id=([0-9]+)/, m) { print m[1] }' file

# split on regex
awk '{ n = split($0, parts, /[,;\t]/); print n }' file

recettes regex courantes

Ces recettes couvrent les tâches courantes d'extraction de texte. -o imprime uniquement la portion correspondante. Notez que les regex ne sont pas idéales pour parser HTML, JSON ou XML — utilisez des parseurs appropriés (jq pour JSON, xmllint pour XML). Pour IPv4, cette regex correspond au format mais pas aux plages valides (255.255.255.255 vs 999.999.999.999).

bash
# IPv4
grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log

# URL
grep -Eo 'https?://[^[:space:]]+' file

# email
grep -Eo '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}' file

# date YYYY-MM-DD
grep -E '[0-9]{4}-[0-9]{2}-[0-9]{2}' file

# hex color
grep -Eo '#[0-9A-Fa-f]{6}' file

# strong password (8+, upper, lower, digit)
grep -E '^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).{8,}$' file

# trim whitespace
sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' file

# extract domain from URL
sed -E 's#https?://([^/]+).*#\1#' file
20

Débogage

options set

set -e sort à toute erreur de commande (indispensable pour les scripts). set -u attrape les fautes de frappe dans les noms de variables. set -o pipefail fait qu'un pipe échoue si une partie échoue (sans lui, seul le code de sortie de la dernière commande compte). set -x trace l'exécution. Combiné comme -euo pipefail, c'est le « mode strict » — le défaut le plus sûr.

bash
#!/bin/bash
set -e                           # exit on error
set -u                           # error on unset variable
set -o pipefail                  # pipe fails if any command fails
set -x                           # print commands (trace)
# or combined:
set -euo pipefail

# shorthand
set -Eeuxo pipefail              # -E: ERR trap inherits

# strict mode for robust scripts
set -euo pipefail
IFS=$'\n\t'

# trace specific section
set -x
complex_logic
set +x

# debug a script
bash -x script.sh
bash -xv script.sh               # verbose + trace

traçage avec -x

set -x imprime chaque commande avant exécution, avec + comme préfixe défaut (PS4). Personnaliser PS4 pour inclure fichier:ligne:fonction rend les traces bien plus utiles pour le débogage. BASH_XTRACEFD redirige la trace vers un autre FD, pour séparer la sortie de trace de stdout/stderr.

bash
# trace entire script
bash -x script.sh

# trace inside script
set -x
echo "tracing on"
set +x

# customize PS4 (the trace prompt)
export PS4='+ ${BASH_SOURCE:-shell}:${LINENO}: ${FUNCNAME[0]:-main}() '
set -x
my_function
set +x

# trace to file
exec 5> /tmp/trace.log
BASH_XTRACEFD=5
set -x
# ... commands ...
set +x
exec 5>&-

gestion des erreurs et trap ERR

Le trap ERR se déclenche quand une commande échoue (avec set -e). $? à l'intérieur du trap donne le code de sortie défaillant. $LINENO montre où l'échec s'est produit. Le builtin caller imprime fichier:ligne:fonction — le boucler produit une stack trace. set -E fait que le trap ERR se déclenche aussi dans les fonctions (sinon il est désactivé en contexte de fonction).

bash
set -e
set -E                           # ERR trap inherits functions

on_error() {
  local exit_code=$?
  local line=$1
  echo "ERROR: command failed at line $line with exit code $exit_code" >&2
  echo "Stack:" >&2
  local i=0
  while caller $i >&2; do ((i++)); done
  exit $exit_code
}
trap 'on_error $LINENO' ERR

# now any failing command triggers the handler
false                            # triggers on_error

# caller builtin gives file:line:func
# stack trace via: while caller $i; do ((i++)); done

linter shellcheck

shellcheck est le linter de facto pour les scripts shell — attrape les problèmes de quoting, les vars inutilisées, les pièges courants, et suggère des idiomes. Exécutez-le sur chaque script. La correction la plus courante est SC2086 : quotez les variables pour empêcher le word-splitting et le globbing. Beaucoup de pipelines CI exigent que shellcheck passe.

bash
# install: apt install shellcheck (or brew, or use shellcheck.net)
# run:
shellcheck script.sh
shellcheck -x script.sh          # trust includes (no SC1090 warnings)
shellcheck -S warning script.sh  # only warnings+
shellcheck --exclude=SC2086 *.sh # exclude specific rules

# common warnings:
# SC2086: double-quote to prevent globbing (use "$var")
# SC2046: quote $(cmd) to prevent word splitting
# SC2034: unused variable
# SC2155: declare and assign separately (masks return code)
# SC2004: $/${} unnecessary in arithmetic

# in script: disable inline
# shellcheck disable=SC2086
echo $unquoted_var

# disable for next line
# shellcheck disable=SC2086
echo $args

logging et diagnostics

Un logger à niveaux (DEBUG/INFO/WARN/ERROR) avec timestamps et couleurs rend les scripts bien plus faciles à déboguer. Loggez toujours vers stderr pour que stdout reste propre pour les données. Supportez un flag --verbose (active set -x) et --dry-run (imprime les commandes sans exécuter). Codes couleur ANSI : 31=rouge, 33=jaune, 32=vert, 90=gris.

bash
# logging function with levels
log() {
  local level=$1; shift
  local msg="$*"
  local ts=$(date '+%Y-%m-%d %H:%M:%S')
  local color
  case $level in
    DEBUG) color='\033[90m' ;;
    INFO)  color='\033[32m' ;;
    WARN)  color='\033[33m' ;;
    ERROR) color='\033[31m' ;;
  esac
  printf "%b[%s] [%s] %s\033[0m\n" "$color" "$ts" "$level" "$msg" >&2
}

log INFO "Starting process"
log WARN "Disk space low"
log ERROR "Connection failed"

# verbose flag
[[ $VERBOSE ]] && set -x

# dry-run mode
if [[ $DRY_RUN ]]; then
  echo "Would: $cmd"
else
  eval "$cmd"
fi
21

Sécurité

quoting et injection

La règle de sécurité bash n°1 : quotez chaque expansion de variable. Les $var non quotés subissent le word-splitting et le globbing, menant à des bugs et de l'injection. Pour les commandes avec des args dynamiques, utilisez un tableau (cmd=(ls "$dir") ; "${cmd[@]}") au lieu d'une chaîne. N'évaluez jamais l'entrée utilisateur avec eval ou $() dans une chaîne shell.

bash
# ALWAYS quote variables
file="my file.txt"
rm $file                         # BAD: rm my file.txt (3 args!)
rm "$file"                       # GOOD: rm "my file.txt"

# use arrays for command + args
files=("a.txt" "b file.txt" "c.txt")
rm "${files[@]}"                # each file as separate arg, safely

# avoid eval
eval "echo $user_input"          # DANGEROUS
echo "$user_input"               # safe

# avoid command substitution in unsafe places
# BAD: filename from user, executed
cmd="ls $userdir"
$cmd                             # word-splitting + globbing

# GOOD: use array
cmd=(ls "$userdir")
"${cmd[@]}"

fichiers temporaires sûrs et secrets

mktemp crée des fichiers avec des noms imprévisibles et des permissions 600 — sûrs des attaques par symlink. Nettoyez toujours avec un trap. Lisez les secrets depuis des variables d'environnement, ne les codez jamais en dur. Évitez de passer les secrets comme args de ligne de commande (visibles via ps) — utilisez stdin, variables d'env, ou un fichier de config avec permissions 600.

bash
# safe temp files
tmp=$(mktemp)                    # atomic, unpredictable name
trap 'rm -f "$tmp"' EXIT

# safe temp dir
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT

# secrets: read from env, never hardcode
api_key="${API_KEY:?Set API_KEY env var}"

# don't leak secrets to process list (ps)
# BAD: curl -H "Authorization: Bearer $TOKEN" ...
# GOOD: pass via stdin or config file
curl -H @- <<EOF https://api.example.com
Authorization: Bearer $TOKEN
EOF

# restrict file perms for secrets
umask 077
echo "$api_key" > ~/.secrets/api
chmod 600 ~/.secrets/api

validation d'entrée

Validez toute entrée utilisateur avant usage. Les regex vérifient le format ; l'arithmétique vérifie les plages. Pour les chemins de fichiers, rejetez .. (traversal) et les chemins absolus sauf si explicitement autorisés. tr -cd supprime les caractères non dans l'ensemble autorisé — utile pour assainir les identifiants. La liste blanche (case) est plus sûre que la liste noire pour les actions autorisées.

bash
# validate numeric input
read -p "Enter a number: " n
if ! [[ "$n" =~ ^[0-9]+$ ]]; then
  echo "Not a number" >&2; exit 1
fi

# validate range
if (( n < 1 || n > 100 )); then
  echo "Out of range" >&2; exit 1
fi

# validate file path (no traversal)
path="$1"
case "$path" in
  *..*|/*) echo "Invalid path" >&2; exit 1 ;;
esac

# sanitize filename
safe=$(echo "$name" | tr -cd 'A-Za-z0-9._-')

# whitelist allowed values
case "$action" in
  start|stop|restart) ;;
  *) echo "Invalid action" >&2; exit 1 ;;
esac

abandon de privilège et setuid

Les scripts shell setuid sont non sûrs — la plupart des systèmes ignorent le bit setuid sur les scripts. Utilisez sudo avec une entrée sudoers stricte (commande spécifique, pas de mot de passe) à la place. Abandonnez root le plus tôt possible via exec sudo -u. Pour les capabilities (par ex., binder le port 80), utilisez setcap au lieu de tourner en root. chmod 700 garde les scripts privés au propriétaire.

bash
# drop privileges in a script running as root
if [[ $EUID -eq 0 ]]; then
  exec sudo -u appuser "$0" "$@"
fi

# check for root
if [[ $EUID -ne 0 ]]; then
  echo "Requires root" >&2; exit 1
fi

# setuid scripts are DANGEROUS — avoid them
# instead, use sudoers entries
# /etc/sudoers.d/myapp:
#   appuser ALL=(root) NOPASSWD: /usr/local/bin/myapp.sh

# restrict script permissions
chmod 700 script.sh              # only owner can run
chown appuser:appuser script.sh

# use capsh instead of full root for fine-grained caps
# setcap 'cap_net_bind_service=+ep' /usr/bin/myapp

signatures et intégrité

Vérifiez toujours les checksums et signatures des scripts téléchargés, surtout les installateurs exécutés en root. sha256sum -c compare à un hash connu bon. Les signatures GPG prouvent à la fois l'intégrité et l'authenticité (l'identité du signataire). Piper curl à sha256sum vous laisse examiner le hash avant exécution — mais le motif plus sûr est télécharger-puis-vérifier-puis-exécuter.

bash
# verify checksum
sha256sum -c file.sha256
# file.sha256 contains: <hash>  <filename>

# generate checksums
sha256sum release.tar.gz > release.tar.gz.sha256
sha256sum *.iso > isos.sha256

# GPG sign and verify
gpg --sign file.txt              # creates file.txt.gpg
gpg --verify file.txt.gpg
gpg --detach-sig --armor file.tar.gz
gpg --verify file.tar.gz.asc file.tar.gz

# download + verify in one go
curl -fsSL https://example.com/install.sh | sha256sum
curl -fsSL https://example.com/install.sh -o install.sh
sha256sum -c <(echo "expected_hash  install.sh")
22

Réseau

curl avancé

curl transfère des données via des URLs. -X définit la méthode, -d envoie des données, -H définit les en-têtes. -O sauvegarde avec le nom distant. -I récupère uniquement les en-têtes. -L suit les redirections. -u user:pass pour l'auth. -v pour verbeux.

bash
# POST with data
curl -X POST -d "name=Alice" https://api.example.com
# JSON POST
curl -H "Content-Type: application/json" \
     -d '{"name":"Alice"}' https://api.example.com
# Download with progress
curl -O https://example.com/file.zip
# Headers only
curl -I https://example.com

wget

wget télécharge des fichiers non interactivement. -m reflète les sites. -c reprend les téléchargements interrompus. -r récursion, -l limite la profondeur. -b tourne en arrière-plan. Mieux que curl pour les téléchargements récursifs.

bash
# Download file
wget https://example.com/file.zip
# Mirror website
wget -m https://example.com
# Resume download
wget -c https://example.com/largefile.zip
# Recursive download
wget -r -l 2 https://example.com
# Background
wget -b https://example.com/file.zip

ssh et scp

ssh se connecte aux machines distantes. -i spécifie un fichier de clé. -L crée un forward de port local. scp copie des fichiers via SSH. -r récursion des répertoires. Utilisez ssh-copy-id pour installer les clés. Configurez ~/.ssh/config pour les alias.

bash
# SSH connect
ssh user@host
# SSH with key
ssh -i ~/.ssh/id_rsa user@host
# Port forwarding
ssh -L 8080:localhost:80 user@host
# Copy file
scp file.txt user@host:/path/
# Copy directory
scp -r dir/ user@host:/path/

netstat et ss

netstat et ss montrent les connexions réseau. -t TCP, -u UDP, -l en écoute, -n numérique, -p processus. ss est plus rapide et plus détaillé que netstat. Utilisez pour trouver quel processus utilise un port ou diagnostiquer les problèmes de connexion.

bash
# List listening ports
netstat -tlnp
ss -tlnp
# Show all connections
netstat -an
ss -tunap
# Show routing table
netstat -r
# Show interface stats
netstat -i

ping et traceroute

ping teste l'atteignabilité et la latence (-c limite le compte). traceroute montre le chemin vers un hôte. dig interroge les enregistrements DNS (A, MX, NS, TXT). +short donne une sortie concise. Utilisez pour les diagnostics réseau et le dépannage DNS.

bash
# Check connectivity
ping -c 4 google.com
# Trace route
traceroute google.com
# DNS lookup
dig example.com
dig +short example.com
# Get DNS records
dig MX example.com
dig NS example.com
23

Scripting shell approfondi

Fonctions

Les fonctions regroupent des commandes. local crée des variables à portée de fonction. $1, $2 sont les arguments. return définit le statut de sortie (0-255). Capturez la sortie avec $(). Les fonctions doivent être définies avant usage. Utilisez source pour charger depuis des fichiers.

bash
greet() {
    local name="$1"
    echo "Hello, $name"
    return 0
}
greet "Alice"
# Return value via $?
result=$(greet "Bob")

Conditionnelles

[ ] est la commande test. -f fichier, -d répertoire, -z chaîne vide, -n non vide. = pour les chaînes, -eq/-ne/-gt/-lt pour les nombres. Quotez toujours les variables pour gérer les espaces. [[ ]] est bash-amélioré avec regex.

bash
if [ -f "/path/file" ]; then
    echo "File exists"
elif [ -d "/path" ]; then
    echo "Directory exists"
else
    echo "Not found"
fi
# String comparison
if [ "$str" = "hello" ]; then ... fi
# Numeric
if [ "$num" -gt 10 ]; then ... fi

Boucles

for itère sur des listes. Le for style C utilise (( )). while lit jusqu'à ce que la condition échoue. while read traite les fichiers ligne par ligne sûrement. Quotez toujours les variables. Utilisez find pour l'itération récursive de fichiers.

bash
# For loop
for i in 1 2 3; do
    echo $i
done
# C-style
for ((i=0; i<5; i++)); do echo $i; done
# While loop
while read line; do
    echo "$line"
done < file.txt
# Iterate files
for f in *.txt; do process "$f"; done

Tableaux

Les tableaux utilisent des parenthèses. ${arr[@]} s'étend à tous les éléments. ${#arr[@]} est la longueur. Quotez toujours « ${arr[@]} » pour gérer les espaces. declare -A crée des tableaux associatifs (bash 4+). Utilisez read -a pour diviser en tableau.

bash
# Declare array
fruits=("apple" "banana" "cherry")
# Access
echo ${fruits[0]}      # apple
echo ${fruits[@]}      # all
echo ${#fruits[@]}     # length
# Iterate
for fruit in "${fruits[@]}"; do
    echo "$fruit"
done
# Associative array
declare -A config
config[key]="value"

Gestion des erreurs

set -e sort en erreur, -u sur vars non définies, pipefail attrape les échecs de pipe. trap ERR gère les erreurs. trap EXIT exécute le nettoyage. command -v vérifie si une commande existe. Commencez toujours les scripts avec set -euo pipefail.

bash
set -e  # Exit on error
set -u  # Error on undefined variable
set -o pipefail  # Pipeline fails if any command fails
set -euo pipefail  # All three (recommended)
trap 'echo "Error on line $LINENO"' ERR
trap 'cleanup' EXIT
# Check command result
if ! command -v docker &> /dev/null; then
    echo "docker not installed"
fi
24

Opérations sur fichiers approfondies

find avancé

find cherche les fichiers par critères. -type f/d pour fichiers/répertoires. -size +1M plus grand que 1 Mo. -mtime -7 modifié dans les 7 jours. -exec exécute une commande sur chaque correspondance. {} est le nom du fichier ; \; termine la commande.

bash
# By name
find . -name "*.txt"
# By type and size
find . -type f -size +1M
# By modification time
find . -mtime -7  # Modified in last 7 days
# Execute command
find . -name "*.log" -exec gzip {} \;
# Delete
find . -name "*.tmp" -delete

tar et compression

tar combine les fichiers ; -z compresse avec gzip. -c create, -x extract, -t list, -v verbose, -f filename. Pour .tar.bz2 utilisez -j. zip/unzip gèrent le format ZIP. Utilisez -C pour extraire vers un répertoire spécifique.

bash
# Create tar.gz
tar -czvf archive.tar.gz dir/
# Extract
tar -xzvf archive.tar.gz
# List contents
tar -tzvf archive.tar.gz
# zip/unzip
zip -r archive.zip dir/
unzip archive.zip

rsync

rsync synchronise efficacement les fichiers, ne transférant que les différences. -a mode archive (préserve les attributs), -v verbeux, -z compresse. --delete supprime les fichiers non dans la source. La barre oblique finale sur src/ compte : avec, copie le contenu ; sans, copie le répertoire.

bash
# Sync local
rsync -av src/ dest/
# Sync to remote
rsync -avz src/ user@host:/path/
# Delete files not in source
rsync -av --delete src/ dest/
# Dry run
rsync -av --dry-run src/ dest/
# Exclude
rsync -av --exclude='*.log' src/ dest/

permissions de fichier

Permissions : r=4, w=2, x=1. 755 = rwxr-xr-x (propriétaire complet, autres lecture/exécution). 644 = rw-r--r-- (fichiers). u/g/o/a = user/group/other/all. chmod -R récursion. chown change la propriété.

bash
# Symbolic
chmod u+x file      # User execute
chmod g-w file      # Group write off
chmod a=r file      # All read only
# Numeric
chmod 755 file      # rwxr-xr-x
chmod 644 file      # rw-r--r--
# Change owner
chown user:group file
# Recursive
chmod -R 755 dir/

ln et symlinks

Les hard links pointent vers le même inode (même système de fichiers uniquement). Les liens symboliques (-s) pointent vers un chemin. Les hard links survivent à la suppression de l'original. Les symlinks cassent si la cible bouge. Utilisez readlink pour résoudre les symlinks.

bash
# Hard link
ln file.txt hardlink.txt
# Symbolic link
ln -s /path/to/target symlink
# Find broken symlinks
find . -type l ! -exec test -e {} \; -print
# Copy symlink target
cp -L symlink dest/
25

Pièges courants

Variables non quotées

Les variables non quotées se divisent sur les espaces et glob. $(ls) casse avec les noms de fichiers contenant des espaces. Utilisez les globs (*.txt) ou find -print0 avec read -d. Quotez toujours les variables : "$var". Utilisez IFS= read -r pour la lecture de ligne sûre.

bash
# BUG: breaks with spaces
for f in $(ls); do echo "$f"; done
# GOOD: handle spaces
for f in *.txt; do echo "$f"; done
# Or with find
find . -name "*.txt" -print0 | while IFS= read -r -d '' f; do
    echo "$f"
done

cp vs mv

cp copie, mv déplace. Les deux peuvent écraser sans avertissement. Utilisez -i pour demander avant d'écraser. Utilisez -n pour ne jamais écraser. mv est atomique sur le même système de fichiers, utile pour les verrous et les mises à jour atomiques.

bash
# cp: copy (original remains)
cp file.txt backup.txt
# mv: move/rename (original removed)
mv file.txt newname.txt
# Common mistake: overwriting
cp important.txt /tmp/  # If /tmp/important.txt exists, overwritten!
# Use -i for safety
cp -i file.txt /tmp/

Dangers de rm

rm -rf est dangereux, surtout avec des variables. Les variables vides causent rm -rf /. Quotez et vérifiez toujours les variables. Envisagez trash-cli pour une suppression récupérable. N'exécutez jamais rm -rf / ou avec sudo négligemment.

bash
# DANGEROUS: recursive force
rm -rf /  # Never do this!
rm -rf $VAR/  # If $VAR is empty, becomes rm -rf /
# Safer: check first
if [ -n "$VAR" ]; then
    rm -rf "$VAR/"
fi
# Use trash-cli instead
trash-put file.txt

Shebang

Le shebang (#!) spécifie l'interpréteur. #!/usr/bin/env bash est plus portable que #!/bin/bash. Utilisez /bin/sh pour une portabilité maximale (évite les bashisms). Rendez toujours les scripts exécutables avec chmod +x.

bash
#!/bin/bash          # bash script
#!/usr/bin/env bash  # Portable bash
#!/usr/bin/env python3  # Python
#!/bin/sh            # POSIX sh (more portable)
# Make executable
chmod +x script.sh
./script.sh

Statut de sortie

$? contient le statut de sortie de la dernière commande. 0 = succès, non-zéro = échec. VVérifiez immédiatement avant qu'une autre commande ne l'écrase. Mieux vaut utiliser if commande directement. Sortez toujours avec des codes de statut significatifs dans les scripts.

bash
# Check last command
if [ $? -eq 0 ]; then
    echo "Success"
fi
# Better: direct check
if command; then
    echo "Success"
else
    echo "Failed"
fi
# Exit with status
exit 0  # Success
exit 1  # Failure

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.