Liste d'arguments trop longue lors de la copie de fichiers


26

Je viens de poser une question relative à la façon de compter les fichiers d'extension particulière. Maintenant, je veux cpces fichiers à un nouveau dir.

J'essaie,

cp *.prj ../prjshp/

et

cp * | grep '\.prj$' ../prjshp/

mais ils donnent la même erreur,

bash: / bin / cp: Liste d'arguments trop longue

Comment les copier?


Réponses:


36

cp *.prj ../prjshp/est la bonne commande, mais vous avez rencontré un cas rare où elle se heurte à une limitation de taille. La deuxième commande que vous avez essayée n'a aucun sens.

Une méthode consiste à exécuter cples fichiers en morceaux. La findcommande sait comment faire:

find -maxdepth 1 -name '*.prj' -exec mv -t ../prjshp {} +
  • find parcourt le répertoire courant et les répertoires en dessous de manière récursive.
  • -maxdepth 1 signifie s'arrêter à une profondeur de 1, c'est-à-dire ne pas rentrer dans les sous-répertoires.
  • -name '*.prj'signifie agir uniquement sur les fichiers dont le nom correspond au modèle spécifié. Notez les guillemets autour du motif: il sera interprété par la findcommande, pas par le shell.
  • -exec … {} +signifie exécuter la commande spécifiée pour tous les fichiers. Il appelle la commande plusieurs fois si nécessaire, en prenant soin de ne pas dépasser la limite de ligne de commande.
  • mv -t ../prjshpdéplace les fichiers spécifiés dans ../prjshp. L' -toption est utilisée ici en raison d'une limitation de la findcommande: les fichiers trouvés (symbolisés par {}) sont passés comme dernier argument de la commande, vous ne pouvez pas ajouter la destination après celle-ci.

Une autre méthode consiste à utiliser rsync.

rsync -r --include='*.prj' --exclude='*' . ../prjshp
  • rsync -r … . ../prjshpcopie le répertoire courant en ../prjshprécursivement.
  • --include='*.prj' --exclude='*'signifie copier les fichiers correspondants *.prjet exclure tout le reste (y compris les sous-répertoires, afin que les .prjfichiers des sous-répertoires ne soient pas trouvés).

3
rsync, de loin la solution la plus simple ici.
ntk4

Pour être un peu compliqué, la deuxième commande cp * | grep '\.prj$' ../prjshp/ n'a aucun sens, mais peut être syntaxiquement valide, si elle se *développe en liste de fichiers, le dernier étant un répertoire (aka cp SOURCE1 SOURCE2....DEST). Le pipe n'a aucun sens, bien sûr, mais reste également syntaxiquement valide en ce qui concerne le shell - il va très bien dup()les descripteurs de fichiers, c'est juste que l'extrémité lecteur du pipe n'obtiendra aucune donnée car cpn'en écrit pas .
Sergiy Kolodyazhnyy

Find et rsync ont produit la même liste d'arguments erreur trop longue pour moi. La boucle for était la solution de contournement la plus simple.
Meezaan-ud-Din

En effet, rsync est le moyen de faire n'importe quelle copie de masse, même si je suis perplexe quant à la mesure dans laquelle nous sommes arrivés avec Linux et nous avons un défaut / bug idiot comme celui-ci et oui, je le considérerais comme un défaut / bug.
MitchellK

22

Cette commande copie les fichiers un par un et fonctionnera même s'ils sont trop nombreux pour *être développés en une seule cpcommande:

for i in *; do cp "$i" ../prjshp/; done

Cela fonctionne pour moi.
1rq3fea324wre

1
Simple et efficace. J'ai eu un problème similaire en supprimant ~ 1/4 million de fichiers JPEG que j'avais extraits d'une vidéo pour un projet. C'est l'approche que j'ai utilisée.
Elder Geek

5

Il y a 3 points clés à garder à l'esprit en cas d' Argument list too longerreur:

  • La longueur des arguments de ligne de commande est limitée par une ARG_MAXvariable qui, selon la définition POSIX, est "... [m] longueur maximale d'argument pour les fonctions exec, y compris les données d'environnement" (non souligné dans l'original) ". En d'autres termes , lorsque le shell exécute un non -built-it, il doit appeler l'un des exec()pour générer le processus de cette commande, et c'est là ARG_MAXqu'entre en jeu. De plus, le nom ou le chemin d'accès à la commande elle-même (par exemple, /bin/echo) joue un rôle.

  • Les commandes intégrées du shell sont exécutées par le shell, ce qui signifie que le shell n'utilise pas de exec()famille de fonctions et n'est donc pas affecté par la ARG_MAXvariable.

  • Certaines commandes, telles que xargset findconnaissent les ARG_MAXvariables, et effectuent à plusieurs reprises des actions sous cette limite

D'après les points ci-dessus et comme le montre l'excellente réponse de Kusalananda sur une question connexe, le Argument list too longpeut également se produire lorsque l'environnement est grand. Donc, en tenant compte du fait que l'environnement de chaque utilisateur peut varier et que la taille des arguments en octets est pertinente, il est difficile de trouver un seul nombre de fichiers / arguments.

Comment gérer une telle erreur?

L'essentiel est de ne pas se concentrer sur le nombre de fichiers, mais de se demander si la commande que vous allez utiliser implique ou non une exec()famille de fonctions et tangentiellement - l'espace de pile.

Utiliser les shell intégrés

Comme discuté précédemment, les fonctions intégrées du shell sont à l'abri des ARG_MAXlimites, c'est-à-dire des choses telles que forboucle, whileboucle, intégrée echoet intégrée printf- toutes celles-ci fonctionneront assez bien.

for i in /path/to/dir/*; do cp "$i" /path/to/other/dir/; done

Sur la question connexe concernant la suppression de fichiers, il y avait une solution en tant que telle:

printf '%s\0' *.jpg | xargs -0 rm --

Notez que cela utilise le shell intégré printf. Si nous appelons l'externe printf, cela impliquera exec(), donc échouera avec un grand nombre d'arguments:

$ /usr/bin/printf "%s\0" {1..7000000}> /dev/null
bash: /usr/bin/printf: Argument list too long

tableaux bash

Selon une réponse de jlliagre, bashn'impose pas de limites aux tableaux, donc la construction d'un tableau de noms de fichiers et l'utilisation de tranches par itération de boucle peuvent également être effectuées, comme le montre la réponse de danjpreron :

files=( /path/to/old_dir/*.prj )
for((I=0;I<${#files[*]};I+=1000)); do 
    cp -t /path/to/new_dir/ "${files[@]:I:1000}" 
done

Cependant, ceci a la limitation d'être spécifique à bash et non POSIX.

Augmentez l'espace de pile

Parfois, vous pouvez voir des gens suggérer d' augmenter l'espace de pile avec ulimit -s <NUM>; sous Linux, la valeur ARG_MAX est de 1 / 4ème de l'espace de pile pour chaque programme, ce qui signifie que l'augmentation de l'espace de pile augmente proportionnellement l'espace pour les arguments.

# getconf reports value in bytes, ulimit -s in kilobytes
$ getconf ARG_MAX
2097152
$ echo $((  $(getconf ARG_MAX)*4 ))
8388608
$ printf "%dK\n" $(ulimit -s) | numfmt --from=iec --to=none
8388608
# Increasing stack space results in increated ARG_MAX value
$ ulimit -s 16384
$ getconf ARG_MAX
4194304

Selon la réponse de Franck Dernoncourt , qui cite Linux Journal, on peut également recompiler le noyau Linux avec une plus grande valeur pour les pages de mémoire maximales pour les arguments, cependant, c'est plus de travail que nécessaire et ouvre des possibilités d'exploits comme indiqué dans l'article cité du Linux Journal.

Évitez la coquille

Une autre façon, est d'utiliser pythonou python3qui vient par défaut avec Ubuntu. L'exemple python + here-doc ci-dessous, est quelque chose que j'ai personnellement utilisé pour copier un grand répertoire de fichiers quelque part dans la plage de 40 000 éléments:

$ python <<EOF
> import shutil
> import os
> for f in os.listdir('.'):
>    if os.path.isfile(f):
>         shutil.copy(f,'./newdir/')
> EOF

Pour les parcours récursifs, vous pouvez utiliser os.walk .

Voir également:


2

À mon humble avis, les outils optimaux pour traiter des hordes de fichiers sont findet xargs. Tu vois man find. Tu vois man xargs. find, avec son -print0commutateur, produit une NULliste séparée des noms de fichiers (les noms de fichiers peuvent contenir n'importe quel caractère NULou /) qui xargscomprend, en utilisant le -0commutateur. xargspuis construit la commande la plus longue autorisée (le plus de noms de fichiers, pas de demi-nom de fichier à la fin) et l'exécute. xargsrépète cette opération jusqu'à ce qu'il findne fournisse plus de noms de fichiers. Courez xargs --show-limits </dev/nullpour voir les limites.

Pour résoudre votre problème, (et après vérification man cppour trouver --target-directory=):

find . -maxdepth 1 -type f -name '*.prj' -print0 | xargs -0 cp --target-directory=../prjshp/
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.