Argumentliste beim Kopieren von Dateien zu lang

26

Ich habe gerade eine Frage gestellt, die sich darauf bezieht, wie ich die Dateien einer bestimmten Erweiterung zählen kann. Nun möchte ich cpdiesen Dateien einen neuen hinzufügen dir.

Ich versuche,

cp *.prj ../prjshp/

und

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

aber sie geben den gleichen Fehler,

bash: / bin / cp: Argumentliste zu lang

Wie kopiere ich sie?

Sam007
quelle

Antworten:

36

cp *.prj ../prjshp/ist der richtige Befehl, aber in seltenen Fällen stößt er auf eine Größenbeschränkung. Der zweite Befehl, den Sie ausprobiert haben, ergibt keinen Sinn.

Eine Methode besteht darin, cpdie Dateien in Blöcken auszuführen . Der findBefehl weiß, wie das geht:

find -maxdepth 1 -name '*.prj' -exec mv -t ../prjshp {} +
  • find Durchläuft das aktuelle Verzeichnis und die Verzeichnisse darunter rekursiv.
  • -maxdepth 1 bedeutet, bei einer Tiefe von 1 anzuhalten, dh nicht in Unterverzeichnisse zurückzukehren.
  • -name '*.prj'bedeutet, nur auf die Dateien zuzugreifen, deren Name mit dem angegebenen Muster übereinstimmt. Beachten Sie die Anführungszeichen um das Muster: Es wird vom findBefehl und nicht von der Shell interpretiert .
  • -exec … {} +bedeutet, den angegebenen Befehl für alle Dateien auszuführen. Bei Bedarf wird der Befehl mehrmals aufgerufen, wobei darauf geachtet wird, dass das Befehlszeilenlimit nicht überschritten wird.
  • mv -t ../prjshpverschiebt die angegebenen Dateien in ../prjshp. Die -tOption wird hier aufgrund einer Einschränkung des findBefehls verwendet: Die gefundenen Dateien (symbolisiert durch {}) werden als letztes Argument des Befehls übergeben. Sie können das Ziel nicht danach hinzufügen.

Eine andere Methode ist zu verwenden rsync.

rsync -r --include='*.prj' --exclude='*' . ../prjshp
  • rsync -r … . ../prjshpkopiert das aktuelle Verzeichnis nach ../prjshprekursiv.
  • --include='*.prj' --exclude='*'bedeutet, dass Dateien kopiert werden, die übereinstimmen, *.prjund alles andere ausgeschlossen wird (einschließlich Unterverzeichnissen, sodass .prjDateien in Unterverzeichnissen nicht gefunden werden).
Gilles 'SO - hör auf böse zu sein'
quelle
3
rsync ist hier mit Abstand die einfachste Lösung.
ntk4
Etwas pingelig zu sein, macht der zweite Befehl cp * | grep '\.prj$' ../prjshp/ keinen Sinn, kann aber syntaktisch gültig sein, wenn er *sich zu einer Liste von Dateien erweitert, wobei der letzte ein Verzeichnis (aka cp SOURCE1 SOURCE2....DEST) ist. Die Pipe macht sicher keinen Sinn, bleibt aber auch syntaktisch gültig, was die Shell betrifft - dup()die Dateideskriptoren sind in Ordnung, nur, dass das Leserende der Pipe keine Daten erhält, weil cpes keine schreibt .
Sergiy Kolodyazhnyy
Sowohl find als auch rsync haben für mich die gleiche Argumentliste mit einem zu langen Fehler erzeugt. Die for-Schleife war die einfachste Problemumgehung.
Meezaan-ud-Din
In der Tat ist Rsync der Weg, um Massenkopien zu erstellen, obwohl ich erstaunt bin, wie weit wir mit Linux gekommen sind und wir einen dummen Fehler wie diesen haben, und ja, ich würde es als Fehler betrachten.
MitchellK
22

Dieser Befehl kopiert die Dateien nacheinander und funktioniert auch dann, wenn es zu viele gibt *, um sie zu einem einzigen cpBefehl zu erweitern :

for i in *; do cp "$i" ../prjshp/; done
ccshields
quelle
Das funktioniert bei mir.
1rq3fea324wre
1
Einfach und effektiv. Ich hatte ein ähnliches Problem beim Entfernen von ~ 1/4 Millionen JPEGs, die ich aus einem Video für ein Projekt extrahiert hatte. Dies ist der Ansatz, den ich verwendet habe.
Elder Geek
5

Bei Argument list too longFehlern sind drei wichtige Punkte zu beachten :

  • Die Länge der Befehlszeilenargumente ist durch eine ARG_MAXVariable begrenzt , die laut POSIX-Definition "... [m] maximale Länge der Argumente für die exec-Funktionen einschließlich Umgebungsdaten" (Hervorhebung hinzugefügt) ist. Das heißt, wenn die Shell ein non ausführt -built-it-Befehl, muss einen von aufrufen exec(), um den Befehlsprozess zu erzeugen, und genau hier ARG_MAXkommt es an. Außerdem spielt der Name oder der Pfad zum Befehl selbst (zum Beispiel /bin/echo) eine Rolle.

  • Shell-Befehle werden von der Shell ausgeführt. Dies bedeutet, dass die Shell keine exec()Funktionsfamilie verwendet und daher von ARG_MAXVariablen nicht betroffen ist.

  • Bestimmte Befehle, wie z. B. xargsund findkennen ARG_MAXVariablen und führen Aktionen unter dieser Grenze wiederholt aus

Aus den obigen Punkten und wie in Kusalanandas hervorragender Antwort auf verwandte Fragen gezeigt, Argument list too longkann dies auch auftreten, wenn die Umgebung groß ist. In Anbetracht der Tatsache, dass die Umgebung jedes Benutzers variieren kann und die Argumentgröße in Byte relevant ist, ist es schwierig, eine einzige Anzahl von Dateien / Argumenten zu finden.

Wie gehe ich mit solchen Fehlern um?

Das Wichtigste ist, sich nicht auf die Anzahl der Dateien zu konzentrieren, sondern darauf, ob der Befehl, den Sie verwenden möchten, eine exec()Funktionsfamilie und tangential - den Stapelspeicherplatz - umfasst.

Verwenden Sie Shell-Built-Ins

Wie bereits erwähnt, sind die Shell-Built-Ins unempfindlich gegen ARG_MAXEinschränkungen, wie z. B. forLoop, whileLoop, Built-In echound Built-In. printfAll diese sind ausreichend leistungsfähig.

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

Bei verwandten Fragen zum Löschen von Dateien gab es eine Lösung als solche:

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

Beachten Sie, dass hierfür die integrierte Shell verwendet wird printf. Wenn wir das Externe anrufen printf, wird dies einbeziehen exec()und daher mit einer großen Anzahl von Argumenten scheitern:

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

Bash-Arrays

Laut einer Antwort von jlliagre bashgibt es keine Einschränkungen für Arrays. Daher können auch Arrays von Dateinamen erstellt und Slices pro Schleifeniteration verwendet werden, wie in der Antwort von danjpreron gezeigt :

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

Dies hat jedoch die Einschränkung, bash-spezifisch und nicht POSIX-spezifisch zu sein.

Erhöhen Sie den Stapelplatz

Manchmal kann man die Menschen sehen , deuten darauf hin , Erhöhung der Stapelspeicher mit ulimit -s <NUM>; Unter Linux beträgt der ARG_MAX-Wert 1/4 des Stapelspeichers für jedes Programm, was bedeutet, dass durch Erhöhen des Stapelspeichers der Speicherplatz für Argumente proportional erhöht wird.

# 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

Laut der Antwort von Franck Dernoncourt , der das Linux Journal zitiert, kann man den Linux-Kernel auch mit einem höheren Wert für maximale Speicherseiten für Argumente neu kompilieren. Dies ist jedoch mehr Arbeit als nötig und eröffnet das Potenzial für Exploits, wie im zitierten Artikel im Linux Journal angegeben.

Vermeiden Sie Muscheln

Eine andere Möglichkeit ist die Verwendung von pythonoder python3die standardmäßig mit Ubuntu kommen. Das folgende Python + Here-Doc- Beispiel wurde von mir persönlich verwendet, um ein großes Verzeichnis mit Dateien im Bereich von 40.000 Elementen zu kopieren:

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

Für rekursive Durchläufe können Sie os.walk verwenden .

Siehe auch:

Sergiy Kolodyazhnyy
quelle
2

IMHO, die optimalen Werkzeuge für die mit Horden von Dateien zu tun sind findund xargs. Sehen man find. Sehen man xargs. findMit dem -print0Schalter wird eine NULseparate Liste von Dateinamen erstellt (Dateinamen können alle Zeichen enthalten, die ausgeführt werden NULoder /), xargsdie mit dem -0Schalter verstanden werden. xargsDann wird der längste zulässige Befehl erstellt (die meisten Dateinamen, kein halber Dateiname am Ende) und ausgeführt. xargsWiederholt dies, bis findkeine Dateinamen mehr angegeben werden. Laufen Sie xargs --show-limits </dev/null, um die Grenzen zu sehen.

Um Ihr Problem zu lösen (und nach Überprüfung man cpzu finden --target-directory=):

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