Wenn ich diesen Befehl verwende mv * ..
, erhalte ich den Fehler -bash: /usr/bin/mv: Argument list too long
. Ich verstehe, dass der Grund dafür darin liegt, dass Bash das Sternchen tatsächlich auf jede passende Datei erweitert, wodurch eine sehr lange Befehlszeile entsteht. Wie kann ich diesen Fehler beheben?
Antwort1
Die Begrenzung liegt nicht in Bash, sondern im execve()
Systemaufruf, der zum Ausführen externer Befehle verwendet wird. Sie könnten Folgendes tun:
printf '%s\0' * | xargs -r0 mv -t .. --
Da printf
in eingebaut ist bash
, gibt es execve()
an dieser Stelle kein . Und es ist seine xargs
Aufgabe, die Liste der Argumente aufzuteilen, um diese Begrenzung zu vermeiden. Hier wird die GNU-spezifische -t
Option von GNU verwendet mv
.
Mit zsh
können Sie das mv
integrierte Programm laden:
zmodload zsh/files
mv -- * ..
Oder verwenden Sie den zargs
Helfer zum Aufteilen:
autoload -Uz zargs # best in ~/.zshrc
zargs -r -- ./* -- mv -t ..
Sie können es ./*
durch ersetzen ./*(D)
, um auch versteckte Dateien zu verschieben, oder den oN
Glob-Qualifizierer hinzufügen, um die Sortierung der Dateinamen zu überspringen, oder den N
Glob-Qualifizierer (mit zargs -r
), um den Fehler zu vermeiden, wenn keine passende Datei vorhanden ist.
zargs -r -- ./*(ND) -- mv -t ..
Gleich wie:
print -rNC1 ./*(ND) | xargs -r0 mv -t ..
Aber ohne die Abhängigkeit zu GNU xargs
.
Unter Linux (dem Kernel, der normalerweise unter Ubuntu verwendet wird) können Sie dieses execve()
Limit auch erhöhen, indem Sie das stacksize
Ressourcenlimit erhöhen:
bash-5.1$ /bin/true {1..200000}
bash: /bin/true: Argument list too long
bash-5.1$ ulimit -s unlimited
bash-5.1$ /bin/true {1..200000}
bash-5.1$
Es ist nicht völlig unbegrenzt (zumindest nicht in aktuellen Versionen des Kernels):
bash-5.1$ /bin/true {1..2000000}
bash: /bin/true: Argument list too long
Beachten Sie, dass die Begrenzung auf der kumulierten Größe der Argumente und der an übergebenen Umgebungsvariablen liegt execve()
, die Berechnung jedoch nicht nur aus der Summe der darin enthaltenen Bytes besteht und die Vorgehensweise je nach Betriebssystem und Version unterschiedlich ist.
Antwort2
Sie können es in zwei oder mehr Schritten tun:
mv [a-k]* .. # or some other pattern matching a subset of the files
mv -- * ..
Oder in einer Schleife,
for name in *; do
mv -- "$name" ..
done
(Dazu müsste allerdings mv
jeder einzelne Name einzeln behandelt werden.)
Oder lass find
dir helfen:
find . -mindepth 1 -maxdepth 1 -exec mv -t .. -- {} +
Dadurch werden alle Namen im aktuellen Verzeichnis gefunden und mithilfe von GNU mit möglichst mv
wenigen Aufrufen von in das darüber liegende Verzeichnis verschoben .mv
Ohne GNU mv
, aber mit einem find
, das immer noch die Nicht-Standard- -mindepth
und -maxdepth
Prädikate kennt,
find . -mindepth 1 -maxdepth 1 -exec sh -c 'mv -- "$@" ..' sh {} +
Keine dieser Varianten kümmert sich um Namenskollisionen. Sie sollten mit Daten testen, die ordnungsgemäß gesichert sind.