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?
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 cp
sur les fichiers en morceaux. La commande find
sait comment procéder:
find -maxdepth 1 -name '*.prj' -exec mv -t ../prjshp {} +
find
parcourt 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 find
et 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 find
name__: 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 rsync
name__.
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).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
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 xargs
et find
connaissent 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.
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 for
name__, la boucle while
name__, la commande intégrée echo
et la commande intégrée printf
name__: 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 printf
de Shell intégré. Si nous appelons l'externe printf
name__, 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, bash
n'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 python
ou 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 .
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/