Bash
En entretien, on peut te demander d'écrire un petit script ou une commande d'une ligne au tableau. Ce qu'on regarde : est-ce que ton script s'arrête proprement en cas d'erreur, est-ce que tes variables sont protégées, est-ce qu'il est lisible.
L'en-tête d'un script robuste
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
-e: arrêt à la première commande qui échoue.-u: erreur si on utilise une variable non définie (une faute de frappe dans$REPERTOIREne donne pas silencieusement une chaîne vide).-o pipefail: un pipe échoue si n'importe quelle commande échoue, pas seulement la dernière.
Limites de set -e à connaître : il ne s'applique pas aux commandes testées dans un if, un while, ou avant && / ||. Et une commande dans une substitution $(...) assignée avec local (local x=$(cmd)) ne fait pas échouer le script, car local réussit. Écrire local x; x=$(cmd).
Le piège n°1 : les guillemets
fichier="mon rapport.txt"
rm $fichier # supprime "mon" et "rapport.txt"
rm "$fichier" # supprime le bon fichier
Toujours "$var", toujours "$@" pour transmettre les arguments. Le jour où un nom contient un espace, le script sans guillemets fait des dégâts. Le pire cas connu : rm -rf "$DIR/"* avec DIR vide. Protection : rm -rf "${DIR:?DIR non défini}/"*.
Variables
nom="valeur" # pas d'espace autour du =
echo "${nom}"
echo "${nom:-defaut}" # valeur par défaut si vide ou non définie
echo "${nom:?message}" # erreur et sortie si vide
echo "${#nom}" # longueur
echo "${fichier%.log}" # retire le suffixe .log
echo "${chemin##*/}" # garde ce qui suit le dernier /, comme basename
readonly SEUIL=90
Codes de retour
Chaque commande renvoie un code : 0 = succès, autre chose = échec. $? contient le code de la dernière commande.
if grep -q "ERROR" app.log; then
echo "Erreurs trouvées"
fi
commande || { echo "échec" >&2; exit 1; }
Un script de supervision doit renvoyer un code non nul quand il détecte un problème. C'est ce que lisent cron, systemd, la CI ou Nagios.
Tests
[[ -f "$f" ]] # fichier existe
[[ -d "$d" ]] # dossier existe
[[ -z "$v" ]] # chaîne vide
[[ -n "$v" ]] # chaîne non vide
[[ "$a" == "$b" ]]
(( n > 90 )) # comparaison numérique
[[ "$s" =~ ^[0-9]+$ ]] # regex
Préférer [[ ]] à [ ] : moins de pièges avec les guillemets et les chaînes vides.
Boucles
for srv in nginx sshd chronyd; do
systemctl is-active --quiet "$srv" || echo "$srv KO"
done
while IFS= read -r ligne; do
echo "$ligne"
done < fichier.txt
for f in /var/log/*.log; do
[[ -e "$f" ]] || continue # si aucun fichier ne correspond
gzip "$f"
done
Ne jamais faire for f in $(ls) : ça casse sur les espaces.
Fonctions
log() {
printf '%s [%s] %s\n' "$(date -Is)" "$1" "$2" >&2
}
check_disk() {
local mount="$1" seuil="$2" usage
usage=$(df --output=pcent "$mount" | tail -1 | tr -dc '0-9')
if (( usage >= seuil )); then
log WARN "$mount à ${usage}%"
return 1
fi
}
Nettoyage avec trap
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
Le dossier temporaire est supprimé quoi qu'il arrive : fin normale, erreur, Ctrl+C.
Arguments
usage() { echo "Usage: $0 [-v] -e <env>" >&2; exit 2; }
verbose=false
env=""
while getopts ":ve:" opt; do
case "$opt" in
v) verbose=true ;;
e) env="$OPTARG" ;;
*) usage ;;
esac
done
[[ -n "$env" ]] || usage
$0 nom du script, $1... arguments, $# nombre d'arguments, $@ tous les arguments.
Traitement de texte
grep -E "ERROR|WARN" app.log
grep -c "500" access.log # nombre de lignes
grep -rn "password" /etc/ 2>/dev/null
awk '{print $1}' access.log # 1re colonne
awk -F: '$3 >= 1000 {print $1}' /etc/passwd # utilisateurs "humains"
awk '{s+=$10} END {print s}' access.log # somme d'une colonne
sed -i 's/ancien/nouveau/g' fichier
sed -n '10,20p' fichier # lignes 10 à 20
cut -d, -f2 fichier.csv
sort | uniq -c | sort -rn | head # top des occurrences
xargs -n1 -P4 # parallélisme
Le top 10 des IP d'un access log, question classique :
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
Les codes HTTP 5xx de la dernière heure, par URL :
awk '$9 ~ /^5/ {print $7}' access.log | sort | uniq -c | sort -rn | head
Redirections
cmd > out.log 2>&1 # stdout et stderr dans le fichier
cmd &> out.log # pareil, syntaxe bash
cmd 2>/dev/null # jeter les erreurs
cmd | tee -a out.log # afficher et écrire
cat <<EOF > config.ini
host=${HOST}
EOF
Planification
crontab -e
# min heure jour-du-mois mois jour-semaine
*/5 * * * * /opt/scripts/check.sh >> /var/log/check.log 2>&1
0 2 * * 0 /opt/scripts/backup.sh
Pièges de cron : le PATH y est minimal (utiliser des chemins absolus), pas de variables d'environnement de ta session. Alternative moderne : les timers systemd, avec logs dans journald et gestion des dépendances.
Qualité
- shellcheck sur tous les scripts, en CI.
- Au-delà de ~100 lignes, ou dès qu'il faut manipuler du JSON ou des structures, passer en Python.
bash -x script.shpour tracer l'exécution.
Questions d'entretien à préparer
- Que fait
set -euo pipefail? - Pourquoi mettre les variables entre guillemets ?
- Écris une commande qui donne le top 10 des IP d'un access log.
- Comment faire un nettoyage garanti à la fin d'un script ? (
trap ... EXIT) - Différence entre
$@et$*? ("$@"garde chaque argument séparé) - Ton script marche à la main mais pas en cron, pourquoi ?