Erreur «Liste d'arguments trop longue» lors de la copie d'un grand nombre de fichiers

12

J'utilise la commande suivante:

\cp -uf /home/ftpuser1/public_html/ftparea/*.jpg /home/ftpuser2/public_html/ftparea/

Et je reçois l'erreur:

-bash: /bin/cp: Argument list too long

J'ai aussi essayé:

ls /home/ftpuser1/public_html/ftparea/*.jpg | xargs -I {} cp -uf {} /home/ftpuser2/public_html/ftparea/

Toujours obtenu -bash: / bin / ls: liste d'arguments trop longue

Des idées?

icelizard
la source
J'essaie de copier tous les fichiers jpgs d'un répertoire vers un autre, mais uniquement les nouveaux fichiers et ceux qui ont été mis à jour.
icelizard
lsn'est pas conçu pour faire ce genre de chose. Utilisez find.
pause jusqu'à nouvel ordre.
Le problème n'est pas avec ls, c'est avec le nombre d'arguments que le shell passe à ls. Vous obtiendriez la même erreur avec vi ou avec n'importe quelle commande non intégrée.
chris
Mais lsn'est surtout pas conçu pour cela: mywiki.wooledge.org/ParsingLs
jusqu'à nouvel ordre.
Certes, mais dans ce cas, l'erreur n'est pas due à une erreur d'analyse avec ls, mais à la transmission d'un milliard d'arguments à un nouveau processus qui se trouve être ls. En plus d'être une utilisation inappropriée de ls, il arrive également de se heurter à une limitation de ressources / conception d'Unix. Dans ce cas, le patient a à la fois un mal de ventre et une jambe cassée.
chris

Réponses:

19

* .jpg se développe en une liste plus longue que le shell ne peut gérer. Essayez ceci à la place

find  /home/ftpuser/public_html/ftparea/ -name "*.jpg" -exec cp -uf "{}" /your/destination \;
Shawn Chin
la source
J'ai utilisé find / home / ftpuser1 / public_html / ftparea / -name "* jpg" -exec cp -uf "{}" / home / ftpuser2 / public_html / ftparea / et j'ai obtenu l'erreur suivante find: argument manquant à `-exec '
icelizard
Vous manquez le dernier argument de cp, le répondeur vous a bien dit. Vérifiez votre implémentation. Notez que dans cette réponse, le point dans "* .jpg" est manquant, cela pourrait conduire à des mauvais comportements (cp un dir nommé "myjpg" par exemple). Notez alors que cela peut être paranoïaque mais plus sûr de spécifier de près ce que vous allez copier en utilisant le fichier -type (en empêchant les dirs, les liens symboliques, etc.)
drAlberT
Après une inspection plus approfondie, j'ai raté le "\;" pour terminer la commande que -exec doit exécuter. Que je suis bête!
icelizard
@AlberT: merci pour les têtes concernant le point manquant. C'était une faute de frappe. Réponse mise à jour.
Shawn Chin
Ce n'est pas que cp ne puisse pas le gérer. La coquille ne peut pas.
d -_- b
6

Il y a une limite maximale à la longueur d'une liste d'arguments pour les commandes système - cette limite est spécifique à la distribution en fonction de la valeur de MAX_ARG_PAGESla compilation du noyau et ne peut pas être modifiée sans recompilation du noyau.

En raison de la façon dont le globbing est géré par le shell, cela affectera la plupart des commandes système lorsque vous utilisez le même argument ("* .jpg"). Étant donné que le glob est d'abord traité par le shell, puis envoyé à la commande, la commande:

cp -uf *.jpg /targetdir/

est essentiellement le même pour le shell que si vous écriviez:

cp -uf 1.jpg 2.jpg ... n-1.jpg n.jpg /targetdir/

Si vous traitez avec beaucoup de jpegs, cela peut devenir ingérable très rapidement. Selon votre convention de dénomination et le nombre de fichiers que vous devez réellement traiter, vous pouvez exécuter la commande cp sur un sous-ensemble différent du répertoire à la fois:

cp -uf /sourcedir/[a-m]*.jpg /targetdir/
cp -uf /sourcedir/[n-z]*.jpg /targetdir/

Cela pourrait fonctionner, mais exactement son efficacité est basée sur la façon dont vous pouvez diviser votre liste de fichiers en blocs globbables pratiques.

Globbable. J'aime ce mot.

Certaines commandes, telles que find et xargs , peuvent gérer de grandes listes de fichiers sans créer de listes d'arguments douloureusement dimensionnées.

find /sourcedir/ -name '*.jpg' -exec cp -uf {} /targetdir/ \;

L'argument -exec exécutera le reste de la ligne de commande une fois pour chaque fichier trouvé par find , en remplaçant le {} par chaque nom de fichier trouvé. Étant donné que la commande cp n'est exécutée que sur un fichier à la fois, la limite de la liste d'arguments n'est pas un problème.

Cela peut être lent car vous devez traiter chaque fichier individuellement. L'utilisation de xargs pourrait fournir une solution plus efficace:

find /sourcedir/ -name '*.jpg' -print0 | xargs -0 cp -uf -t /destdir/

xargs peut prendre la liste complète des fichiers fournie par find , la décomposer en listes d'arguments de tailles gérables et exécuter cp sur chacune de ces sous-listes.

Bien sûr, il est également possible de simplement recompiler votre noyau, en définissant une valeur plus élevée pour MAX_ARG_PAGES. Mais recompiler un noyau est plus de travail que je ne veux l'expliquer dans cette réponse.

goldPseudo
la source
Je ne sais pas pourquoi cela a été rejeté. C'est la seule réponse qui semble expliquer pourquoi cela se produit. Peut-être parce que vous n'avez pas suggéré d'utiliser xargs comme optimisation?
chris
ajouté dans la solution xargs, mais je suis toujours inquiet que les downvotes soient dus à quelque chose de manifestement faux dans mes détails et personne ne veut me dire ce que c'est. :(
goldPseudo
xargssemble être beaucoup plus efficace, car le nombre d'appels de commande qui en résulte est beaucoup plus petit. Dans mon cas, je vois des performances 6 à 12 fois meilleures lors de l'utilisation, argspuis lorsque l'utilisation d'une -execsolution avec un nombre croissant de fichiers augmente l'efficacité.
Jan Vlcinsky
3

Cela se produit car votre expression générique ( *.jpg) dépasse la limite de longueur d'argument de ligne de commande lorsqu'elle est développée (probablement parce que vous avez beaucoup de fichiers .jpg sous /home/ftpuser/public_html/ftparea).

Il existe plusieurs façons de contourner cette limitation, comme l'utilisation de findou xargs. Jetez un œil à cet article pour plus de détails sur la façon de procéder.

mfriedman
la source
+1 pour la bonne ressource externe sur le sujet.
viam0Zah
3

Comme l'a souligné GoldPseudo, il y a une limite au nombre d'arguments que vous pouvez transmettre à un processus que vous générez. Voir sa réponse pour une bonne description de ce paramètre.

Vous pouvez éviter le problème en ne passant pas trop d'arguments au processus ou en réduisant le nombre d'arguments que vous passez.

Une boucle for dans le shell, find et ls, grep et une boucle while font tous la même chose dans cette situation -

for file in /path/to/directory/*.jpg ; 
do
  rm "$file"
done

et

find /path/to/directory/ -name '*.jpg' -exec rm  {} \;

et

ls /path/to/directory/ | 
  grep "\.jpg$" | 
  while
    read file
  do
    rm "$file"
  done

tous ont un programme qui lit le répertoire (le shell lui-même, find et ls) et un programme différent qui prend en fait un argument par exécution et parcourt toute la liste des commandes.

Maintenant, cela sera lent car le rm doit être bifurqué et exécuté pour chaque fichier qui correspond au modèle * .jpg.

C'est là que xargs entre en jeu. xargs prend une entrée standard et pour chaque N (pour freebsd c'est 5000 par défaut) lignes, il génère un programme avec N arguments. xargs est une optimisation des boucles ci-dessus car il vous suffit de bifurquer des programmes 1 / N pour itérer sur l'ensemble des fichiers qui lisent les arguments de la ligne de commande.

chris
la source
1

Le glob «*» s'étend à trop de noms de fichiers. Utilisez plutôt find / home / ftpuser / public_html -name '* .jpg'.

William Pursell
la source
Find et echo * aboutissent à la même sortie - la clé ici utilise xargs et pas seulement en passant tous les 1 milliard d'arguments de ligne de commande à la commande que le shell essaie de fork.
chris
echo * échouera s'il y a trop de fichiers, mais find réussira. En outre, l'utilisation de find -exec avec + équivaut à l'utilisation de xargs. (Cependant, tous ne trouvent pas de support +)
William Pursell
1

L'utilisation de l' +option pour find -execaccélérera considérablement l'opération.

find  /home/ftpuser/public_html/ftparea/ -name "*jpg" -exec cp -uf -t /your/destination "{}" +

L' +option doit {}être le dernier argument, donc utiliser l' option -t /your/destination(ou --target-directory=/your/destination) pour la cpfaire fonctionner.

De man find:

-exec commande {} +

          This  variant  of the -exec action runs the specified command on  
          the selected files, but the command line is built  by  appending  
          each  selected file name at the end; the total number of invoca  
          tions of the command will  be  much  less  than  the  number  of  
          matched  files.   The command line is built in much the same way  
          that xargs builds its command lines.  Only one instance of  ‘{}’  
          is  allowed  within the command.  The command is executed in the  
          starting directory.

Edit : réarrangement des arguments en cp

En pause jusqu'à nouvel ordre.
la source
Je reçois: argument manquant dans `-exec '/ home / ftpuser1 / public_html / ftparea / -name' * jpg '-exec cp -uf" {} "/ home / ftpuser2 / public_html / ftparea / +
icelizard
J'ai réorganisé les arguments cppour corriger cette erreur.
pause jusqu'à nouvel ordre.
1

Il semble que vous ayez trop de *.jpgfichiers dans ce répertoire pour les mettre tous sur la ligne de commande à la fois. Tu pourrais essayer:

find /home/ftpuser/public_html/ftparea1 -name '*.jpg' | xargs -I {} cp -uf {} /home/ftpuser/public_html/ftparea2/

Vous devrez peut-être vérifier man xargsvotre implémentation pour voir si le -Icommutateur est correct pour votre système.

En fait, avez-vous vraiment l'intention de copier ces fichiers au même endroit où ils se trouvent déjà?

Greg Hewgill
la source
excuses ce sont deux répertoires différents devraient être ftpuser1 et ftpuser2
icelizard
Je viens d'essayer ceci: ls /home/ftpuser1/public_html/ftparea/*.jpg | xargs -I {} cp -uf {} / home / ftpuser2 / public_html / ftparea / Toujours obtenu -bash: / bin / ls: Liste d'arguments trop longue
icelizard
Oh, vous avez tout à fait raison, bien sûr, lsaura le même problème! J'ai changé pour findce qui ne le sera pas.
Greg Hewgill
0

Accéder au dossier

cd /home/ftpuser1/public_html/

et exécutez ce qui suit:

cp -R ftparea/ /home/ftpuser2/public_html/

De cette façon, si le dossier 'ftparea' a des sous-dossiers, cela pourrait être un effet négatif si vous n'en voulez que les fichiers '* .jpg', mais s'il n'y a pas de sous-dossiers, cette approche sera certainement beaucoup plus rapide que en utilisant find et xargs

pinpinokio
la source