risposta-alla-domanda-sullo-sviluppo-web-bd.com

Come posso usare xargs per copiare file che hanno spazi e virgolette nei loro nomi?

Sto cercando di copiare un mucchio di file sotto una directory e un numero di file ha spazi e virgolette singole nei loro nomi. Quando provo a mettere insieme find e grep con xargs, ottengo il seguente errore:

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

Qualche suggerimento per un uso più efficace di xargs?

Questo è su Mac OS X 10.5. (Leopard) con BSD xargs.

216
Drew Stephens

Puoi combinare tutto ciò in un singolo comando find:

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

Questo gestirà nomi di file e directory con spazi all'interno. Puoi utilizzare -name per ottenere risultati sensibili al maiuscolo/minuscolo.

Nota: il flag -- passato a cp gli impedisce di elaborare i file che iniziano con - come opzioni.

191
godbyk

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

Non so se grep supporta --null, né se xargs supporta -0, su Leopard, ma su GNU va tutto bene.

114

Il modo più semplice per fare ciò che vuole il poster originale è cambiare il delimitatore da qualsiasi spazio bianco al carattere di fine riga come questo:

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

Questo è più efficiente in quanto non esegue "cp" più volte:

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

Ho riscontrato lo stesso problema. Ecco come l'ho risolto:

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

Ho usato sed per sostituire ogni riga di input con la stessa riga, ma racchiusa tra virgolette doppie. Dalla pagina man sed, "... Una e commerciale (` `& '') che appare nella sostituzione è sostituita dalla stringa corrispondente a RE ..." - in questo caso, .*, l'intera linea.

Questo risolve l'errore xargs: unterminated quote.

55
oyouareatubeo

Questo metodo funziona su Mac OS X v10.7.5 (Lion):

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

Ho anche testato la sintassi esatta che hai pubblicato. Funzionava bene anche il 10.7.5.

50
the_minted

Basta non usare xargs. È un programma accurato ma non va bene con find di fronte a casi non banali.

Ecco una soluzione portatile (POSIX), ovvero una che non richiede find, xargs o cp _ [GNU estensioni specifiche:

find . -name "*FooBar*" -exec sh -c 'cp -- "[email protected]" ~/foo/bar' sh {} +

Nota la fine + anziché la più consueta ;.

Questa soluzione:

  • gestisce correttamente file e directory con spazi incorporati, newline o qualunque carattere esotico.

  • funziona su qualsiasi sistema Unix e Linux, anche quelli che non forniscono il toolkit GNU.

  • non usa xargs che è un programma utile e utile, ma richiede troppe modifiche e funzionalità non standard per gestire correttamente l'output di find.

  • è anche più efficiente (leggi più veloce) rispetto alle risposte accettate e la maggior parte se non tutte le altre.

Nota anche che, nonostante quanto affermato in alcune altre risposte o commenti, la citazione di {} è inutile (a meno che tu non stia usando l'esotico fishShell).

11
jlliagre

Cerca di usare l'opzione --null della riga di comando per xargs con l'opzione -print0 in find.

8
Shannon Nelson

Per coloro che si basano su comandi, oltre a trovare, ad esempio ls:

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar
8

Ho scoperto che la sintassi seguente funziona bene per me.

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

In questo esempio, sto cercando i 200 file più grandi oltre 1.000.000 di byte nel filesystem montato su "/ usr/pcapps".

La linea di linea Perl tra "trova" e "xargs" sfugge/cita ogni spazio vuoto in modo che "xargs" passi qualsiasi nome di file con spazi vuoti incorporati a "ls" come singolo argomento.

5
bill_starr
find | Perl -lne 'print quotemeta' | xargs ls -d

Credo che questo funzionerà in modo affidabile per qualsiasi personaggio tranne il feed di linea (e sospetto che se hai feed di riga nei tuoi nomi di file, allora hai problemi peggiori di così). Non richiede GNU findutils, solo Perl, quindi dovrebbe funzionare praticamente ovunque.

5
mavit

Per me, stavo cercando di fare qualcosa di leggermente diverso. Volevo copiare i miei file .txt nella mia cartella tmp. I nomi di file .txt contengono spazi e caratteri apostrofo. Questo ha funzionato sul mio Mac.

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

Con Bash (non POSIX) è possibile utilizzare la sostituzione del processo per ottenere la riga corrente all'interno di una variabile. Ciò ti consente di utilizzare le virgolette per sfuggire ai caratteri speciali:

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

Tenere presente che la maggior parte delle opzioni discusse in altre risposte non sono standard su piattaforme che non utilizzano le utility GNU (Solaris, AIX, HP-UX, ad esempio). Vedi le specifiche POSIX per il comportamento degli xarg 'standard'.

Trovo anche il comportamento di xargs in base al quale viene eseguito il comando almeno una volta, anche senza input, per essere un fastidio.

Ho scritto la mia versione privata di xargs (xargl) per affrontare i problemi degli spazi nei nomi (solo le nuove righe separate - sebbene la combinazione 'find ... -print0' e 'xargs -0' sia piuttosto ordinata dato che i nomi dei file non possono contiene ASCII NUL '\ 0' caratteri. Il mio xargl non è completo come dovrebbe essere degno di essere pubblicato - soprattutto perché GNU ha strutture che sono almeno altrettanto buone .

2

Ho creato un piccolo script wrapper portatile chiamato "xargsL" attorno a "xargs" che risolve la maggior parte dei problemi.

Contrariamente a xargs, xargsL accetta un percorso per riga. I nomi dei percorsi possono contenere qualsiasi carattere tranne (ovviamente) newline o byte NUL.

Nessun elenco è consentito o supportato nell'elenco dei file - i nomi dei file possono contenere qualsiasi tipo di spazio bianco, barre rovesciate, backtick, caratteri jolly Shell e simili - xargsL li elaborerà come caratteri letterali, senza alcun danno.

Come funzione bonus aggiunta, xargsL eseguirà no il comando una volta se non ci sono input!

Nota la differenza:

$ true | xargs echo no data
no data

$ true | xargsL echo no data # No output

Qualsiasi argomento fornito a xargsL verrà passato a xargs.

Ecco lo script shell POSIX "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+"[email protected]"}
fi

Inserisci lo script in una directory nel tuo $ PATH e non dimenticare di farlo

$ chmod +x xargsL

lo script lì per renderlo eseguibile.

1

Ho usato la risposta di Bill Star leggermente modificato su Solaris:

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

Questo metterà le virgolette attorno ad ogni riga. Non ho usato l'opzione '-l' anche se probabilmente sarebbe di aiuto.

L'elenco dei file che stavo andando potrebbe avere '-', ma non newline. Non ho usato il file di output con nessun altro comando in quanto voglio rivedere ciò che è stato trovato prima di iniziare a cancellarli in modo massiccio tramite xargs.

1

Ho giocato un po 'con questo, ho iniziato a contemplare la modifica di xargs e ho capito che per il tipo di caso d'uso di cui stiamo parlando qui, una semplice reimplementazione in Python è un'idea migliore.

Per prima cosa, avere ~ 80 righe di codice per tutto ciò significa che è facile capire cosa sta succedendo, e se è richiesto un comportamento diverso, puoi semplicemente hackerarlo in un nuovo script in meno tempo di quello che serve per ottenere una risposta da qualche parte come Stack Overflow.

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

Con yargs come scritto (e Python 3 installato) puoi digitare:

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

per eseguire la copia di 203 file alla volta. (Qui 203 è solo un segnaposto, ovviamente, e l'utilizzo di uno strano numero come 203 chiarisce che questo numero non ha altro significato.)

Se vuoi davvero qualcosa di più veloce e senza la necessità di Python, prendi zarg e yarg come prototipi e riscrivi in ​​C++ o C.

1
John Allsup

versione Perl di bill_starr non funzionerà bene per le newline incorporate (gestisce solo gli spazi). Per quelli su ad es. Solaris dove non hai gli strumenti GNU, una versione più completa potrebbe essere (usando sed) ...

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

regola gli argomenti find e grep o altri comandi di cui hai bisogno, ma sed risolverà le tue nuove righe/spazi/schede incorporate.

1
rjb1

Se le versioni find e xarg sul tuo sistema non supportano gli switch -print0 e -0 (ad esempio AIX find e xargs) puoi usare questo codice dall'aspetto terribile:

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

Qui sed si occuperà di sfuggire agli spazi e alle virgolette per xargs.

Testato su AIX 5.3

1
Jan Ptáčník

Potrebbe essere necessario grep nella directory Foobar come:

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