06 - Pipes et redirections
Ce que tu vas apprendre
- Les pipes pour chainer des commandes
- stdout et stderr (descripteurs 1 et 2)
- Les redirections (>, >>, 2>, /dev/null)
- tee, xargs, la substitution de commande et les here documents
Prerequisites
Connaitre les bases du terminal et les éditeurs. Voir l'article sur les éditeurs.
La philosophie Unix en une phrase
"Fais une seule chose et fais-la bien." Chaque commande Linux est un petit outil specialise. La puissance vient de la combinaison de ces outils. Le pipe (|) est la colle qui les relie. C'est probablement le concept le plus fondamental de Linux, et celui qui séparé un utilisateur basique d'un utilisateur efficace.
Le pipe : chainer des commandes
Le pipe (|) prend la sortie d'une commande et l'envoie comme entree a la suivante :
bash# Sans pipe : deux etapes
ps aux > /tmp/processes.txt
grep "node" /tmp/processes.txt
# Avec pipe : une seule ligne
ps aux | grep "node"
# Chainer autant qu'on veut
cat /var/log/nginx/access.log | grep "POST" | awk '{print $7}' | sort | uniq -c | sort -rn | head -10
# Traduction : dans les logs nginx, prends les POST, extrais l'URL,
# trie, compte les doublons, trie par nombre decroissant, garde le top 10
J'utilise des pipes 50 fois par jour. C'est le reflexe numero un a développer. Quand tu te retrouves a écrire un script Python de 30 lignes pour traiter du texte, demande-toi si une ligne de pipes ne ferait pas le meme travail.
stdout et stderr : les deux sorties
Chaque commande a deux flux de sortie :
stdin (0) : l'entree standard (ce que tu tapes, ou ce qui vient d'un pipe)
stdout (1) : la sortie standard (les resultats normaux)
stderr (2) : la sortie d'erreur (les messages d'erreur)
Par défaut, stdout et stderr s'affichent tous les deux dans le terminal. Mais ce sont deux flux distincts :
bash# Cette commande produit du stdout ET du stderr
$ find / -name "*.conf" 2>/dev/null
# 2>/dev/null redirige stderr vers le neant
# On ne voit que les resultats, pas les "Permission denied"
# Prouver que ce sont deux flux differents
$ ls fichier_existant fichier_inexistant
fichier_existant # stdout
ls: cannot access 'fichier_inexistant': No such file or directory # stderr
Les redirections
Les redirections envoient les flux vers des fichiers au lieu du terminal :
bash# stdout vers un fichier (ecrase)
echo "hello" > output.txt
# stdout vers un fichier (ajoute a la fin)
echo "world" >> output.txt
# stderr vers un fichier
commande_qui_plante 2> errors.log
# stdout ET stderr vers le meme fichier
commande 2>&1 > all_output.log
# ou plus simplement :
commande &> all_output.log
# stderr vers stdout (pour le passer dans un pipe)
commande 2>&1 | grep "error"
# Tout jeter (silencer une commande)
commande > /dev/null 2>&1
# ou :
commande &> /dev/null
/dev/null : le trou noir
/dev/null est un fichier special qui avale tout ce qu'on lui envoie. C'est l'équivalent de "je m'en fiche de cette sortie" :
bash# Cas courant : cron jobs (tu ne veux pas de mail pour chaque execution)
*/5 * * * * /opt/scripts/backup.sh > /dev/null 2>&1
# Tester si une commande reussit sans voir la sortie
if grep -q "pattern" fichier.txt; then
echo "Trouve"
fi
# Le -q (quiet) fait la meme chose que > /dev/null pour grep
tee : écrire et afficher en meme temps
tee duplique le flux : il écrit dans un fichier ET affiche dans le terminal :
bash# Sans tee : tu choisis entre voir et sauvegarder
commande > output.txt # Sauvegarde, mais tu ne vois rien
commande # Tu vois, mais tu ne sauvegardes pas
# Avec tee : les deux
commande | tee output.txt # Affiche ET sauvegarde
commande | tee -a output.txt # Affiche ET ajoute au fichier
commande 2>&1 | tee debug.log # Capture stdout+stderr
# Cas reel : suivre un build et garder les logs
npm run build 2>&1 | tee build.log
# Tu vois le build en temps reel ET tu as les logs apres
Un usage malin de tee avec sudo :
bash# Ca ne marche PAS (le > est execute par ton user, pas root)
sudo echo "127.0.0.1 monsite.local" > /etc/hosts
# Ca marche (tee est execute par sudo)
echo "127.0.0.1 monsite.local" | sudo tee -a /etc/hosts
xargs : transformer stdin en arguments
xargs prend des lignes d'entree et les transforme en arguments d'une commande :
bash# Supprimer tous les fichiers .log trouves par find
find /tmp -name "*.log" | xargs rm
# Plus sur avec les noms de fichiers contenant des espaces
find /tmp -name "*.log" -print0 | xargs -0 rm
# Executer une commande pour chaque ligne
cat urls.txt | xargs -I {} curl -s {}
# {} est remplace par chaque ligne de urls.txt
# Limiter le nombre d'arguments par commande
find . -name "*.js" | xargs -n 5 echo
# Passe 5 fichiers a la fois
# Paralleliser
find . -name "*.png" | xargs -P 4 -I {} convert {} -resize 50% resized/{}
# -P 4 : 4 processus en parallele
# Cas reel : reformater tous les fichiers TypeScript
find src -name "*.ts" | xargs prettier --write
Substitution de commande
$(commande) exécuté une commande et inséré sa sortie dans une autre commande :
bash# Date dans un nom de fichier
cp database.sql "backup_$(date +%Y%m%d_%H%M%S).sql"
# Cree backup_20260329_103045.sql
# Nombre de fichiers
echo "Il y a $(find . -name '*.ts' | wc -l) fichiers TypeScript"
# Utiliser le resultat dans une variable
BRANCH=$(git branch --show-current)
echo "Tu es sur la branche $BRANCH"
# IP du serveur
SERVER_IP=$(hostname -I | awk '{print $1}')
# Ancienne syntaxe (backticks) - eviter, moins lisible
BRANCH=`git branch --show-current` # Fonctionne mais prefere $()
Here documents (heredoc)
Les heredocs permettent d'envoyer plusieurs lignes de texte a une commande :
bash# Ecrire un bloc de texte dans un fichier
cat <<EOF > config.yaml
server:
host: "0.0.0.0"
port: 8080
database:
url: "postgres://localhost:5432/mydb"
EOF
# Avec sudo
sudo tee /etc/nginx/sites-available/monsite.conf <<EOF
server {
listen 80;
server_name monsite.com;
root /var/www/monsite;
}
EOF
# Heredoc sans expansion de variables (quotes autour de EOF)
cat <<'EOF' > script.sh
echo "La variable est $HOME"
# $HOME ne sera PAS remplace, le texte sera litteral
EOF
Combiner des commandes
Trois opérateurs pour enchaîner des commandes :
bash# && : execute la suivante SI la precedente a reussi
mkdir -p /opt/myapp && cd /opt/myapp && git clone repo.git .
# Si mkdir echoue, le reste ne s'execute pas
# || : execute la suivante SI la precedente a echoue
test -f config.yaml || cp config.yaml.example config.yaml
# Si le fichier n'existe pas, on copie l'exemple
# ; : execute quoi qu'il arrive
echo "Debut" ; commande_risquee ; echo "Fin"
# "Fin" s'affiche meme si commande_risquee echoue
# Combiner les trois
test -d /opt/myapp && echo "Existe" || echo "N'existe pas"
# Cas reel : deploiement
git pull && npm install && npm run build && pm2 restart all || echo "Deploy FAILED"
Exemples du monde réel
Quelques pipelines que j'utilise dans mon travail quotidien :
bash# Trouver les 10 plus gros fichiers
du -ah . | sort -rh | head -10
# Compter les lignes de code par extension
find src -type f | sed 's/.*\.//' | sort | uniq -c | sort -rn
# Voir les ports ouverts et les processus associes
sudo ss -tlnp | awk 'NR>1 {print $4, $7}' | sort
# Extraire les emails uniques d'un fichier de log
grep -oE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' log.txt | sort -u
# Monitorer un fichier de log en filtrant les erreurs
tail -f /var/log/app.log | grep --line-buffered "ERROR"
Pour des exemples avances de pipelines dans un contexte DevOps, paltemps.fr propose des cas d'usage supplementaires.
Résumé
- Le pipe
|est le concept central : chaîne des commandes simples pour des résultats puissants - stdout (1) et stderr (2) sont deux flux separes, redirige-les indépendamment
> fichierecrase,>> fichierajoute,2>redirige les erreursteepour voir et sauvegarder en meme tempsxargspour transformer des lignes en arguments de commande$()pour insérer le résultat d'une commande dans une autre&&pour enchaîner si succes,||pour le fallback
Precedent : Editeurs | Suivant : grep et find en profondeur