Wie kann ich mit xargs Dateien kopieren, die Leerzeichen und Anführungszeichen in ihrem Namen hat?

StackOverflow https://stackoverflow.com/questions/143171

Frage

Ich versuche, eine Reihe von Dateien unter einem Verzeichnis und eine Anzahl der Dateien zu kopieren Leerzeichen und einfache Anführungszeichen in ihrem Namen. Wenn ich zu bespannen versuche zusammen find und grep mit xargs, erhalte ich folgende Fehlermeldung:

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

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

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

War es hilfreich?

Lösung

Sie können all das in einem einzigen Befehl kombiniert find:

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

Dies wird Dateinamen und Verzeichnisse mit Leerzeichen in ihnen umgehen. Sie können -name verwenden, um case-sensitive Ergebnisse zu erzielen.

. Hinweis: Der -- Flag cp weitergegeben verhindert, dass Dateien verarbeitet mit - als Optionen Start

Andere Tipps

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

Ich weiß nicht, ob grep --null unterstützt, noch ob xargs unterstützt -0, auf Leopard, aber auf GNU es ist alles gut.

Der einfachste Weg zu tun, was das ursprüngliche Plakat will, ist das Trennzeichen von einem beliebigen Leerzeichen zu ändern, um nur die End-of-Line-Zeichen wie folgt aus:

find whatever ... | xargs -d "\n" cp -t /var/tmp

Das ist effizienter, da es nicht „cp“ mehrere Male läuft:

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar

Ich lief in das gleiche Problem. Hier ist, wie ich es gelöst:

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

Ich benutzen sed jede Zeile der Eingabe mit der gleichen Linie zu ersetzen, aber durch doppelte Anführungszeichen umgeben. Von der sed Manpage " ... Ein Zeichen (` `& '') im Ersatz erscheint durch die Zeichenfolge ersetzt Anpassung des RE ... " - in diesem Fall .*, die gesamte Zeile.

Das löst den xargs: unterminated quote Fehler.

Diese Methode funktioniert auf Mac OS X v10.7.5 (Lion):

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

Ich habe auch getestet, die genaue Syntax Sie auf dem Laufenden. Das funktionierte auch gut auf 10.7.5.

Nur nicht xargs verwenden. Es ist ein nettes kleines Programm, aber es nicht gut mit find geht, wenn sie mit nicht trivialen Fällen konfrontiert.

Hier ist eine tragbare (POSIX) Lösung, das heißt eine, die nicht find, xargs oder cp GNU spezifische Erweiterungen benötigt:

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

Beachten Sie die Endung + anstelle der üblicheren ;.

Diese Lösung:

  • korrekt behandelt Dateien und Verzeichnisse mit eingebetteten Leerzeichen, Zeilenumbrüchen oder was auch immer exotischen Zeichen.

  • funktioniert auf jedem Unix und Linux-System, auch diejenigen, die nicht die GNU-Toolkit bereitgestellt wird.

  • nicht xargs verwendet werden, die ein schönes und nützliches Programm ist, erfordern aber zu viel Zwicken und Nicht-Standard-Funktionen richtig find Ausgabe zu behandeln.

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

Beachten Sie auch, dass trotz allem, was in einigen anderen Antworten oder Kommentare angegeben zitiert {} nutzlos ist (es sei denn Sie die exotische fishshell verwenden).

Schauen Sie in die --null für Kommandozeilenoption xargs mit der -print0 Option in FIND.

Für diejenigen, die auf Befehle setzen, anders als finden, zB ls:

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar
find | perl -lne 'print quotemeta' | xargs ls -d

Ich glaube, dass dies für eine beliebiges Zeichen zuverlässig funktionieren wird außer Zeilenvorschub (und ich vermute, dass, wenn Sie Line-Feeds in Ihrem Dateinamen haben, dann haben Sie schlimmere Probleme bekommen als diese). Dabei spielt es keine GNU findutils erfordern, nur Perl, so sollte es überall ziemlich viel arbeiten.

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

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

In diesem Beispiel, ich suche für die größten 200 Dateien über 1.000.000 Bytes im Dateisystem unter "/ usr / pcapps" montiert ist.

Der Perl-Line-Liner zwischen „finden“ und „xargs“ entkommt / zitiert jeweils leer, so „xargs“ geht ein beliebigen Dateinamen mit eingebetteten Leerzeichen zu „ls“ als ein einziges Argument.

Beachten Sie, dass die meisten der in anderen Antworten diskutierten Optionen sind nicht Standard auf Plattformen, die das GNU-Dienstprogramme nicht verwenden (Solaris, AIX, HP-UX, zum Beispiel). Finden Sie in der POSIX Spezifikation für 'Standard' xargs Verhalten.

Ich habe auch das Verhalten von xargs finden, wobei er den Befehl mindestens einmal ausgeführt wird, auch ohne Eingabe, ein Ärgernis zu sein.

Ich schrieb meine eigene private Version von xargs (xargl) mit den Problemen der Leerzeichen in Namen zu behandeln (nur Zeilenumbrüche getrennt - obwohl die ‚finden ... -print0‘ und ‚xargs -0‘ Kombination recht ordentlich ist da Dateinamen können nicht ASCII NUL ‚\ 0‘ Zeichen enthalten My xargl nicht so vollständig ist, wie es sein müsste wert Publishing sein -.. zumal GNU verfügt über Einrichtungen, die mindestens so gut sind,

Mit Bash (nicht POSIX) können Sie die Prozess Substitution verwenden, um die aktuelle Zeile innerhalb einer Variablen zu erhalten. Dadurch können Sie Anführungszeichen verwenden Sonderzeichen zu entkommen:

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

Für mich, ich habe versucht etwas anderes zu tun. Ich wollte meine TXT-Dateien in meine tmp-Ordner kopieren. Die TXT-Dateinamen Leerzeichen enthalten und Apostroph Zeichen. Das funktionierte auf meinem Mac.

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

Wenn finden und xarg Versionen auf Ihrem System nicht -print0 und -0 Schalter (zum Beispiel AIX finden und xargs) können Sie mit diesem schrecklich sucht Code unterstützen:

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

Hier kümmert sed die Räume und Angebote für xargs zu entkommen.

Getestet auf AIX 5.3

Ich habe einen kleinen tragbaren Wrapper-Skript namens „xargsL“ um „xargs“, die die meisten Probleme anspricht.

Im Gegensatz zu xargs akzeptiert xargsL einen Pfad pro Zeile. Die Pfadnamen ein beliebiges Zeichen enthalten außer (natürlich) Newline oder NUL Bytes.

Angabe nicht erlaubt ist oder in der Dateiliste unterstützt - Ihre Dateinamen alle Arten von Leerzeichen, Schrägstriche, Backticks, Shell-Wildcard-Zeichen und dergleichen enthalten können -. XargsL werden sie als normale Zeichen verarbeiten, kein Schaden getan

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

Beachten Sie den Unterschied:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

Jede zu xargsL gegebenen Argumente werden durch zu xargs übergeben werden.

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

#! /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

Setzen Sie das Skript in ein Verzeichnis in Ihrem $ PATH und vergessen Sie nicht,

$ chmod +x xargsL

das Skript gibt es ausführbar zu machen.

bill_starr Perl Version für eingebettete Zeilenumbrüche nicht gut funktionieren (meistert nur mit Leerzeichen). Für die auf z.B. Solaris, wo Sie nicht die GNU-Tools haben, eine vollständigere Version könnte sein (mit sed) ...

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

stellen Sie die Entdeckung und grep Argumente oder andere Befehle, wie Sie benötigen, aber die sed Ihre eingebetteten Zeilenumbrüche / Leerzeichen / Tabs beheben.

I verwendet Antwort Bill Star leicht auf Solaris geändert:

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

Dies wird um jede Zeile Anführungszeichen gesetzt. Ich habe nicht verwenden, um die ‚-l‘ Option, obwohl es wahrscheinlich helfen würde.

Die Dateiliste dachte ich würde wahrscheinlich ‚-‘, aber nicht Zeilenumbrüche. Ich habe nicht die Ausgabedatei mit anderen Befehlen verwendet, wie ich überprüfen möchten, was gefunden wurde, bevor ich beginne gerade massiv sie über xargs löschen.

spielte ich mit diesem ein wenig, begann zu modifizieren xargs Betrachtung, und erkannte, dass für die Art der Verwendung Fall, dass wir hier reden, ein einfaches Neuimplementierung in Python ist eine bessere Idee.

Für eine Sache, mit ~ 80 Zeilen Code für das Ganze bedeutet es einfach ist, um herauszufinden, was los ist, und wenn ein anderes Verhalten erforderlich ist, können Sie es einfach hacken in ein neues Skript in weniger Zeit, als es nimmt, um eine Antwort auf irgendwo wie Stack-Überlauf zu kommen.

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

Mit yargs wie geschrieben (und Python 3 installiert) Sie können eingeben:

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

das Kopieren 203 Dateien auf einmal zu tun. (Hier 203 ist nur ein Platzhalter, natürlich, und mit einer seltsamen Zahl wie 203 macht deutlich, dass diese Zahl keine andere Bedeutung hat.)

Wenn Sie wirklich wollen etwas schneller und ohne die Notwendigkeit für Python, nehmen zargs und yargs als Prototypen und schreiben in C ++ oder C.

Sie müssen möglicherweise Foobar Verzeichnis grep wie:

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .

Rahmen Herausforderung - Sie fragen, wie xargs zu verwenden. Die Antwort ist: Sie müssen nicht xargs verwenden, weil Sie es nicht brauchen

.

Die Kommentar von user80168 beschreibt eine Möglichkeit, dies mit cp direkt zu tun, ohne cp für jede Datei aufrufen:

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

Das funktioniert, weil:

  • der cp -t Flag erlaubt das Zielverzeichnis in der Nähe von Anfang cp zu geben, anstatt am Ende. Von man cp:
   -t, --target-directory=DIRECTORY
         copy all SOURCE arguments into DIRECTORY
  • Die -- Flag teilt cp alles nach als Dateinamen zu interpretieren, nicht ein Flag, so Dateien mit - oder -- Start cp nicht zu verwechseln; Sie brauchen noch dies, weil die - / -- Zeichen von cp interpretiert werden, während andere Sonderzeichen von der Shell interpretiert werden.

  • Die find -exec command {} + Variante hat im Wesentlichen die gleiche 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.

Dieses in Durch die Verwendung direkt finden, dies vermeidet die Notwendigkeit eines Rohres oder ein Shell-Aufruf, so dass Sie brauchen sich keine Sorgen über keine bösen Zeichen in Dateinamen.

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

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

Die Vorteile sind:

  • Es ist eingebaut, so dass es schneller.
  • Führen Sie den Befehl mit allen Dateinamen in einer Zeit, so dass es schneller.
  • Sie können auch andere Argumente an den Dateinamen anhängen. Für cp, können Sie auch:

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

    jedoch nicht einig Befehle solche Funktion haben.

Die Nachteile:

  • Vielleicht nicht gut skalieren, wenn es zu viele Dateinamen sind. (Die Grenze? Ich weiß es nicht, aber ich hatte mit 10 MB Listendatei getestet, die 10000 + Dateinamen ohne Probleme enthält, unter Debian)

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

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top