J’utilise les systèmes Unix et Linux depuis près de 20 ans. Lorsque je me suis retrouvé avec un ordinateur portable Windows pendant un certain temps, je me suis senti perdu et j’ai rapidement installé le sous-système Windows pour Linux. Et pourtant, le nombre de commandes que je connais et que j’utilise régulièrement est étonnamment court : cd, ls, rm, pwd, mkdir, source, node, npm et bien sûr, git. Même alors, je ne pense pas que je vraiment “sais” crétin.
Dans son excellent livre « Don’t Make Me Think Revisité», Steve Krug décrit comment
Face à toute technologie, très peu de gens prennent le temps de lire les instructions. Au lieu de cela, nous allons de l’avant et nous débrouillons, inventant nos propres histoires vaguement plausibles sur ce que nous faisons et pourquoi cela fonctionne.
Le livre de Krug porte sur la convivialité du Web et des appareils mobiles, mais ce qu’il avance ici s’applique à tous les domaines technologiques. Et on peut dire sans se tromper que je me débrouille avec Unix. Si vous me demandiez si quelque chose était une commande Unix ou une partie du shell, je hausserais probablement les épaules et dirais quelque chose comme « Je… je ne sais pas ça ! ? ».
Jusqu’à récemment, StackOverflow a toujours été ma béquille sur laquelle m’appuyer. D’après la fréquence avec laquelle il apparaît dans les réponses, je sais que find est une commande très utile et polyvalente. Cependant, les chances que je me souvienne comment l’utiliser sans le rechercher sont impossibles à distinguer de 0.
Mais nous disposons désormais d’alternatives sous la forme de divers grands modèles de langage ou, pour être plus précis, d’assistants IA multimodaux (comme Claude et ChatGPT) et d’agents (comme Claude Code et Codex). Je me décrirais comme indépendant de l’IA : je vois des avantages, je m’inquiète des problèmes. C’est donc avec un certain (je dirais sain) degré de scepticisme que j’ai commencé à utiliser Claude pour m’aider à écrire certaines fonctions shell afin d’automatiser les tâches que je fais ou que j’aimerais faire régulièrement.
Je dis « j’ai » écrit. La réalité est bien sûr que j’ai demandé à Claude d’écrire et de réécrire des choses pour moi et ensuite je me suis demandé pourquoi il faisait ce qu’il faisait. Cela donne probablement l’impression que j’évaluais Claude pour un emploi, mais c’est en fait que je veux comprendre ce qui a été écrit et, comme nous l’avons déjà évoqué, je ne suis pas très doué dans ce domaine.
Première tâche : réparer un problème commun git erreur
L’une de mes erreurs les plus courantes avec git est de valider des modifications dans une branche (généralement principale) qui étaient censées être appliquées à une nouvelle branche de fonctionnalités. Je sais un moyen de s’en remettre : créer la nouvelle branche que je voulais créer depuis le début ; faire un rebase interactif pour supprimer le commit de main ; passez à la nouvelle branche de fonctionnalités que je viens de créer. Il n’y a pas beaucoup d’étapes dans le processus, mais c’est quand même un peu compliqué. Demandons à Claude :
Tim
Si tu es con [commit] vers une mauvaise branche, existe-t-il un moyen rapide de déplacer la modification vers une nouvelle branche et de la supprimer du dernier commit de la branche actuelle ?
Claude
Oui, en supposant que vous n’ayez pas encore poussé :
# Create and switch to the correct branch from current state git switch -c correct-branch # Go back to the wrong branch and undo the commit (keeps changes staged) git switch wrong-branch git reset --soft HEAD~1 # Discard the changes git restore --staged . git restore .
Ou si vous voulez que cela reste encore plus simple — si le commit est uniquement sur la mauvaise branche et n’est pas poussé :
git switch -c correct-branch git switch wrong-branch git reset --hard HEAD~1
Le switch -c crée la nouvelle branche pointant vers le même commit, afin que votre travail y soit conservé. Ensuite, vous supprimez simplement la mauvaise branche avec reset –hard.
Ici, nous rencontrons quelques problèmes.
- Aucune des suggestions de Claude n’a fait l’étape finale de ce que je voulais : me ramener à la nouvelle branche correcte. C’est entièrement de ma faute. J’ai oublié de demander (et je ne l’ai pas remarqué du tout). Je fais beaucoup ce genre de choses.
- Même pour des tâches simples, les réponses de Claude peuvent être assez verbeuses.
Le problème ici n’est pas pratique concernant l’utilisation de Claude ou d’un agent alternatif, le vrai problème est qu’il est assez difficile d’écrire un article de blog d’une longueur raisonnable qui va réellement n’importe où tout en racontant avec précision la conversation que vous avez avec Claude. Ce qui suit est donc une paraphrase plutôt qu’une transcription ; raccourci pour le bien de votre santé mentale et de la mienne.
Tim
Puis-je utiliser git switch à la place ?
Claude
Oui, et c’est plus propre. git switch - vous évite de taper explicitement le mauvais nom de branche.
Tim
On dirait que c’est le genre de chose que je devrais transformer en fonction Bash
Claude
Convenu. Quelque chose comme ceci (avec un contrôle de sécurité supplémentaire) :
git-move-last() {
if [ -z "$1" ]; then
echo "Usage: git-move-last <new-branch-name>"
return 1
fi
git switch -c "$1"
git switch -
git reset --hard HEAD~1
}
Tim
J’ai opté pour une décapitation. Si je voulais passer à la nouvelle branche à la fin, j’ajoute simplement un autre commutateur git – ?
Claude
Ha 🪓.
Exactement:
git-behead() {
if [ -z "$1" ]; then
echo "Usage: git-behead <new-branch-name>"
return 1
fi
git switch -c "$1"
git switch -
git reset --hard HEAD~1
git switch -
}
Tim
Ok, encore une idée. Supposons que je n’ai pas regardé ce que je fais et que je souhaite supprimer plus que le dernier commit dans une nouvelle branche. Pouvons-nous recréer ce qui précède mais avec un nombre de validations facultatif dont la valeur par défaut est 1 ?
Claude
git-behead() {
if [ -z "$1" ]; then
echo "Usage: git-behead <new-branch-name> [commit-count]"
return 1
fi
local count="${2:-1}"
git switch -c "$1"
git switch -
git reset --hard HEAD~"$count"
git switch -
}
Tim
Pourriez-vous commenter quelque chose qui ne vous semble pas explicite ?
Claude
git-behead() {
if [ -z "$1" ]; then
echo "Usage: git-behead <new-branch-name> [commit-count]"
return 1
fi
local count="${2:-1}" # default to 1 commit if not specified
git switch -c "$1" # create new branch at current HEAD
git switch - # switch back to original branch
git reset --hard HEAD~"$count" # remove commits from original branch
git switch - # switch to new branch
}
Mettre cela dans mon fichier .bashrc me donne une commande simple que je peux appeler chaque fois que je fais l’erreur que je fais régulièrement et un argument facultatif à utiliser chaque fois que je n’ai pas prêté attention à la branche sur laquelle je me trouve depuis une période prolongée. C’est aussi assez court pour que je pense suivre chaque ligne, malgré ma connaissance limitée de Bash, et il y a des commentaires là-bas pour quand le futur moi a oublié. Cette conversation a duré environ quinze minutes et comprenait plus de questions que ce que j’ai inclus ici, plus le temps que j’ai passé à exécuter divers tests ad hoc avec les différentes itérations de la fonction. Cela semble être une utilisation assez efficace de mon temps.
Deuxième tâche : répertorier les fichiers image
Je fais beaucoup de visualisation de données, je m’occupe des illustrations de ce site et je prends des photos pour nos conférences. Bref, je traite beaucoup d’images dans des formats variés. Ce serait bien de pouvoir lister uniquement les images et leurs propriétés clés dans un emplacement spécifique avec une fonction simple. Demandons à Claude :
Tim
Pouvez-vous m’écrire un script Bash pour afficher une liste d’images dans un répertoire indiquant le nom du fichier, la largeur, la hauteur et la taille (en Ko) et rien d’autre ?
Claude
Voici un script propre utilisant l’identification d’ImageMagick (largement disponible) avec une solution de secours pour la taille du fichier :
#!/usr/bin/env bash
DIR="${1:-.}"
if ! command -v identify &>/dev/null; then
echo "Error: imagemagick is required (install with: brew install imagemagick / apt install imagemagick)" >&2
exit 1
fi
shopt -s nullglob nocaseglob
files=("$DIR"/*.{jpg,jpeg,png,gif,webp,bmp,tiff,tif,avif})
shopt -u nullglob nocaseglob
if [[ ${#files[@]} -eq 0 ]]; then
echo "No images found in: $DIR" >&2
exit 0
fi
printf "%-40s %8s %8s %10s\n" "FILENAME" "WIDTH" "HEIGHT" "SIZE (kB)"
printf "%-40s %8s %8s %10s\n" "--------" "-----" "------" "---------"
for f in "${files[@]}"; do
name="$(basename "$f")"
read -r width height < <(identify -format "%w %h" "$f" 2>/dev/null | head -1)
size_kb=$(awk "BEGIN { printf \"%.1f\", $(wc -c < "$f") / 1024 }")
printf "%-40s %8s %8s %10s\n" "$name" "$width" "$height" "$size_kb"
done
Formats pris en charge : jpg, jpeg, png, gif, webp, bmp, tiff, avif.
Exemple de sortie :
FILENAME WIDTH HEIGHT SIZE (kB) -------- ----- ------ --------- banner.png 1200 630 84.3 avatar.jpg 400 400 32.1 hero.webp 1920 1080 210.7
J’ai demandé un scénario, j’ai reçu un scénario. Je voulais une fonction. Celui-ci est, encore une fois, sur moi.
Tim
Désolé, pouvons-nous en faire une fonction plutôt qu’un script ?
Claude
Les principaux changements : shebang supprimé, exit remplacé par return et les variables sont désormais locales. Pour l’utiliser, recherchez-le dans votre shell et appelez-le normalement :
imgls() {
local dir="${1:-.}"
if ! command -v identify &>/dev/null; then
echo "Error: imagemagick is required (install with: brew install imagemagick / apt install imagemagick)" >&2
return 1
fi
shopt -s nullglob nocaseglob
local files=("$dir"/*.{jpg,jpeg,png,gif,webp,bmp,tiff,tif,avif})
shopt -u nullglob nocaseglob
if [[ ${#files[@]} -eq 0 ]]; then
echo "No images found in: $dir" >&2
return 0
fi
printf "%-40s %8s %8s %10s\n" "FILENAME" "WIDTH" "HEIGHT" "SIZE (kB)"
printf "%-40s %8s %8s %10s\n" "--------" "-----" "------" "---------"
local f name width height size_kb
for f in "${files[@]}"; do
name="$(basename "$f")"
read -r width height < <(identify -format "%w %h" "$f" 2>/dev/null | head -1)
size_kb=$(awk "BEGIN { printf \"%.1f\", $(wc -c < "$f") / 1024 }")
printf "%-40s %8s %8s %10s\n" "$name" "$width" "$height" "$size_kb"
done
}
La conversation continue un peu à partir de là et la fonction devient de plus en plus détaillée. Il serait fastidieux de montrer toute la conversation mais de résumer « nous » :
- Ajoutez la prise en charge de SVG, y compris l’extraction des dimensions du
viewBoxattribut. - Correction d’un bug avec les dimensions GIF.
- Ajustez le texte de l’en-tête.
- Ajoutez la prise en charge de la recherche récursive dans un répertoire.
- Corrigez la mise en page lors d’une recherche récursive.
- Arrondissez les valeurs dans la colonne TAILLE
- Ajoutez des commentaires.
Le résultat final :
imgls() {
local recursive=0
# Parse flags — currently only -r (recursive) is supported
while getopts ":r" opt; do
case $opt in
r) recursive=1 ;;
*) echo "Usage: imgls [-r] [directory]" >&2; return 1 ;;
esac
done
# Shift past any parsed flags so $1 is the directory argument
shift $((OPTIND - 1))
OPTIND=1
# Use first argument as directory, default to current directory if omitted
local dir="${1:-.}"
# identify is provided by imagemagick and used to read raster image dimensions
if ! command -v identify &>/dev/null; then
echo "Error: imagemagick is required (install with: brew install imagemagick / apt install imagemagick)" >&2
return 1
fi
local exts=("jpg" "jpeg" "png" "gif" "webp" "bmp" "tiff" "tif" "avif" "svg")
local files=()
if [[ $recursive -eq 1 ]]; then
# Build a find command matching all extensions, case-insensitively
local find_args=("$dir" "-type" "f" "(")
for i in "${!exts[@]}"; do
[[ $i -gt 0 ]] && find_args+=("-o")
find_args+=("-iname" "*.${exts[$i]}")
done
find_args+=(")")
while IFS= read -r f; do
files+=("$f")
done < <(find "${find_args[@]}" | sort)
else
# nullglob: unmatched globs expand to nothing instead of being treated as literals
# nocaseglob: match extensions case-insensitively (e.g. .JPG as well as .jpg)
shopt -s nullglob nocaseglob
files=("$dir"/*.{jpg,jpeg,png,gif,webp,bmp,tiff,tif,avif,svg})
shopt -u nullglob nocaseglob
fi
if [[ ${#files[@]} -eq 0 ]]; then
echo "No images found in: $dir" >&2
return 0
fi
# In recursive mode show the relative path, otherwise just the filename
printf "%-40s %11s %12s %10s\n" "FILENAME" "WIDTH (px)" "HEIGHT (px)" "SIZE (kB)"
printf "%-40s %11s %12s %10s\n" "--------" "----------" "-----------" "---------"
local f name width height size_kb viewbox current_subdir="" subdir
for f in "${files[@]}"; do
if [[ $recursive -eq 1 ]]; then
# Print a header line when we enter a new subdirectory
subdir="$(dirname "${f#$dir/}")"
if [[ "$subdir" != "$current_subdir" ]]; then
[[ -n "$current_subdir" ]] && echo ""
echo "${subdir}/"
current_subdir="$subdir"
fi
name="$(basename "$f")"
else
name="$(basename "$f")"
fi
# wc -c counts raw bytes; awk converts to kB with one decimal place
size_kb=$(awk "BEGIN { printf \"%d\", $(wc -c < "$f") / 1024 }")
if [[ "${f,,}" == *.svg ]]; then
# SVGs are XML so we can parse dimensions directly without imagemagick.
# Prefer viewBox (e.g. "0 0 100 200") as it's the most reliable source
# of the intended coordinate space; fall back to width/height attributes.
viewbox=$(grep -oi 'viewBox="[^"]*"' "$f" 2>/dev/null | head -1 | grep -oi '"[^"]*"' | tr -d '"')
if [[ -n "$viewbox" ]]; then
# viewBox is "min-x min-y width height" — fields 3 and 4
width=$(echo "$viewbox" | awk '{printf "%g", $3}')
height=$(echo "$viewbox" | awk '{printf "%g", $4}')
else
width=$(grep -oi 'width="[^"]*"' "$f" 2>/dev/null | head -1 | grep -oi '"[^"]*"' | tr -d '"px"')
height=$(grep -oi 'height="[^"]*"' "$f" 2>/dev/null | head -1 | grep -oi '"[^"]*"' | tr -d '"px"')
fi
# ${var:--} expands to "-" if var is empty or unset
width="${width:--}"
height="${height:--}"
else
# [0] selects only the first frame, preventing animated GIFs from
# producing one line of output per frame
read -r width height < <(identify -format "%w %h" "${f}[0]" 2>/dev/null)
fi
printf "%-40s %11s %12s %10s\n" "$name" "$width" "$height" "$size_kb"
done
}
Le « temps de développement » ici était plus long et je mentirais si je disais que j’ai compris chaque ligne de ce fonction. Mais je comprends les principes généraux et je comprends ce que cela peut et ne peut pas faire. Et être capable de lister toutes les images (de presque tous les types pertinents) dans un répertoire avec uniquement leurs propriétés clés dans un joli format tabulaire est pour moi un très beau gain de productivité.
Pensées finales
Les discussions sur l’IA tendent facilement vers les extrêmes : « c’est incroyable », « c’est un désastre », « c’est inutile ». Ceci est particulièrement inutile lorsque l’on se concentre sur la réussite ou l’échec de l’exécution d’une seule tâche. Tout ce que nous avons fait dans cet article, c’est créer quelques scripts Bash. Pour moi l’IA en question, Claude, a été utiliséecomplet. Ni plus ni moins. Malgré cela, le processus m’a beaucoup fait réfléchir à la façon dont la façon dont nous interagissons avec les machines évolue. Bien qu’il soit facile de sur-personnifier l’IA, cette phrase du film qui a donné son titre à cet article de blog semble maintenant, en 2026, avoir été prémonitoire :
L’avènement des ordinateurs et l’accumulation ultérieure de données incalculables ont donné naissance à un nouveau système de mémoire et de pensée parallèle au vôtre.
Pour les mises à jour et les révisions de cet article, consultez le message original
En rapport
PakarPBN
A Private Blog Network (PBN) is a collection of websites that are controlled by a single individual or organization and used primarily to build backlinks to a “money site” in order to influence its ranking in search engines such as Google. The core idea behind a PBN is based on the importance of backlinks in Google’s ranking algorithm. Since Google views backlinks as signals of authority and trust, some website owners attempt to artificially create these signals through a controlled network of sites.
In a typical PBN setup, the owner acquires expired or aged domains that already have existing authority, backlinks, and history. These domains are rebuilt with new content and hosted separately, often using different IP addresses, hosting providers, themes, and ownership details to make them appear unrelated. Within the content published on these sites, links are strategically placed that point to the main website the owner wants to rank higher. By doing this, the owner attempts to pass link equity (also known as “link juice”) from the PBN sites to the target website.
The purpose of a PBN is to give the impression that the target website is naturally earning links from multiple independent sources. If done effectively, this can temporarily improve keyword rankings, increase organic visibility, and drive more traffic from search results.