Wie kann ich xargs verwenden, um Dateien zu kopieren, deren Namen Leerzeichen und Anführungszeichen enthalten?

232

Ich versuche, eine Reihe von Dateien unter ein Verzeichnis zu kopieren, und einige Dateien enthalten Leerzeichen und einfache Anführungszeichen. Wenn ich versuche, zusammen findund grepmit zu fädeln xargs, erhalte ich die folgende Fehlermeldung:

find .|grep "FooBar"|xargs -I{} cp "{}" ~/foo/bar
xargs: unterminated quote

Irgendwelche Vorschläge für eine robustere Verwendung von xargs?

Dies ist unter Mac OS X 10.5.3 (Leopard) mit BSD xargs.

Drew Stephens
quelle
2
Die GNU xargs-Fehlermeldung mit einem Dateinamen, der ein einfaches Anführungszeichen enthält, ist eher hilfreich: "xargs: nicht übereinstimmendes einfaches Anführungszeichen; standardmäßig sind Anführungszeichen für xargs speziell, sofern Sie nicht die Option -0 verwenden".
Steve Jessop
3
GNU xargs hat auch --delimiteroption ( -d). Versuchen Sie es mit \nals Trennzeichen. Dies verhindert, dass xargsZeilen mit Leerzeichen in mehrere Wörter / Argumente getrennt werden.
MattBianco

Antworten:

199

Sie können all das in einem einzigen findBefehl kombinieren :

find . -iname "*foobar*" -exec cp -- "{}" ~/foo/bar \;

Dadurch werden Dateinamen und Verzeichnisse mit Leerzeichen behandelt. Sie können verwenden -name, um zwischen Groß- und Kleinschreibung unterscheidende Ergebnisse zu erhalten.

Hinweis: Das übergebene --Flag cpverhindert , dass Dateien verarbeitet werden, die -als Optionen beginnen.

Godbyk
quelle
70
Benutzer verwenden xargs, da es normalerweise schneller ist, eine ausführbare Datei fünfmal mit jeweils 200 Argumenten aufzurufen, als sie 1000 Mal mit jeweils einem Argument aufzurufen.
tzot
12
Die Antwort von Chris Jester-Young sollte dort die "gute Antwort" sein ... Übrigens funktioniert diese Lösung nicht, wenn ein Dateiname mit "-" beginnt. Zumindest braucht es "-" nach cp.
Keltia
11
Geschwindigkeitsbeispiel - Über 829 Dateien dauerte die Methode "find -exec" 26 Sekunden, während das Methodenwerkzeug "find -print0 | xargs --null" 0,7 Sekunden dauerte. Bedeutender Unterschied.
Peter Porter
7
@tzot Ein verspäteter Kommentar xargsist jedoch nicht erforderlich, um das von Ihnen beschriebene Problem zu beheben. Er findunterstützt ihn bereits mit der -exec +Interpunktion.
Jlliagre
3
beantwortet nicht die Frage, wie man mit Räumen
umgeht
117

find . -print0 | grep --null 'FooBar' | xargs -0 ...

Ich weiß nicht, ob Leopard grepunterstützt --nulloder xargsunterstützt -0, aber bei GNU ist alles gut.

Chris Jester-Young
quelle
1
Leopard unterstützt "-Z" (es ist GNU grep) und natürlich unterstützen find (1) und xargs (1) "-0".
Keltia
1
Unter OS X 10.9 grep -{z|Z}bedeutet "Verhalten als zgrep" (Dekomprimieren) und nicht das beabsichtigte "Drucken eines Null-Bytes nach jedem Dateinamen". Verwenden Sie grep --null, um Letzteres zu erreichen.
Bassim
4
Was ist los mit find . -name 'FooBar' -print0 | xargs -0 ...?
Quentin Pradet
1
@QuentinPradet Natürlich für eine feste Zeichenfolge wie "FooBar" -nameoder gut -pathfunktionieren. Das OP hat die Verwendung von angegeben grep, vermutlich weil sie die Liste mit regulären Ausdrücken filtern möchten.
Chris Jester-Young
1
@ Hi-Angel Genau deshalb benutze ich xargs -0 in Verbindung mit find -print0 . Letzterer druckt Dateinamen mit einem NUL-Terminator und Ersterer empfängt Dateien auf diese Weise. Warum? Dateinamen in Unix können Zeilenumbrüche enthalten. Sie dürfen jedoch keine NUL-Zeichen enthalten.
Chris Jester-Young
92

Der einfachste Weg, um das zu tun, was das Originalplakat will, besteht darin, das Trennzeichen von einem Leerzeichen in ein Zeilenendezeichen wie das folgende zu ändern:

find whatever ... | xargs -d "\n" cp -t /var/tmp
user87601
quelle
4
Diese Antwort ist einfach, effektiv und direkt auf den Punkt gebracht: Das für xargs festgelegte Standardtrennzeichen ist zu breit und muss für das, was OP tun möchte, eingegrenzt werden. Ich kenne das aus erster Hand, weil ich heute auf genau dasselbe Problem gestoßen bin und etwas Ähnliches getan habe, außer in Cygwin. Hätte ich die Hilfe für den Befehl xargs gelesen, hätte ich vielleicht ein paar Kopfschmerzen vermieden, aber Ihre Lösung hat sie für mich behoben. Vielen Dank ! (Ja, OP war unter MacOS mit BSD xargs, was ich nicht benutze, aber ich hoffe, dass der xargs "-d" -Parameter in allen Versionen vorhanden ist).
Etienne Delavennat
7
Gute Antwort, funktioniert aber nicht auf dem Mac. Stattdessen können wir Rohr den Fund in sed -e 's_\(.*\)_"\1"_g'an Kraft Anführungszeichen um den Dateinamen
ishahak
10
Dies sollte die akzeptierte Antwort sein. Die Frage war über die Verwendung xargs.
Mohammad Alhashash
2
Ich bekommexargs: illegal option -- d
nehem
1
Es sei darauf hingewiesen, dass Dateinamen auf vielen * nix-Systemen ein Zeilenumbruchzeichen enthalten können. Es ist unwahrscheinlich, dass Sie jemals in freier Wildbahn darauf stoßen, aber wenn Sie Shell-Befehle für nicht vertrauenswürdige Eingaben ausführen, kann dies ein Problem sein.
Soren Björnstad
71

Dies ist effizienter, da "cp" nicht mehrmals ausgeführt wird:

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar
Tometzky
quelle
1
Das hat bei mir nicht funktioniert. Es hat versucht, ~ / foo / bar in alles zu cp, was Sie finden, aber nicht das Gegenteil
Shervin Asgari
13
Das Flag -t für cp ist eine GNU-Erweiterung, AFAIK, und unter OS X nicht verfügbar. Wenn dies jedoch der Fall wäre, würde es wie in dieser Antwort gezeigt funktionieren.
Metamatt
2
Ich benutze Linux. Danke für den '-t'-Schalter. Das hat mir gefehlt :-)
Vahid Pazirandeh
59

Ich bin auf das gleiche Problem gestoßen. So habe ich es gelöst:

find . -name '*FoooBar*' | sed 's/.*/"&"/' | xargs cp ~/foo/bar

Früher habe ich sedjede Eingabezeile durch dieselbe Zeile ersetzt, aber von doppelten Anführungszeichen umgeben. Auf der sedManpage wird " ... Ein kaufmännisches Und (" & "), das in der Ersetzung erscheint, durch die Zeichenfolge ersetzt, die mit der RE übereinstimmt ... " - in diesem Fall .*die gesamte Zeile.

Dies löst den xargs: unterminated quoteFehler.

oyouareatubeo
quelle
3
Ich bin unter Windows und benutze gnuwin32, also musste ich es verwenden sed s/.*/\"&\"/, damit es funktioniert.
Pat
Ja, aber vermutlich würde dies keine Dateinamen mit "in behandeln - es sei denn, sed zitiert auch Anführungszeichen?
Artfulrobot
Verwenden sedist genial und vorerst die richtige Lösung, ohne das Problem neu zu schreiben!
Entonio
53

Diese Methode funktioniert unter Mac OS X 10.7.5 (Lion):

find . | grep FooBar | xargs -I{} cp {} ~/foo/bar

Ich habe auch die genaue Syntax getestet, die Sie veröffentlicht haben. Das hat auch am 10.7.5 gut funktioniert.

the_minted
quelle
4
Dies funktioniert, -Iimpliziert aber -L 1(so heißt es im Handbuch), was bedeutet, dass der Befehl cp einmal pro Datei = v langsam ausgeführt wird.
Artfulrobot
xargs -J% cp% <Zielverzeichnis> Ist unter OSX möglicherweise effizienter.
Walker D
3
Sorry, aber das ist FALSCH. Zuerst erzeugt es genau den Fehler, den der TO vermeiden wollte. Sie müssen xargs verwenden find ... -print0und xargs -0arbeiten, um "standardmäßig sind Anführungszeichen etwas Besonderes" zu verwenden. Zweitens: Verwenden Sie '{}'diese {}Option im Allgemeinen nicht in Befehlen, die an xargs übergeben werden, um sie vor Leerzeichen und Sonderzeichen zu schützen.
Andreas Spindler
3
Sorry Andreas Spindler, ich bin mit xargs nicht so vertraut und habe diese Zeile nach einigem Experimentieren gefunden. Es scheint für die meisten Leute zu funktionieren, die es kommentiert und positiv bewertet haben. Würde es Ihnen etwas ausmachen, etwas detaillierter darauf einzugehen, welche Art von Fehler es erzeugt? Würde es Ihnen auch etwas ausmachen, die genauen Eingaben zu veröffentlichen, die Sie für korrekter halten? Danke dir.
the_minted
12

Nur nicht benutzen xargs. Es ist ein ordentliches Programm, aber es passt nicht gut findzu nicht trivialen Fällen.

Hier ist eine tragbare (POSIX) Lösung, dh eine, die keine benötigt find, xargsoder cpGNU-spezifische Erweiterungen:

find . -name "*FooBar*" -exec sh -c 'cp -- "$@" ~/foo/bar' sh {} +

Beachten Sie das Ende +anstelle des üblicheren ;.

Diese Lösung:

  • Behandelt Dateien und Verzeichnisse mit eingebetteten Leerzeichen, Zeilenumbrüchen oder anderen exotischen Zeichen korrekt.

  • funktioniert auf jedem Unix- und Linux-System, auch auf solchen, die das GNU-Toolkit nicht bereitstellen.

  • verwendet kein xargsschönes und nützliches Programm, erfordert jedoch zu viele Optimierungen und nicht standardmäßige Funktionen, um die findAusgabe richtig zu handhaben .

  • ist auch effizienter ( schneller lesen ) als die akzeptierten und die meisten, wenn nicht alle anderen Antworten.

Beachten Sie auch, dass das Zitieren trotz der Angaben in einigen anderen Antworten oder Kommentaren {}nutzlos ist (es sei denn, Sie verwenden die exotische fishShell).

jlliagre
quelle
1
@PeterMortensen Sie übersehen wahrscheinlich das End-Plus. findkann tun, was xargsohne Overhead geht.
Jlliagre
8

Verwenden Sie die Befehlszeilenoption --null für xargs mit der Option -print0 in find.

Shannon Nelson
quelle
8

Für diejenigen, die sich auf andere Befehle als find verlassen, z ls.

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar
Aleksandr Guidrevitch
quelle
1
Funktioniert aber langsam, weil -Iimpliziert-L 1
Artfulrobot
6
find | perl -lne 'print quotemeta' | xargs ls -d

Ich glaube, dass dies für jeden Charakter außer Zeilenvorschub zuverlässig funktioniert (und ich vermute, dass Sie schlimmere Probleme haben, wenn Sie Zeilenvorschübe in Ihren Dateinamen haben). Es erfordert keine GNU-Findutils, nur Perl, also sollte es so ziemlich überall funktionieren.

Mavit
quelle
Ist es möglich, einen Zeilenvorschub in einem Dateinamen zu haben? Noch nie davon gehört.
MTK
2
Tatsächlich ist es. Versuchen Sie zBmkdir test && cd test && perl -e 'open $fh, ">", "this-file-contains-a-\n-here"' && ls | od -tx1
mavit
1
|perl -lne 'print quotemeta'ist genau das, wonach ich gesucht habe. Andere Beiträge hier haben mir nicht geholfen, da findich grep -rldie Anzahl der PHP-Dateien nicht nur auf mit Malware infizierte Dateien reduzieren musste, sondern auch.
Marcos
Perl und Quotemeta sind weitaus allgemeiner als print0 / -0 - danke für die allgemeine Lösung für das Pipelining von Dateien mit Leerzeichen
bmike
5

Ich habe festgestellt, dass die folgende Syntax für mich gut funktioniert.

find /usr/pcapps/ -mount -type f -size +1000000c | perl -lpe ' s{ }{\\ }g ' | xargs ls -l | sort +4nr | head -200

In diesem Beispiel suche ich nach den 200 größten Dateien über 1.000.000 Bytes im Dateisystem, das unter "/ usr / pcapps" bereitgestellt wird.

Der Perl-Zeilenumbruch zwischen "find" und "xargs" maskiert / zitiert jedes Leerzeichen, sodass "xargs" einen beliebigen Dateinamen mit eingebetteten Leerzeichen als einzelnes Argument an "ls" übergibt.

Peter Mortensen
quelle
3

Frame Challenge - Sie fragen, wie Sie Xargs verwenden. Die Antwort lautet: Sie verwenden keine Xargs, weil Sie sie nicht benötigen.

Der Kommentar vonuser80168 beschreibt eine Möglichkeit, dies direkt mit cp zu tun, ohne cp für jede Datei aufzurufen:

find . -name '*FooBar*' -exec cp -t /tmp -- {} +

Dies funktioniert, weil:

  • Mit dem cp -tFlag kann das Zielverzeichnis cpeher am Anfang als am Ende angegeben werden. Von man cp:
   -t, --target-directory=DIRECTORY
         copy all SOURCE arguments into DIRECTORY
  • Das --Flag weist darauf hin cp, dass alles danach als Dateiname und nicht als Flag interpretiert werden soll, sodass Dateien mit beginnen -oder --nicht verwechseln cp. Sie benötigen dies weiterhin, da die -/ ---Zeichen von interpretiert werden cp, während alle anderen Sonderzeichen von der Shell interpretiert werden.

  • Die find -exec command {} +Variante macht im Wesentlichen dasselbe wie xargs. Von man find:

   -exec command {} +                                                     
         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‐
         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, and (when find is being invoked
         from a shell) it should be quoted (for example, '{}') to protect
         it  from  interpretation  by shells.  The command is executed in
         the starting directory.  If any invocation  returns  a  non-zero
         value  as exit status, then find returns a non-zero exit status.
         If find encounters an error, this can sometimes cause an immedi‐
         ate  exit, so some pending commands may not be run at all.  This
         variant of -exec always returns true.

Wenn Sie dies direkt in find verwenden, wird die Notwendigkeit eines Pipe- oder Shell-Aufrufs vermieden, sodass Sie sich keine Gedanken über böse Zeichen in Dateinamen machen müssen.

gerrit
quelle
Erstaunlicher Fund, ich hatte keine Ahnung !!! "-exec Dienstprogramm [Argument ...] {} + Wie -exec, außer dass" {} "für jeden Aufruf des Dienstprogramms durch so viele Pfadnamen wie möglich ersetzt wird. Dieses Verhalten ähnelt dem von xargs (1) ). " in der BSD-Implementierung.
Conny
2

Beachten Sie, dass die meisten der in anderen Antworten beschriebenen Optionen auf Plattformen, auf denen die GNU-Dienstprogramme nicht verwendet werden (z. B. Solaris, AIX, HP-UX), nicht Standard sind. Informationen zum Standardverhalten von xargs finden Sie in der POSIX- Spezifikation.

Ich finde auch das Verhalten von xargs, bei dem der Befehl mindestens einmal ausgeführt wird, auch ohne Eingabe, ein Ärgernis.

Ich habe meine eigene private Version von xargs (xargl) geschrieben, um die Probleme von Leerzeichen in Namen zu lösen (nur Zeilenumbrüche sind getrennt - obwohl die Kombination 'find ... -print0' und 'xargs -0' ziemlich ordentlich ist, da Dateinamen dies nicht können enthalten ASCII NUL '\ 0' Zeichen. Mein xargl ist nicht so vollständig, wie es sein müsste, um veröffentlicht zu werden - zumal GNU über mindestens ebenso gute Funktionen verfügt.

Jonathan Leffler
quelle
2
GitHub oder es ist nicht passiert
Corey Goldberg
@CoreyGoldberg: Ich denke, das ist damals nicht passiert.
Jonathan Leffler
POSIX findbraucht das überhaupt nicht xargs(und das war schon vor 11 Jahren so).
Jlliagre
2

Mit Bash (nicht POSIX) können Sie die Prozessersetzung verwenden, um die aktuelle Zeile innerhalb einer Variablen abzurufen. Auf diese Weise können Sie Anführungszeichen verwenden, um Sonderzeichen zu umgehen:

while read line ; do cp "$line" ~/bar ; done < <(find . | grep foo)
StackedCrooked
quelle
2

Für mich habe ich versucht, etwas anderes zu machen. Ich wollte meine TXT-Dateien in meinen tmp-Ordner kopieren. Die .txt-Dateinamen enthalten Leerzeichen und Apostrophzeichen. Dies funktionierte auf meinem Mac.

$ find . -type f -name '*.txt' | sed 's/'"'"'/\'"'"'/g' | sed 's/.*/"&"/'  | xargs -I{} cp -v {} ./tmp/
Moises
quelle
1

Wenn find- und xarg-Versionen auf Ihrem System nicht unterstützt -print0und -0wechselt (z. B. AIX find und xargs), können Sie diesen schrecklich aussehenden Code verwenden:

 find . -name "*foo*" | sed -e "s/'/\\\'/g" -e 's/"/\\"/g' -e 's/ /\\ /g' | xargs cp /your/dest

Hier kümmert sich sed darum, den Leerzeichen und Anführungszeichen für xargs zu entkommen.

Getestet unter AIX 5.3

Jan Ptáčník
quelle
1

Ich habe ein kleines tragbares Wrapper-Skript namens "xargsL" um "xargs" erstellt, das die meisten Probleme behebt.

Im Gegensatz zu xargs akzeptiert xargsL einen Pfadnamen pro Zeile. Die Pfadnamen können beliebige Zeichen außer (offensichtlich) Zeilenumbrüchen oder NUL-Bytes enthalten.

In der Dateiliste ist kein Anführungszeichen zulässig oder wird nicht unterstützt. Ihre Dateinamen können alle Arten von Leerzeichen, Backslashes, Backticks, Shell-Platzhalterzeichen und dergleichen enthalten. XargsL verarbeitet sie als Literalzeichen, ohne dass Schaden entsteht.

Als zusätzlichen Bonus - Feature wird xargsL nicht den Befehl ausführen einmal , wenn es keine Eingabe!

Beachten Sie den Unterschied:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

Alle Argumente, die xargsL gegeben werden, werden an xargs weitergeleitet.

Hier ist das POSIX-Shell-Skript "xargsL":

#! /bin/sh
# Line-based version of "xargs" (one pathname per line which may contain any
# amount of whitespace except for newlines) with the added bonus feature that
# it will not execute the command if the input file is empty.
#
# Version 2018.76.3
#
# Copyright (c) 2018 Guenther Brunthaler. All rights reserved.
#
# This script is free software.
# Distribution is permitted under the terms of the GPLv3.

set -e
trap 'test $? = 0 || echo "$0 failed!" >& 2' 0

if IFS= read -r first
then
        {
                printf '%s\n' "$first"
                cat
        } | sed 's/./\\&/g' | xargs ${1+"$@"}
fi

Legen Sie das Skript in einem Verzeichnis in Ihrem $ PATH ab und vergessen Sie es nicht

$ chmod +x xargsL

das Skript dort, um es ausführbar zu machen.

Günther Brunthaler
quelle
1

Die Perl-Version von bill_starr funktioniert nicht gut für eingebettete Zeilenumbrüche (nur für Leerzeichen). Für diejenigen unter zB Solaris, bei denen Sie nicht über die GNU-Tools verfügen, ist möglicherweise eine vollständigere Version (mit sed) ...

find -type f | sed 's/./\\&/g' | xargs grep string_to_find

Passen Sie die Argumente find und grep oder andere Befehle nach Bedarf an, aber das sed korrigiert Ihre eingebetteten Zeilenumbrüche / Leerzeichen / Tabulatoren.

Peter Mortensen
quelle
1

Ich habe die Antwort von Bill Star verwendet, die unter Solaris leicht modifiziert wurde:

find . -mtime +2 | perl -pe 's{^}{\"};s{$}{\"}' > ~/output.file

Dadurch werden Anführungszeichen um jede Zeile gesetzt. Ich habe die Option '-l' nicht verwendet, obwohl sie wahrscheinlich helfen würde.

Die Dateiliste, in die ich gegangen bin, hat möglicherweise '-', aber keine Zeilenumbrüche. Ich habe die Ausgabedatei nicht mit anderen Befehlen verwendet, da ich überprüfen möchte, was gefunden wurde, bevor ich anfange, sie über xargs massiv zu löschen.

Carl Yamamoto-Fürst
quelle
1

Ich habe ein wenig damit gespielt, angefangen, über das Ändern von xargs nachzudenken, und festgestellt, dass für den Anwendungsfall, über den wir hier sprechen, eine einfache Neuimplementierung in Python eine bessere Idee ist.

Zum einen bedeutet ~ 80 Codezeilen für das Ganze, dass es einfach ist, herauszufinden, was vor sich geht, und wenn ein anderes Verhalten erforderlich ist, können Sie es einfach in kürzerer Zeit als nötig in ein neues Skript hacken eine Antwort auf irgendwo wie Stack Overflow.

Siehe https://github.com/johnallsup/jda-misc-scripts/blob/master/yargs und https://github.com/johnallsup/jda-misc-scripts/blob/master/zargs.py .

Wenn yargs wie geschrieben (und Python 3 installiert) ist, können Sie Folgendes eingeben:

find .|grep "FooBar"|yargs -l 203 cp --after ~/foo/bar

um jeweils 203 Dateien zu kopieren. (Hier ist 203 natürlich nur ein Platzhalter, und die Verwendung einer seltsamen Zahl wie 203 macht deutlich, dass diese Zahl keine andere Bedeutung hat.)

Wenn Sie wirklich etwas schneller und ohne Python benötigen, nehmen Sie Zargs und Yargs als Prototypen und schreiben Sie sie in C ++ oder C neu.

John Allsup
quelle
0

Möglicherweise müssen Sie das Foobar-Verzeichnis wie folgt durchsuchen:

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .
Fred
quelle
1
Laut Manpage -iist es veraltet und -Isollte stattdessen verwendet werden.
Acumenus
-1

Wenn Sie Bash verwenden, können Sie stdout in ein Array von Zeilen konvertieren , indem Sie mapfile:

find . | grep "FooBar" | (mapfile -t; cp "${MAPFILE[@]}" ~/foobar)

Die Vorteile sind:

  • Es ist eingebaut, also schneller.
  • Führen Sie den Befehl mit allen Dateinamen gleichzeitig aus, damit er schneller ist.
  • Sie können andere Argumente an die Dateinamen anhängen. Denn cpSie können auch:

    find . -name '*FooBar*' -exec cp -t ~/foobar -- {} +
    

    Einige Befehle verfügen jedoch nicht über eine solche Funktion.

Die Nachteile:

  • Möglicherweise nicht gut skalierbar, wenn zu viele Dateinamen vorhanden sind. (Das Limit? Ich weiß es nicht, aber ich hatte unter Debian problemlos mit einer 10-MB-Listendatei getestet, die über 10000 Dateinamen enthält.)

Nun ... wer weiß, ob Bash unter OS X verfügbar ist?

Xiè Jìléi
quelle