Warum funktioniert find -exec mv {} ./target/ + nicht?

98

Ich möchte genau wissen, was {} \;und {} \+und | xargs .... Bitte klären Sie diese mit Erläuterungen.

Unter 3 Befehlen wird das gleiche Ergebnis ausgeführt und ausgegeben, aber der erste Befehl benötigt etwas Zeit und das Format unterscheidet sich ebenfalls geringfügig.

find . -type f -exec file {} \;
find . -type f -exec file {} \+
find . -type f | xargs file

Dies liegt daran, dass der erste fileBefehl für jede Datei ausgeführt wird, die vom findBefehl stammt. Im Grunde läuft es also wie folgt:

file file1.txt
file file2.txt

Aber letztere 2 finden mit -execBefehlen den Befehl file einmal für alle Dateien wie unten ausführen:

file file1.txt file2.txt

Dann führe ich die folgenden Befehle aus, bei denen der erste ohne Probleme ausgeführt wird, der zweite jedoch eine Fehlermeldung ausgibt.

find . -type f -iname '*.cpp' -exec mv {} ./test/ \;
find . -type f -iname '*.cpp' -exec mv {} ./test/ \+ #gives error:find: missing argument to `-exec'

Für Befehl mit {} \+gibt es mir die Fehlermeldung

find: missing argument to `-exec'

warum ist das so? Kann mir bitte jemand erklären, was ich falsch mache?

Shahadat Hossain
quelle
Die eigentliche Frage ist einfach: Warum funktioniert die erste und die zweite nicht? (1) finden. -type f -iname ' .cpp' -exec mv {} ./test/ \; (2) finden. -Typ f -Iname ' .cpp' -exec mv {} ./test/ \ +
Shahadat Hossain

Antworten:

185

Die Handbuchseite (oder das Online-GNU-Handbuch ) erklärt so ziemlich alles.

find -exec Befehl {} \;

Für jedes Ergebnis command {}wird ausgeführt. Alle Vorkommen von {}werden durch den Dateinamen ersetzt. ;wird mit einem Schrägstrich versehen, um zu verhindern, dass die Shell ihn interpretiert.

find -exec Befehl {} +

Jedes Ergebnis wird angehängt commandund anschließend ausgeführt. Unter Berücksichtigung der Befehlslängenbeschränkungen kann dieser Befehl möglicherweise mehrmals ausgeführt werden, wobei die Handbuchseite mich unterstützt:

Die Gesamtzahl der Aufrufe des Befehls ist viel geringer als die Anzahl der übereinstimmenden Dateien.

Beachten Sie dieses Zitat aus der Handbuchseite:

Die Befehlszeile wird ähnlich wie xargs seine Befehlszeilen erstellt

Aus diesem Grund sind keine Zeichen zwischen {}und +außer Leerzeichen zulässig . +lässt find erkennen, dass die Argumente genau wie an den Befehl angehängt werden sollen xargs.

Die Lösung

Glücklicherweise kann die GNU-Implementierung von mvdas Zielverzeichnis als Argument mit einem -toder dem längeren Parameter akzeptieren --target. Die Verwendung wird sein:

mv -t target file1 file2 ...

Ihr findBefehl wird:

find . -type f -iname '*.cpp' -exec mv -t ./test/ {} \+

Von der Handbuchseite:

-exec Befehl;

Befehl ausführen; true, wenn der Status 0 zurückgegeben wird. Alle folgenden zu findenden Argumente gelten als Argumente für den Befehl, bis ein Argument aus `; ' angetroffen wird. Die Zeichenfolge "{}" wird durch den aktuellen Dateinamen ersetzt, der überall dort verarbeitet wird, wo er in den Argumenten des Befehls vorkommt, nicht nur in Argumenten, in denen er allein ist, wie in einigen Versionen von find. Diese beiden Konstruktionen müssen möglicherweise maskiert (mit einem "\") oder in Anführungszeichen gesetzt werden, um sie vor der Erweiterung durch die Shell zu schützen. Im Abschnitt BEISPIELE finden Sie Beispiele für die Verwendung der Option -exec. Der angegebene Befehl wird einmal für jede übereinstimmende Datei ausgeführt. Der Befehl wird im Startverzeichnis ausgeführt. Es gibt unvermeidbare Sicherheitsprobleme bei der Verwendung der Aktion -exec. Sie sollten stattdessen die Option -execdir verwenden.

-exec Befehl {} +

Diese Variante der Aktion -exec führt den angegebenen Befehl für die ausgewählten Dateien aus. Die Befehlszeile wird jedoch erstellt, indem jeder ausgewählte Dateiname am Ende angehängt wird. Die Gesamtzahl der Aufrufe des Befehls ist viel geringer als die Anzahl der übereinstimmenden Dateien. Die Befehlszeile wird ähnlich wie xargs seine Befehlszeilen erstellt. Innerhalb des Befehls ist nur eine Instanz von "{}" zulässig. Der Befehl wird im Startverzeichnis ausgeführt.

Lekensteyn
quelle
1
Ich weiß tatsächlich, wie es funktioniert. Ich habe dieses Handbuch mehrmals durchgesehen, aber ich habe eine Fehlermeldung für die Verwendung von {} + erhalten, obwohl es für {} \ funktioniert. und ich benutze Cygwin in Windows.
Shahadat Hossain
1
@ Shahadat: Hast du den Teil vor "Von der Handbuchseite" gelesen? Sie haben ./test/zwischen {}und gesetzt +, aber zwischen diesen sind keine Nicht-Leerzeichen zulässig.
Lekensteyn
ru sagt, dass ich ./test/ nicht zwischen {} und + setzen soll. Wie funktioniert dann der Befehl mv? mv benötigt die Quelle {} und das Ziel ./test/ und die Beendigung mit +. Kannst du bitte den Befehl schreiben, was du für richtig hältst?
Shahadat Hossain
@ Shahadat: Ich sehe, was Sie erreichen wollen. Windows führt Programme nur langsam aus, daher möchten Sie es zu einem Befehl kombinieren. Ich werde der Antwort eine Alternative hinzufügen.
Lekensteyn
1
Der +Befehl ist ein bisschen seltsam AFAIU, da er die Dateien am "Ende" (und nicht anstelle von {}) festhält. Warum also {}überhaupt verwenden - das ist verwirrend. Vielen Dank für die -tOption, die ich nicht kannte. Es scheint, dass diese Option als -exec +Problemumgehung für dieses Problem erstellt wurde!
e2-e4
6

Unter Mac OS X ist bei der Verwendung einer ZSH- Shell dasselbe Problem aufgetreten : In diesem Fall gibt es keine -tOption für mv, sodass ich eine andere Lösung finden musste. Der folgende Befehl war jedoch erfolgreich:

find .* * -maxdepth 0 -not -path '.git' -not -path '.backup' -exec mv '{}' .backup \;

Das Geheimnis war , die Zahnspange zu zitieren . Die Klammern müssen sich nicht am Ende des execBefehls befinden.

Ich habe unter Ubuntu 14.04 (mit BASH- und ZSH- Shells) getestet , es funktioniert genauso.

Bei Verwendung des +Zeichens muss es jedoch tatsächlich am Ende des execBefehls stehen.

Arvymetall
quelle
{}muss in den fishund rcMuscheln zitiert werden , aber nicht in zsh, bashnoch in irgendwelchen anderen Muscheln der Bourne oder csh Familien.
Stephane Chazelas
@StephaneChazelas Yep, erneut unter einem Ubuntu mit getestet bash, in der Tat sind die Anführungszeichen nicht notwendig. Seltsamerweise hatte ich ein Problem, wenn ich sie nicht unter MacOS zitierte (mit zsh). Aber ich habe keinen Mac in Reichweite, um es erneut zu versuchen ...
Arvymetal
3

Das Standardäquivalent find -iname ... -exec mv -t dest {} +für findImplementierungen, die nicht unterstützt werden, -inameoder mvImplementierungen, die nicht unterstützt werden, -tist die Verwendung einer Shell, um die Argumente neu zu ordnen:

find . -name '*.[cC][pP][pP]' -type f -exec sh -c '
  exec mv "$@" /dest/dir/' sh {} +

Durch die Verwendung -name '*.[cC][pP][pP]'vermeiden wir auch die Abhängigkeit vom aktuellen Gebietsschema, um zu entscheiden, welche Großbuchstabenversion von coder verwendet wird p.

Beachten Sie, dass +im Gegensatz dazu ;in keiner Shell etwas Besonderes ist und daher nicht in Anführungszeichen gesetzt werden muss (obwohl das Zitieren nicht schadet, außer natürlich bei solchen Shells rc, die \als Anführungszeichen-Operator nicht unterstützt werden).

Der nachlauf /in /dest/dir/ist , so dass mvmit einem Fehler fehlschlägt statt Umbenennung foo.cppauf /dest/dirdem Fall, dass nur eine cppDatei gefunden wurde und /dest/dirnicht existierte oder war kein Verzeichnis (oder Symlink - Verzeichnis).

Stephane Chazelas
quelle
+1 ... In-Shell-Operation als Vorstufe zur Ausführung eines Befehls ist tatsächlich für eine Vielzahl von Anwendungsfällen nützlich ... nett.
Cbhihe
0
find . -name "*.mp3" -exec mv --target-directory=/home/d0k/Музика/ {} \+
DarkLabs
quelle
Bitte fügen Sie Ihrer Antwort eine Erklärung hinzu, damit andere daraus lernen können
Nico Haase,
Sie müssen die Frage beantworten, die zur Erklärung aufgefordert wurde. Nur Code ist keine Antwort.
Lajos Arpad
-1

Nein, der Unterschied zwischen +und \;sollte umgekehrt werden. +Hängt die Dateien an das Ende des Befehls exec an, \;führt dann den Befehl exec aus und führt den Befehl für jede Datei aus.

Das Problem ist, find . -type f -iname '*.cpp' -exec mv {} ./test/ \+dass es find . -type f -iname '*.cpp' -exec mv {} ./test/ + nicht nötig sein sollte, dem zu entkommen oder das zu beenden+

xargs habe ich schon lange nicht mehr benutzt, aber ich denke, es funktioniert wie +.

Mike Ramirez
quelle
Ich habe es auch damit versucht, aber die gleiche Fehlermeldung erhalten. Außerdem habe ich überall nur + verwendet, aber in meinem Cygwin muss ich \ + oder "+" verwenden, um zu arbeiten.
Shahadat Hossain
Oh, das ist eine Cygwin-Umgebung. Entschuldigung, dann weiß ich nicht, ich benutze nicht die Cygwin-Shell, ich benutze nur ein * nix.
Mike Ramirez
1
@ Shahadat Hossain versuchen -name "*.cpp"Ich benutze -iname kaum, es sei denn, ich möchte eine schwierige Regex-Suche durchführen, wie -iname '??? work. * \. Cpp'
Mike Ramirez
1
@ Mike: Ich denke du verstehst den Unterschied zwischen -inameund falsch -name. -inameist die Version, bei der die Groß- und Kleinschreibung nicht berücksichtigt wird, -nameund weist keine Unterschiede im Umgang mit regulären Ausdrücken auf. Ich schlage vor, Befehle vor dem Posten auszuprobieren. Ihr Befehl schlägt auch in meiner Shell fehl.
Lekensteyn
1
@Lekensteyn Es wurde bereits vor Ihrem Kommentar festgestellt, dass dies der Fall war. Ich dachte, ich hätte Shahadat vor Ihrem Beitrag bestätigt, es war ein einfaches "OK". Nein, ich habe es nicht manuell ausgeführt, sondern von oben und verwende diese Form der Regex-Suche selten mit find. Es war nur eine Sache, die helfen könnte.
Mike Ramirez