web-dev-qa-db-fra.com

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

Je viens de poser une question liée à la façon dont je peux compter les fichiers avec une extension particulière. Maintenant, je veux cp ces fichiers dans un nouveau dir.

J'essaie,

cp *.prj ../prjshp/

et

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

mais ils donnent la même erreur,

bash:/bin/cp: la liste d'arguments est trop longue

Comment puis-je les copier?

24
Sam007

cp *.prj ../prjshp/ est la bonne commande, mais vous avez touché un cas rare où la taille est limitée. La deuxième commande que vous avez essayée n'a aucun sens.

Une méthode consiste à exécuter cpsur les fichiers en morceaux. La commande findsait comment procéder:

find -maxdepth 1 -name '*.prj' -exec mv -t ../prjshp {} +
  • findparcourt le répertoire en cours et les répertoires situés en dessous de celui-ci de manière récursive.
  • -maxdepth 1 signifie qu’il faut s’arrêter à une profondeur de 1, c’est-à-dire ne pas recurse dans des 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 commande findet non 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 ../prjshp déplace les fichiers spécifiés dans ../prjshp. L'option -t est utilisée ici en raison d'une limitation de la commande findname__: les fichiers trouvés (symbolisés par {}) sont passés en tant que dernier argument de la commande. Vous ne pouvez pas ajouter la destination après.

Une autre méthode consiste à utiliser rsyncname__.

rsync -r --include='*.prj' --exclude='*' . ../prjshp
  • rsync -r … . ../prjshp copie le répertoire en cours dans ../prjshp de manière récursive.
  • --include='*.prj' --exclude='*' signifie copier les fichiers correspondant à *.prj et exclure tout le reste (y compris les sous-répertoires, afin que les fichiers .prj des sous-répertoires ne soient pas trouvés).
33
Gilles

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

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

Il y a 3 points clés à garder à l'esprit lorsque vous faites face à l'erreur Argument list too long:

  • La longueur des arguments en ligne de commande est limitée par la variable ARG_MAX qui, par définition POSIX est "... [m] longueur maximale de l'argument des fonctions exec , y compris les données d'environnement "(caractères gras ajoutés)". C'est-à-dire que, lorsque Shell exécute une commande non intégrée, il doit appeler l'un des exec() pour générer le processus de cette commande, et c'est là que ARG_MAX arrive En outre, 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 la famille de fonctions exec() et n'est donc pas affecté par la variable ARG_MAX.

  • Certaines commandes, telles que xargset findconnaissent la variable ARG_MAX et exécutent à plusieurs reprises des actions inférieures à cette limite.

D'après les points ci-dessus et comme indiqué dans excellente réponse de Kusalananda à une question connexe, le Argument list too long peut également apparaître lorsque l'environnement est grand. Donc, compte tenu du fait que l'environnement de chaque utilisateur peut varier et que la taille de l'argument en octets est pertinente, il est difficile de créer un nombre unique de fichiers/arguments.

Comment gérer une telle erreur?

L'essentiel est de ne pas se focaliser sur le nombre de fichiers, mais bien de savoir si la commande que vous allez utiliser implique la famille de fonctions exec() et de manière tangentielle - l'espace de pile.

Utiliser les fonctions intégrées de Shell

Comme indiqué précédemment, les fonctions intégrées du shell sont insensibles à la limite ARG_MAX, telles que la boucle forname__, la boucle whilename__, la commande intégrée echoet la commande intégrée printfname__: toutes ces performances seront suffisamment performantes.

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

Sur question connexe sur 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 printfde Shell intégré. Si nous appelons l'externe printfname__, cela impliquera exec() et échouera donc avec un grand nombre d'arguments:

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

tableaux de bash

Selon ne 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 danjpreron's réponse :

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

Ceci, cependant, a la limitation d'être spécifique à bash et non-POSIX.

Augmenter l'espace de la pile

Parfois, vous pouvez voir des personnes suggérer augmenter l'espace de la pile avec ulimit -s <NUM>; sous Linux, la valeur de ARG_MAX est égale à 1/4 de l’espace de pile pour chaque programme, ce qui signifie que l’augmentation de l’espace de pile augmente proportionnellement l’espace alloué aux 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 réponse de Franck Dernoncourt , qui cite Linux Journal, on peut aussi recompiler un noyau Linux avec une valeur plus grande pour un maximum de pages de mémoire pour les arguments, cependant, cela demande plus de travail que nécessaire et ouvre un potentiel pour des exploits comme indiqué dans l'article cité de Linux Journal.

Évitez Shell

Une autre façon consiste à utiliser pythonou python3 qui sont fournis 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 traversées récursives, vous pouvez utiliser os.walk .

Voir également:

4

IMHO, les outils optimaux pour gérer les hordes de fichiers sont find et xargs. Voir man find. Voir man xargs. find, avec son commutateur -print0, produit une liste de noms de fichiers séparés par NUL- (les noms de fichiers peuvent contenir n'importe quel caractère execpt NUL ou /) compris par xargs, à l'aide du commutateur -0. xargs construit ensuite la commande la plus longue autorisée (le plus grand nombre de noms de fichiers, sans demi-nom de fichier à la fin) et l'exécute. xargs répète cette opération jusqu'à ce que find ne fournisse plus de noms de fichiers. Exécutez xargs --show-limits </dev/null pour voir les limites.

Pour résoudre votre problème (et après avoir vérifié man cp afin de trouver --target-directory=):

find . -maxdepth 1 -type f -name '*.prj' -print0 | xargs -0 cp --target-directory=../prjshp/
2
waltinator