Passer au contenu principal
Linux, OS

Globbing sous Linux : maîtriser *, ?, [] et les motifs Bash sans catastrophe

Le globbing est l’un des mécanismes les plus pratiques du shell Linux. Au lieu d’écrire chaque nom de fichier individuellement, on décrit un motif et le shell recherche les noms qui lui correspondent.

Une commande comme :

rm *.bak

ne signifie donc pas réellement :

« Demande à rm de comprendre ce qu’est *.bak. »

Dans un shell comme Bash, c’est généralement le shell qui développe d’abord le motif. Si le répertoire contient :

ancien.bak
config.bak
rapport.txt

Bash peut transformer la commande en :

rm ancien.bak config.bak

puis lancer rm avec ces deux noms comme arguments.

Le globbing est donc une expansion réalisée par le shell. La commande reçoit généralement les noms déjà développés, pas le motif que vous aviez tapé.

Les trois caractères fondamentaux

En Bash, les trois constructions les plus importantes sont :

Motif Signification
* Zéro, un ou plusieurs caractères
? Exactement un caractère
[...] Un caractère appartenant à un ensemble ou une plage

L’astérisque : *

L’astérisque correspond à une chaîne de longueur quelconque, y compris une chaîne vide.

Exemple :

ls *.txt

peut correspondre à :

notes.txt
rapport.txt
a.txt

Le motif :

log*

peut correspondre à :

log
log.txt
logs
log_2026.txt
logiciel

Car après :

log

l’astérisque accepte zéro ou plusieurs caractères.

L’astérisque peut aussi être placé au milieu

ls rapport*.pdf

peut sélectionner :

rapport.pdf
rapport1.pdf
rapport_final.pdf
rapport_2026.pdf

Et :

ls *2026*

peut sélectionner tous les noms contenant :

2026

Le point d’interrogation : ?

Contrairement à *, le point d’interrogation correspond à exactement un caractère.

Par exemple :

ls fichier?.txt

peut correspondre à :

fichier1.txt
fichierA.txt
fichier_.txt

mais pas à :

fichier.txt
fichier10.txt

Le premier manque d’un caractère ; le second en possède deux là où le motif n’en autorise qu’un.

Compter les caractères avec ?

Le motif :

?????.txt

demande cinq caractères avant :

.txt

Il peut donc sélectionner :

notes.txt
image.txt

mais pas :

rapport.txt

Les crochets : […]

Une expression entre crochets correspond à un caractère choisi dans un ensemble.

Par exemple :

ls fichier[123].txt

peut correspondre à :

fichier1.txt
fichier2.txt
fichier3.txt

mais pas à :

fichier4.txt

Utiliser une plage

On peut écrire :

[a-c]

pour demander un caractère appartenant à la plage correspondante.

Par exemple :

ls [a-c]*

sélectionne les noms dont le premier caractère correspond à cette plage.

Attention : les plages dépendent de la locale

Une expression comme :

[a-z]

n’est pas toujours une garantie absolue de comportement ASCII.

Les plages peuvent dépendre notamment de :

LC_COLLATE
LC_ALL
locale courante

Bash possède aussi l’option :

globasciiranges

pour traiter les plages selon le comportement traditionnel de la locale C.

Pour des scripts où la nature des caractères est importante, les classes de caractères peuvent être plus explicites.

Les classes de caractères

Dans une expression entre crochets, on peut utiliser des classes comme :

[[:digit:]]
[[:alpha:]]
[[:alnum:]]
[[:lower:]]
[[:upper:]]
[[:space:]]

Par exemple :

ls fichier[[:digit:]].txt

recherche un nom contenant un chiffre à cette position.

Négation avec [!…]

Si le premier caractère de la classe est :

!

la classe est négative.

Exemple :

ls [!0-9]*

signifie :

« Sélectionner les noms dont le premier caractère n’est pas compris entre 0 et 9. »

Il peut donc parfaitement sélectionner :

rapport2026.txt

car son premier caractère est :

r

Ce motif ne signifie donc pas « aucun chiffre dans le nom »

Cette interprétation serait incorrecte :

[!0-9]*
=
aucun chiffre n'importe où

Le test ne concerne que le caractère correspondant à l’expression entre crochets.

Exemples courants avec ls

# Tout ce qui correspond à *
ls *

# Fichiers se terminant par .txt
ls *.txt

# Noms commençant par log
ls log*

# Exactement cinq caractères
ls ?????

# Premier caractère entre a et c
ls [a-c]*

# Premier caractère non numérique
ls [!0-9]*

# Un chiffre à une position précise
ls rapport[0-9].txt

Premier grand piège : les fichiers cachés

Sous Unix/Linux, un nom commençant par :

.

est conventionnellement considéré comme caché.

Exemples :

.bashrc
.profile
.gitignore
.config

Par défaut, un glob :

*

ne correspond pas au point situé au début d’un nom.

Ainsi :

ls *

n’inclut normalement pas :

.bashrc

Le point initial doit normalement être explicite

Pour rechercher certains fichiers cachés :

ls .*

Mais cette forme mérite elle-même un peu d’attention selon le shell et le contexte.

Une approche ciblée reste souvent préférable :

ls .*.conf

L’option dotglob

Bash peut modifier ce comportement :

shopt -s dotglob

Une fois l’option activée, :

*

peut également correspondre aux noms commençant par un point, à l’exception des traitements particuliers de :

.
..

Pour désactiver :

shopt -u dotglob

Pourquoi dotglob demande de la prudence

Un script qui suppose que :

*

ignore les fichiers cachés peut changer radicalement de comportement si :

dotglob

est activé.

Avant une copie ou suppression importante, il est donc utile de connaître les options de shell actives.

Deuxième grand piège : aucun fichier ne correspond

Supposons qu’aucun fichier .bak n’existe et que vous exécutiez :

printf '%s\n' *.bak

Avec le comportement Bash par défaut, le motif qui ne trouve rien reste généralement inchangé.

Le résultat peut donc être :

*.bak

et non une liste vide.

Conséquence avec une commande

Si aucun fichier ne correspond à :

*.bak

alors :

rm *.bak

peut finalement appeler rm avec l’argument littéral :

*.bak

qui répondra généralement qu’un tel fichier n’existe pas.

nullglob

L’option :

shopt -s nullglob

demande à Bash de supprimer complètement un motif sans correspondance.

Ainsi, si aucun fichier ne correspond :

files=( *.bak )

donnera un tableau vide plutôt qu’un élément littéral :

*.bak

failglob

Une autre approche consiste à utiliser :

shopt -s failglob

Dans ce mode, un motif sans correspondance provoque une erreur d’expansion.

Cela peut être utile dans certains scripts où :

« Aucun fichier trouvé »

doit être considéré comme une anomalie plutôt que comme un résultat acceptable.

nullglob et failglob ne répondent pas au même besoin

Configuration Motif sans résultat
Comportement Bash normal Le motif reste littéral
nullglob Le motif disparaît
failglob Une erreur est générée

Quoting : pourquoi les guillemets changent tout

Les caractères spéciaux doivent rester non protégés pour être interprétés comme des motifs de globbing.

Cette commande :

ls *.txt

permet au shell de développer :

*.txt

En revanche :

ls "*.txt"

protège l’astérisque.

ls reçoit alors littéralement :

*.txt

et cherchera un fichier portant exactement ce nom.

Les guillemets ne sont pourtant pas « mauvais »

Il faut simplement les placer au bon endroit.

Supposons :

dir="/home/alice/Mes Documents"

On peut écrire :

ls "$dir"/*.txt

Ici :

  • "$dir" est protégé contre le découpage en mots ;
  • *.txt reste disponible pour le globbing.

C’est une construction beaucoup plus sûre que :

ls $dir/*.txt

si la variable contient des espaces ou d’autres caractères particuliers.

Le shell développe les motifs, pas forcément la commande

On peut le démontrer avec :

printf '<%s>\n' *.txt

Si le répertoire contient :

a.txt
rapport final.txt

le shell transmet deux arguments séparés :

a.txt
rapport final.txt

Le nom contenant un espace reste un seul résultat de globbing.

C’est très différent d’une mauvaise construction utilisant une substitution de commande.

Ne jamais remplacer le globbing par ls

Cette construction est à éviter :

for file in $(ls *.txt); do
    ...
done

Elle reconvertit la sortie textuelle de ls puis la soumet notamment au découpage en mots.

Un fichier comme :

rapport final.txt

risque alors d’être traité comme :

rapport
final.txt

La forme naturelle est simplement :

for file in *.txt; do
    printf '%s\n' "$file"
done

Les espaces dans les noms ne cassent pas un glob correctement utilisé

Si :

*.txt

correspond à :

rapport final.txt

le résultat du globbing constitue bien un nom de fichier.

Le vrai danger apparaît ensuite si l’on écrit :

rm $file

au lieu de :

rm -- "$file"

Pourquoi utiliser — ?

Un fichier peut parfaitement s’appeler :

-rf

ou :

--preserve-root

Lorsqu’une commande Unix voit un argument commençant par :

-

elle peut l’interpréter comme une option.

De nombreuses commandes GNU acceptent :

--

pour indiquer :

« Les options sont terminées ; ce qui suit est un argument ordinaire. »

Par exemple :

rm -- *.bak

est préférable à :

rm *.bak

lorsqu’on veut se protéger des noms commençant par un tiret.

La suppression : vérifier avant d’exécuter

Avant :

rm -- *.bak

on peut afficher la sélection :

printf '%s\n' *.bak

ou :

ls -- *.bak

Encore faut-il se souvenir du comportement en cas d’absence de correspondance.

Pour un script sérieux, un tableau avec nullglob peut être plus robuste.

Exemple sûr avec un tableau Bash

shopt -s nullglob

files=( *.bak )

if ((${#files[@]})); then
    printf 'Fichiers à supprimer :\n'
    printf '  %s\n' "${files[@]}"
else
    printf 'Aucun fichier .bak trouvé.\n'
fi

On peut ensuite décider explicitement de l’opération à effectuer.

Brace expansion : les accolades ne sont pas du globbing

Cette commande est très pratique :

ls *.{sh,py}

Mais elle combine en réalité deux mécanismes.

Bash effectue d’abord une brace expansion :

*.{sh,py}

devient :

*.sh *.py

Puis le shell effectue le globbing sur chaque motif.

Les accolades sont purement textuelles

Par exemple :

echo fichier{1,2,3}.txt

produit :

fichier1.txt fichier2.txt fichier3.txt

même si aucun de ces fichiers n’existe.

C’est une différence importante avec le globbing.

Autres exemples de brace expansion

mkdir -p projet/{src,doc,tests}

cp rapport.{pdf,txt} sauvegarde/

echo serveur{1..5}

La brace expansion n’est pas une fonctionnalité POSIX sh universelle. Elle doit donc être utilisée en connaissance de cause dans un script explicitement Bash.

Les motifs étendus : extglob

Bash propose un système de motifs avancés appelé :

extended globbing
extglob

Activation :

shopt -s extglob

Désactivation :

shopt -u extglob

Les opérateurs extglob

Motif Signification
?(motifs) Zéro ou une occurrence
*(motifs) Zéro ou plusieurs occurrences
+(motifs) Une ou plusieurs occurrences
@(motifs) Exactement un des motifs proposés
!(motifs) Tout sauf les motifs proposés

Plusieurs possibilités sont séparées par :

|

Fichiers .sh ou .py avec extglob

ls *.@(sh|py)

avec :

shopt -s extglob

Tout sauf les fichiers .conf

La syntaxe correcte est :

shopt -s extglob
printf '%s\n' !(*.conf)

Et non :

!( *.conf )

Les espaces feraient partie de la syntaxe shell et empêcheraient le motif de fonctionner comme prévu.

Supprimer tout sauf les .conf

Techniquement :

shopt -s extglob
rm -- !(*.conf)

Mais cette commande mérite une inspection préalable :

printf '%s\n' !(*.conf)

Car « tout sauf » est une formulation particulièrement efficace pour découvrir après coup qu’un fichier important n’avait pas l’extension attendue.

Exclure les noms contenant un chiffre

Le motif initial :

[!0-9]*

ne suffit pas.

Avec extglob, on peut exprimer :

!(*[0-9]*)

Ce motif sélectionne les noms qui ne correspondent pas à :

*[0-9]*

c’est-à-dire ceux qui ne contiennent aucun chiffre correspondant à cette classe.

extglob peut modifier la syntaxe du shell

Les parenthèses deviennent significatives lorsque extglob est utilisé.

Dans certains contextes, notamment lors de la définition de fonctions, l’option doit donc être activée avant que Bash analyse le code contenant les motifs étendus.

Dans un script Bash utilisant intensivement extglob, il est raisonnable de placer :

shopt -s extglob

avant les constructions qui en dépendent.

globstar : ** pour parcourir récursivement

Bash possède également l’option :

globstar

Activation :

shopt -s globstar

Lorsque cette option est active, deux astérisques adjacents :

**

peuvent traverser zéro ou plusieurs niveaux de répertoires.

Exemple récursif

shopt -s globstar
printf '%s\n' **/*.log

peut trouver :

app.log
logs/app.log
serveurs/web/logs/access.log

sans utiliser explicitement find.

Différence entre * et **

Avec les options normales :

*.txt

ne traverse pas le caractère :

/

Il travaille dans le niveau correspondant du chemin.

Avec :

globstar

le motif :

**/*.txt

peut parcourir récursivement l’arborescence.

globstar et fichiers cachés

L’activation de :

globstar

ne signifie pas automatiquement :

inclure les fichiers cachés

Pour cela, le comportement de :

dotglob

reste pertinent.

nocaseglob : ignorer la casse

Par défaut, sur un système Linux classique :

*.jpg

ne correspond pas nécessairement à :

PHOTO.JPG

Avec Bash :

shopt -s nocaseglob

les comparaisons de glob peuvent ignorer la casse.

À désactiver avec :

shopt -u nocaseglob

Les noms de fichiers Linux sont sensibles à la casse

Sur les systèmes de fichiers Linux courants :

image.jpg
Image.jpg
IMAGE.JPG

peuvent être trois noms distincts.

Le globbing suit normalement cette distinction.

Globbing et variables

Supposons :

pattern='*.txt'

Cette commande :

printf '%s\n' $pattern

peut soumettre le contenu non cité à plusieurs expansions, dont le filename expansion.

En revanche :

printf '%s\n' "$pattern"

affichera :

*.txt

littéralement.

Cette différence est réelle, mais utiliser des variables non citées uniquement pour provoquer du globbing peut rendre les scripts difficiles à raisonner.

Préférer les tableaux lorsque les listes deviennent complexes

Par exemple :

shopt -s nullglob

images=( *.jpg *.jpeg *.png )

for image in "${images[@]}"; do
    printf '%s\n' "$image"
done

Chaque élément reste un argument distinct, même si son nom contient :

  • des espaces ;
  • des tabulations ;
  • des astérisques littéraux ;
  • d’autres caractères particuliers.

Globbing et récursivité : quand utiliser find ?

Le globbing est excellent pour :

*.txt
images/*.jpg
logs/**/*.log

Mais pour des recherches complexes, find reste souvent plus adapté.

Par exemple :

find /var/log -type f -name '*.log'

permet de combiner :

  • type de fichier ;
  • nom ;
  • taille ;
  • date ;
  • propriétaire ;
  • permissions ;
  • profondeur ;
  • actions.

Pourquoi le motif de find est entre guillemets ?

Cette commande est correcte :

find . -name '*.txt'

Les guillemets empêchent Bash de développer :

*.txt

avant le lancement de find.

Cette fois, nous voulons justement que :

find

reçoive le motif et effectue lui-même sa recherche récursive.

Comparer les deux situations

Globbing par Bash

ls *.txt

Bash développe le motif avant d’appeler ls.

Motif interprété par find

find . -name '*.txt'

Bash protège le motif et find l’interprète lui-même.

Les guillemets ne sont donc ni systématiquement bons ni systématiquement mauvais : il faut savoir quel programme doit interpréter le motif.

Globbing et expressions régulières : ne pas confondre

Un glob :

*.txt

et une expression régulière ne suivent pas les mêmes règles.

Dans un glob :

*

signifie :

n'importe quelle chaîne

Dans une expression régulière, :

*

signifie généralement :

répéter zéro ou plusieurs fois
l'élément précédent

Exemple de différence

Glob :

*.txt

Expression régulière ressemblante :

.*\.txt$

Copier directement la syntaxe de l’un vers l’autre est donc une méthode efficace pour fabriquer des résultats incompréhensibles.

Globbing et case

La commande Bash :

case "$file" in
    *.txt)
        echo "Fichier texte"
        ;;
esac

utilise également des motifs de type shell.

Le mécanisme de pattern matching est donc utile au-delà des simples noms de fichiers.

Globbing dans [[ ]]

Avec Bash :

if [[ $file == *.txt ]]; then
    echo "Fichier texte"
fi

la partie droite non citée :

*.txt

est interprétée comme un motif.

Si on écrit :

if [[ $file == "*.txt" ]]; then

on demande au contraire une correspondance littérale avec les caractères :

*.txt

rm et les globs : zone à haute responsabilité

Ces commandes sont parfaitement valides :

rm -- *.bak
rm -- temp*
rm -- ?.txt

Mais elles peuvent viser beaucoup plus de fichiers que prévu.

Avant une suppression importante, utilisez par exemple :

printf '%s\n' temp*

puis vérifiez le résultat.

Attention aux répertoires

rm sans :

-r

ne supprime normalement pas les répertoires ordinaires.

Mais :

rm -r -- temp*

peut supprimer aussi bien :

temp.txt

que :

temp_backup/

et toute son arborescence.

L’ajout de :

-f

supprime encore davantage de garde-fous.

La combinaison :

récursif
+
forcé
+
wildcard

mérite donc une attention légèrement supérieure à celle accordée à la commande permettant de connaître l’heure.

Copier avec cp

Exemple classique :

cp -- *.jpg /home/alice/images/

Le shell développe d’abord les JPG puis cp reçoit la liste.

Pour copier plusieurs types avec Bash :

cp -- *.{jpg,jpeg,png} /home/alice/images/

Attention encore une fois au cas où l’un des motifs ne correspond à rien.

Déplacer avec mv

mv -- *.pdf /home/alice/archives/

Pour sélectionner les fichiers contenant une année :

mv -- *2026* /home/alice/archives/

Le motif peut aussi sélectionner des répertoires correspondant au nom.

Si seuls des fichiers ordinaires doivent être traités, une boucle avec test ou find peut offrir davantage de contrôle.

Exemple avec vérification du type

shopt -s nullglob

for file in *.pdf; do
    [[ -f $file ]] || continue
    printf 'Déplacement : %s\n' "$file"
    mv -- "$file" /home/alice/archives/
done

Les principaux shopt liés au globbing

Option Effet
extglob Active les motifs étendus comme !() et @()
nullglob Un motif sans correspondance disparaît
failglob Un motif sans correspondance provoque une erreur
dotglob Les globs peuvent inclure les noms commençant par un point
globstar Active le comportement récursif de **
nocaseglob Ignore la casse lors du globbing de noms
globasciiranges Traite les plages selon l’ordre ASCII traditionnel

Voir les options actives

Pour afficher les options shopt :

shopt

Pour une option précise :

shopt -p nullglob
shopt -p dotglob
shopt -p globstar

Activer temporairement plusieurs options

shopt -s nullglob dotglob

Puis les désactiver :

shopt -u nullglob dotglob

Dans un script, il est préférable de déclarer explicitement les options dont le comportement dépend.

Globbing Bash et POSIX sh ne sont pas identiques

Les motifs fondamentaux :

*
?
[...]

sont largement portables.

Mais des fonctionnalités comme :

extglob
globstar
brace expansion
shopt

sont spécifiques à Bash ou ne doivent pas être supposées disponibles dans un script portable sh.

Un script commençant par :

#!/bin/sh

ne devrait donc pas utiliser tranquillement :

shopt -s extglob

puis prétendre être portable.

Ne pas confondre shell et système d’exploitation

Le globbing décrit ici est avant tout celui de :

Bash

et non une propriété abstraite imposée par « Linux ».

Un système Linux peut utiliser :

  • Bash ;
  • Dash ;
  • Zsh ;
  • Fish ;
  • Ksh ;
  • d’autres shells.

Leurs fonctionnalités avancées de globbing peuvent différer.

Les erreurs classiques

Croire que * inclut automatiquement les dotfiles

Par défaut sous Bash :

*

n’inclut pas les noms commençant par :

.

Croire qu’un motif sans correspondance devient une liste vide

Par défaut, Bash laisse normalement le motif littéral.

Utilisez :

nullglob

ou :

failglob

si un autre comportement est souhaité.

Croire que [!0-9]* exclut tous les nombres

Non.

Il exclut seulement les noms dont le caractère correspondant initial est numérique.

Confondre *.{sh,py} avec un glob unique

Il s’agit de :

brace expansion
+
globbing

Mettre le glob entre guillemets au mauvais moment

ls "*.txt"

empêche Bash de développer l’astérisque.

Ne pas mettre une variable entre guillemets

Cette forme :

rm -- "$file"

est généralement correcte lorsqu’un seul nom stocké dans une variable doit être traité.

Utiliser ls pour construire une liste

Évitez :

for f in $(ls)

Utilisez directement les expansions du shell ou des méthodes sûres adaptées aux noms de fichiers.

Utiliser rm -rf avant d’avoir vérifié le motif

Le shell ne sait pas que :

temp*

devait seulement sélectionner trois fichiers temporaires et non :

temp
templates
temporary-backup
temp-prod-database

Il n’est pas malveillant. Il est simplement remarquablement littéral.

Tableau pratique des motifs

Motif Signification
* N’importe quelle chaîne, sauf dotfiles initiaux par défaut
? Exactement un caractère
[abc] Un caractère parmi a, b ou c
[a-z] Un caractère dans une plage dépendant notamment de la locale
[!0-9] Un caractère qui n’est pas dans la plage indiquée
[[:digit:]] Un caractère appartenant à la classe des chiffres
*.txt Nom se terminant par .txt
log* Nom commençant par log
*2026* Nom contenant 2026
?????.txt Cinq caractères suivis de .txt
*.{sh,py} Brace expansion en deux globs : *.sh et *.py
**/*.log Recherche récursive avec globstar
!(*.conf) Tout sauf ce qui correspond à *.conf avec extglob

Une méthode prudente pour les opérations en masse

Avant une opération destructive, adoptez cette séquence :

1. Construire le motif

2. Afficher ses correspondances

3. Vérifier les fichiers cachés si nécessaire

4. Vérifier le comportement en cas de zéro correspondance

5. Vérifier qu'aucun répertoire inattendu n'est sélectionné

6. Seulement ensuite exécuter rm, mv ou cp

Par exemple :

shopt -s nullglob

files=( temp*.bak )

printf 'Sélection :\n'
printf '  %s\n' "${files[@]}"

# Après vérification :
# rm -- "${files[@]}"

Ce type de préparation est un peu moins spectaculaire que :

rm -rf temp*

mais produit généralement des journées beaucoup plus courtes.

Les règles essentielles à retenir

  • * correspond à zéro ou plusieurs caractères.
  • ? correspond exactement à un caractère sous Bash.
  • [...] sélectionne un caractère appartenant à un ensemble.
  • Les plages comme [a-z] peuvent dépendre de la locale.
  • [!0-9]* signifie que le premier caractère n’est pas numérique, pas que le nom ne contient aucun chiffre.
  • Le shell développe généralement le glob avant de lancer la commande.
  • Mettre un glob entier entre guillemets empêche normalement son expansion par Bash.
  • Les résultats du globbing conservent correctement les noms contenant des espaces ; il faut ensuite citer les variables qui les contiennent.
  • * n’inclut pas les dotfiles par défaut.
  • dotglob modifie ce comportement.
  • Un motif sans correspondance reste normalement littéral ; nullglob ou failglob permettent de changer cela.
  • *.{sh,py} combine brace expansion et filename expansion.
  • extglob ajoute des motifs comme !(), @() et +().
  • globstar donne un rôle récursif à **.
  • Les globs ne sont pas des expressions régulières.
  • Pour une recherche complexe ou récursive, find peut être plus approprié.
  • Évitez de parser la sortie de ls pour construire une liste de fichiers.
  • Avec les commandes acceptant cette convention, -- protège contre les noms commençant par un tiret.
  • Avant toute suppression en masse, affichez d’abord ce que le motif sélectionne.

Conclusion : le shell vise exactement ce que vous lui demandez

Le globbing permet de transformer :

cp photo1.jpg destination/
cp photo2.jpg destination/
cp photo3.jpg destination/

en :

cp -- *.jpg destination/

et de construire des sélections beaucoup plus puissantes avec :

?
[]
brace expansion
extglob
globstar

Mais cette efficacité repose sur une règle fondamentale :

motif
↓
expansion par le shell
↓
liste d'arguments
↓
exécution de la commande

Comprendre cette séquence explique presque tous les comportements qui paraissent mystérieux au début.

Le terminal n’interprète pas votre intention.

Si :

rm -rf -- temp*

sélectionne :

temp.txt
temporary/
templates/
temp-backup-important/

Bash ne considère pas que les deux derniers « avaient pourtant l’air importants ».

Il a reçu un motif.

Il l’a développé correctement.

Et, comme souvent sous Unix, la catastrophe éventuelle sera techniquement conforme à la documentation.