Erreur « Argument list too long » : find et xargs expliqués

Dans un répertoire qui contient des dizaines de milliers de fichiers, rm *.log affiche bash: /bin/rm: Argument list too long et ne supprime rien. Le message vient du noyau, pas de rm : le shell a développé le motif en une liste d’arguments qui dépasse ARG_MAX, la taille maximale acceptée par un appel unique à execve(2). La forme find ... -print0 | xargs -0 découpe cette liste en lots et fait passer la suppression ou la copie.
Sommaire
- « Argument list too long » est errno E2BIG, renvoyé par execve
- Supprimer des milliers de fichiers : la séquence find puis xargs
- find -exec {} +, find -delete et xargs : quel outil pour quel besoin
- « xargs: argument line too long » survient quand on empêche xargs de découper
- Un espace dans un nom de fichier fait supprimer le mauvais fichier
- Copier un arbre entier sans l’aplatir
- Ce qui change entre findutils 4.8, 4.9 et 4.10
- Ce qu’on nous demande le plus
« Argument list too long » est errno E2BIG, renvoyé par execve
La page execve(2) définit E2BIG ainsi : « The total number of bytes in the environment (envp) and argument list (argv) is too large, an argument or environment string is too long, or the full path of the executable is too long. » Le noyau refuse l’appel avant même de lancer le programme, et bash traduit l’errno 7 en Argument list too long.
getconf ARG_MAX
Sur une architecture x86_64, cette commande affiche 2097152, soit 2 Mio. Depuis Linux 2.6.23, cette valeur vaut le quart de la limite souple de pile RLIMIT_STACK, elle-même plafonnée aux trois quarts de la constante _STK_LIM (8 Mio). La même page ajoute que la limite par chaîne est de 32 pages, soit 131072 octets pour MAX_ARG_STRLEN. Avant Linux 2.6.23, la mémoire réservée aux arguments était fixée à 32 pages, soit 128 ko sur une architecture à pages de 4 ko.
Deux conséquences comptent. ARG_MAX est partagé avec l’environnement : chaque variable exportée réduit la place laissée aux arguments, ce qui explique qu’un script échoue dans une session chargée et passe dans un terminal nu. Et la limite suit la pile, si bien que ulimit -s relève aussi ARG_MAX.
Supprimer des milliers de fichiers : la séquence find puis xargs
Les valeurs citées sont celles de GNU findutils 4.9.0, livré par Debian 12 (paquet 4.9.0-4) et Ubuntu 24.04 LTS (paquet 4.9.0-5build1). L’exemple porte sur des fichiers .log sous /var/tmp/lot.
-
Mesurer la limite et la place prise par l’environnement.
xargs --show-limits < /dev/null
La sortie nomme chaque valeur et se termine par le tampon réellement utilisé :
Your environment variables take up 2841 bytes POSIX upper limit on argument length (this system): 2092264 POSIX smallest allowable upper limit on argument length (all systems): 4096 Maximum length of command we could actually use: 2089423 Size of command buffer we are actually using: 131072
Les octets de l’environnement varient selon la session, mais la dernière ligne vaut 131072, soit 128 Kio, valeur par défaut documentée dans
xargs(1). Un lot plus petit que cette taille passe toujours. -
Compter les octets de la liste, pas le nombre de fichiers : c’est la taille cumulée qui déclenche
E2BIG.find /var/tmp/lot -name '*.log' -printf '%p\n' | wc -c
La réponse est un seul nombre, par exemple
9724180. Comparé aux 2092264 octets utiles, ce total de 9,7 Mo ne peut pas tenir dans un seul appel. -
Simuler l’opération sans rien détruire, en remplaçant la commande par
echo.find /var/tmp/lot -name '*.log' -print0 | xargs -0 -n 1000 -t echo rm -f | head -n 2
Chaque lot apparaît sur une ligne, précédé de sa taille :
echo rm -f /var/tmp/lot/a.log /var/tmp/lot/b.log ... echo rm -f /var/tmp/lot/z.log /var/tmp/lot/aa.log ...
-n 1000limite chaque appel à mille arguments et-trecopie la commande avant de l’exécuter. Plusieurs lignes confirment le fractionnement. -
Lancer la suppression réelle, une fois la simulation relue.
find /var/tmp/lot -name '*.log' -print0 | xargs -0 rm -f echo $?
Aucune sortie n’est attendue de la première commande :
xargsne parle que s’il échoue. La seconde doit afficher0. Un123signale qu’au moins une invocation a échoué, sans dire laquelle. -
Vérifier qu’il ne reste aucun fichier correspondant.
find /var/tmp/lot -name '*.log' | wc -l
Le compteur doit afficher
0. Toute valeur supérieure désigne des fichiers en lecture seule ou appartenant à un autre utilisateur.
Ajouter -r (--no-run-if-empty) évite un piège propre à GNU xargs : sans cette option, la commande est lancée une fois même sur une entrée vide, soit un rm sans argument. La forme finale, sûre dans un script, s’écrit find ... -print0 | xargs -0 -r rm -f.
find -exec {} +, find -delete et xargs : quel outil pour quel besoin
Les quatre formes ci-dessous suppriment les mêmes fichiers, mais ne construisent pas leur ligne de commande de la même manière.
| Forme | Découpage en lots | Noms avec espaces ou retours à la ligne | Processus lancés |
|---|---|---|---|
find ... -delete |
sans objet, aucun appel externe | exact, le nom n’est jamais reparsé | aucun |
find ... -exec rm {} + |
oui, comme xargs |
exact | quelques rm |
find ... -print0 | xargs -0 rm |
oui, 128 Kio par défaut | exact si les deux options vont ensemble | quelques rm |
find ... | xargs rm |
oui | faux : coupe sur les espaces | quelques rm, parallélisables avec -P |
find(1) décrit -exec rm {} + comme construisant sa ligne « in much the same way that xargs builds its command lines ». Cette variante existe depuis findutils 4.2.12 (2005) et évite le tube, mais {} doit y être unique et placé juste avant +. find ... -delete, ajouté en findutils 4.2.3 (2004), supprime sans fork ni exec et ne peut donc jamais atteindre ARG_MAX ; il implique -depth et refuse un répertoire non vide.
« xargs: argument line too long » survient quand on empêche xargs de découper
Un second message apparaît lorsque xargs est configuré pour ne pas fractionner l’entrée. Cette commande, fréquente pour déplacer des fichiers un par un, échoue :
find /var/tmp/lot -name '*.mp4' -print0 | xargs -i {} -0 mv -t /var/tmp/dest {}
xargs: argument line too long
xargs(1) donne la cause : -I, dont -i est le synonyme historique, « implies -x and -L 1 ». Chaque élément produit donc une commande séparée, et -x ordonne à xargs de s’arrêter si la taille dépasse la limite au lieu de découper. La section BUGS du manuel ajoute que -I met chaque ligne en mémoire tampon, ce qui impose une longueur maximale à lire. Le remède est de retirer -I et de laisser le découpage automatique agir :
find /var/tmp/lot -name '*.mp4' -print0 | xargs -0 mv -t /var/tmp/dest
L’option GNU -t de mv, fournie par coreutils, place le répertoire de destination en fin de commande, comme cp -t. Relever -s ne corrige pas l’erreur : xargs(1) calcule la valeur maximale en retirant la taille de l’environnement et 2048 octets de marge, et ramène toute demande trop haute. Un -n 500 modeste reste plus lisible.
Un espace dans un nom de fichier fait supprimer le mauvais fichier
Sans -print0 côté find ni -0 côté xargs, la liste est coupée sur les blancs. Un fichier nommé facture janvier.log devient deux arguments et rm reçoit deux chemins qui ne correspondent à rien ; avec un retour à la ligne dans le nom, la coupure se fait au milieu et la commande peut viser un autre fichier. find(1) rappelle que -print0 termine chaque nom par un octet nul, et xargs(1) que -0 lit ce séparateur et désactive l’interprétation des guillemets et de l’antislash.
Copier un arbre entier sans l’aplatir
find /var/tmp/lot -type f -print0 | xargs -0 -n 500 cp -t /var/tmp/dest
Cette commande aplatit l’arborescence : tous les fichiers arrivent au premier niveau de /var/tmp/dest. Pour conserver les sous-répertoires, tar convient mieux, puisqu’il n’y a plus de liste d’arguments à transmettre :
tar -C /var/tmp/lot -cf - . | tar -C /var/tmp/dest -xf -
Une synchronisation répétée relève d’un outil dédié, traité dans rsync pour la sauvegarde. Pour accélérer un lot de copies indépendantes, xargs -P 4 lance jusqu’à quatre commandes en parallèle ; la valeur par défaut de -P est 1 et l’ordre des sorties n’est alors plus garanti.
Ce qui change entre findutils 4.8, 4.9 et 4.10
Comme les autres utilitaires d’un système Linux, find et xargs sont fournis par findutils, dont la version dépend de la distribution. -files0-from, qui lit les points de départ depuis un fichier au lieu de la ligne de commande, est apparu dans findutils 4.9.0 (2022) et manque à Debian 11 (bullseye, paquet 4.8.0-1). Debian 12 (bookworm) fournit 4.9.0-4, Debian 13 (trixie) fournit 4.10.0-3 et Ubuntu 24.04 LTS fournit 4.9.0-5build1 ; les trois acceptent -print0, -exec {} + et -delete. La version 4.11 (2026) modifie un point précis : -mount n’est plus un synonyme de -xdev et saute désormais le point de montage lui-même, conformément à POSIX issue 8. Un script qui s’appuie sur l’ancienne équivalence donne un résultat différent selon la version installée.
Ce qu’on nous demande le plus
Pourquoi xargs n’empêche-t-il pas toujours « Argument list too long » ?
Parce que xargs découpe par défaut, mais que -I (ou -i) supprime ce découpage : le manuel indique que l’option « implies -x and -L 1 ». Chaque élément forme alors une commande unique, et -x transforme le dépassement en arrêt immédiat. Retirer -I et garder -0 rétablit le fonctionnement normal.
Quelle est la limite exacte d’une ligne de commande sous Linux ?
getconf ARG_MAX renvoie 2097152 sur x86_64, mais depuis Linux 2.6.23 la valeur réelle dépend de la limite souple de pile RLIMIT_STACK, dont elle vaut le quart, avec un plancher de 32 pages. La limite s’applique aux arguments et à l’environnement réunis, et une chaîne unique ne peut pas dépasser MAX_ARG_STRLEN, soit 131072 octets.
Faut-il préférer find -delete ou find | xargs rm ?
find ... -delete ne lance aucun processus externe et ne peut jamais atteindre ARG_MAX ; il traite les noms d’échappement sans les interpréter. Il implique -depth et échoue sur un répertoire non vide. find ... -print0 | xargs -0 rm reste utile quand la commande finale n’existe pas dans find.
Pourquoi find sans résultat déclenche-t-il quand même la commande ?
GNU xargs exécute la commande une fois même avec une entrée vide, comme l’indique xargs(1) à propos de l’option -r. Dans un script, find ... -print0 | xargs -0 -r rm -f évite de lancer un rm sans argument lorsqu’aucun fichier ne correspond.
Comment vérifier qu’une suppression par lots a tout traité ?
Après l’opération, find /chemin -name '*.log' | wc -l doit afficher 0, et echo $? lancé juste après la commande xargs doit valoir 0. Un code de retour 123 signale qu’une invocation a échoué sans indiquer laquelle.
Sources : xargs(1), options -0, -n, -s, -I, -r, -P et section BUGS, find(1), actions -exec {} +, -delete, -print0, execve(2), errno E2BIG et limites d’arguments, cp(1), option -t, Debian Sources, versions de findutils par distribution.