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


232

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 stringa insieme finde grepcon 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.3 (Leopard) con BSD xargs.


2
Il messaggio di errore GNU xargs per questo con un nome file contenente una virgoletta singola è piuttosto più utile: "xargs: virgoletta singola senza eguali; per impostazione predefinita le virgolette sono speciali per xargs a meno che non si usi l'opzione -0".
Steve Jessop,

3
GNU xargs ha anche l' --delimiteropzione ( -d). Provalo \ncome delimitatore, Ciò impedisce xargsdi separare le linee con spazi in più parole / argomenti.
MattBianco,

Risposte:


199

Puoi combinare tutto ciò in un singolo findcomando:

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

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

Nota: il --flag passato per cpimpedirgli di elaborare i file a partire da -come opzioni.


70
Le persone usano xargs perché in genere è più veloce chiamare un eseguibile 5 volte con 200 argomenti ogni volta che chiamarlo 1000 volte con un argomento ogni volta.
Tzot

12
La risposta di Chris Jester-Young dovrebbe essere la "buona risposta" lì ... A proposito, questa soluzione non funziona se un nome file inizia con "-". Almeno, ha bisogno di "-" dopo cp.
Keltia,

11
Esempio di velocità: oltre 829 file, il metodo "find -exec" ha impiegato 26 secondi mentre lo strumento del metodo "find -print0 | xargs --null" 0,7 secondi. Differenza significativa.
Peter Porter

7
@tzot Un commento in ritardo ma comunque, xargsnon è necessario per affrontare il problema che stai descrivendo, findlo supporta già con la -exec +punteggiatura.
jlliagre,

3
non risponde alla domanda su come gestire gli spazi
Ben Glasser,

117

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

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


1
Leopard supporta "-Z" (è GNU grep) e ovviamente find (1) e xargs (1) supportano "-0".
Keltia,

1
Su OS X 10.9 grep -{z|Z}significa "comportarsi come zgrep" (decomprimere) e non l'intenzione "stampare un byte zero dopo ogni nome di file". Utilizzare grep --nullper raggiungere quest'ultimo.
bassim

4
Cosa c'è che non va find . -name 'FooBar' -print0 | xargs -0 ...?
Quentin Pradet,

1
@QuentinPradet Ovviamente, per una stringa fissa come "FooBar", -nameo -pathfunziona bene. L'OP ha specificato l'uso di grep, presumibilmente perché vogliono filtrare l'elenco usando espressioni regolari.
Chris Jester-Young,

1
@ Hi-Angel Questo è esattamente il motivo per cui lo uso xargs -0 insieme find -print0 . Quest'ultimo stampa i nomi dei file con un terminatore NUL e il primo riceve i file in questo modo. Perché? I nomi di file in Unix possono contenere caratteri di nuova riga. Ma non possono contenere caratteri NUL.
Chris Jester-Young,

92

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

4
Questa risposta è semplice, efficace e diretta al punto: il delimitatore predefinito impostato per xargs è troppo ampio e deve essere ristretto per ciò che OP vuole fare. Lo so di persona perché oggi ho riscontrato esattamente lo stesso problema facendo qualcosa di simile, tranne in Cygwin. Se avessi letto l'aiuto per il comando xargs, avrei potuto evitare alcuni mal di testa, ma la tua soluzione l'ha risolto per me. Grazie ! (Sì, OP era su MacOS usando xargs BSD, che non uso, ma spero che il parametro "-d" di xargs esista in tutte le versioni).
Etienne Delavennat,

7
Bella risposta ma non funziona su Mac. Invece abbiamo possibile reindirizzare il ritrovamento in sed -e 's_\(.*\)_"\1"_g'per forzare le virgolette intorno al nome del file
ishahak

10
Questa dovrebbe essere la risposta accettata. La domanda era sull'uso xargs.
Mohammad Alhashash,

2
Ricevoxargs: illegal option -- d
nehem l'

1
Vale la pena sottolineare che i nomi dei file possono contenere un carattere di nuova riga su molti sistemi * nix. È improbabile che ti imbatterai mai in questo in natura, ma se stai eseguendo comandi shell su input non attendibili questo potrebbe essere un problema.
Soren Bjornstad,

71

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

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

1
Questo non ha funzionato per me. Ha cercato di cp ~ / foo / bar in qualunque cosa tu trovi, ma non il contrario
Shervin Asgari,

13
Il flag -t su cp è un'estensione GNU, AFAIK, e non è disponibile su OS X. Ma se lo fosse, funzionerebbe come mostrato in questa risposta.
metamatt,

2
Sto usando Linux. Grazie per l'interruttore '-t'. Questo è quello che mi mancava :-)
Vahid Pazirandeh

59

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

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

Ho usato sedper sostituire ciascuna linea di ingresso con la stessa linea, ma circondato da virgolette. Dalla sedpagina man, " ... Una e commerciale (` `& '') che appare nella sostituzione è sostituita dalla stringa corrispondente a RE ... " - in questo caso,.* , l'intera riga.

Questo risolve l' xargs: unterminated quoteerrore.


3
Sono su Windows e utilizzo gnuwin32, quindi ho dovuto usarlo sed s/.*/\"&\"/per farlo funzionare.
Pat

Sì, ma presumibilmente questo non gestirà i nomi di file con "in - a meno che sed non citi anche le virgolette?
artfulrobot,

L'utilizzo sedè geniale e per ora la soluzione corretta senza riscrivere il problema!
entonio,

53

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.


4
Funziona, ma -Iimplica -L 1(così dice il manuale), il che significa che il comando cp viene eseguito una volta per file = v lento.
artfulrobot,

xargs -J% cp% <dir destinazione> È probabilmente più efficiente su OSX.
Walker D,

3
Siamo spiacenti, ma questo è SBAGLIATO. Innanzitutto produce esattamente l'errore che il TO voleva evitare. È necessario utilizzare find ... -print0e xargs -0lavorare intorno a xargs "per impostazione predefinita le virgolette sono speciali". In secondo luogo, generalmente '{}'non usare i {}comandi passati a xargs, per proteggere da spazi e caratteri speciali.
Andreas Spindler,

3
Mi dispiace Andreas Spindler, non ho familiarità con xargs e ho trovato questa frase dopo un po 'di sperimentazione. Sembra funzionare per la maggior parte delle persone che hanno commentato e votato. Ti dispiacerebbe andare un po 'più in dettaglio su che tipo di errore produce? Inoltre, ti dispiacerebbe pubblicare l'input esatto che ritieni più corretto? Grazie.
the_minted

12

Basta non usare xargs. È un programma accurato ma non va benefind fronte a casi non banali.

Ecco una soluzione portatile (POSIX), ovvero una che non richiede find, xargso cpestensioni specifiche GNU:

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

Nota il finale +invece del più solito; .

Questa soluzione:

  • gestisce correttamente file e directory con spazi incorporati, newline o caratteri esotici.

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

  • non utilizza xargsun programma utile e utile, ma richiede troppe modifiche e funzionalità non standard per gestire correttamente l' findoutput.

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

Si noti inoltre che, nonostante quanto affermato in altre risposte o commenti, la citazione {}è inutile (a meno che non si stia utilizzando la fishshell esotica ).



1
@PeterMortensen Probabilmente trascuri il vantaggio finale. findpuò fare ciò che xargsfa senza alcun sovraccarico.
jlliagre,

8

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


8

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

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

1
Funziona ma rallenta perché -Iimplica-L 1
artfulrobot

6
find | perl -lne 'print quotemeta' | xargs ls -d

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


È possibile avere un avanzamento riga in un nome file? Non ne ho mai sentito parlare.
mtk,

2
Certo che lo è. Prova, ad esempio,mkdir test && cd test && perl -e 'open $fh, ">", "this-file-contains-a-\n-here"' && ls | od -tx1
mavit,

1
|perl -lne 'print quotemeta'è esattamente quello che stavo cercando. Altri post qui non mi hanno aiutato perché piuttosto che finddovevo usarli grep -rlper ridurre notevolmente il numero di file PHP solo a quelli infetti da malware.
Marcos,

perl e quotemeta sono molto più generici di print0 / -0 - grazie per la soluzione generale per il pipeline di file con spazi
bmike,

5

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".

Il line-liner 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.


3

Frame challenge - stai chiedendo come usare xargs. La risposta è: non usi xargs, perché non ne hai bisogno.

Il commento diuser80168 descrive un modo per farlo direttamente con cp, senza chiamare cp per ogni file:

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

Questo funziona perché:

  • il cp -tflag consente di dare la directory di destinazione vicino all'inizio cp, piuttosto che alla fine. Da man cp:
   -t, --target-directory=DIRECTORY
         copy all SOURCE arguments into DIRECTORY
  • La --bandiera dice cpdi interpretare tutto dopo come un nome file, non come una bandiera, quindi i file che iniziano con -o --non confondono cp; hai ancora bisogno di questo perché i caratteri -/ --sono interpretati da cp, mentre tutti gli altri caratteri speciali sono interpretati dalla shell.

  • La find -exec command {} +variante essenzialmente fa lo stesso di xargs. Da 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.

Usando questo in find direttamente, questo evita la necessità di una pipe o di una chiamata shell, in modo tale da non doverti preoccupare di alcun brutto personaggio nei nomi dei file.


Incredibile scoperta, non ne avevo idea !!! "-exec utility [argomento ...] {} + Lo stesso di -exec, tranne per il fatto che` `{} '' è sostituito con il maggior numero di percorsi possibili per ogni invocazione dell'utilità. Questo comportamento è simile a quello di xargs (1 )." nell'implementazione di BSD.
Conny,

2

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 POSIX specifiche 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 caratteri ASCII NUL '\ 0'. Il mio xargl non è completo come dovrebbe essere degno di essere pubblicato, specialmente perché GNU ha strutture almeno altrettanto buone.


2
GitHub o non è successo
Corey Goldberg,

@CoreyGoldberg: immagino che non sia successo allora.
Jonathan Leffler,

POSIX findnon è necessario xargsin primo luogo (e questo era già vero 11 anni fa).
jlliagre,

2

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

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/

1

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

 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

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 non eseguirà 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+"$@"}
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

La versione Perl di bill_starr non funzionerà bene per le newline incorporate (gestisce solo gli spazi). Per quelli su es. Solaris in cui 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

Ho usato la risposta di Bill Star leggermente modificata 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 poiché desidero 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 installato Python 3) 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.


0

Potrebbe essere necessario grep nella directory Foobar come:

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

1
Per la pagina man, -iè deprecato e -Idovrebbe essere usato invece.
Acumenus,

-1

Se stai usando Bash, puoi convertire stdout in un array di linee mapfile:

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

I vantaggi sono:

  • È integrato, quindi è più veloce.
  • Esegui il comando con tutti i nomi di file in una volta, quindi è più veloce.
  • È possibile aggiungere altri argomenti ai nomi dei file. Per cp, puoi anche:

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

    tuttavia, alcuni comandi non hanno tale funzione.

Gli svantaggi:

  • Forse non ridimensionare bene se ci sono troppi nomi di file. (Il limite? Non lo so, ma avevo testato con 10 MB di file elenco che include oltre 10000 nomi di file senza problemi, sotto Debian)

Bene ... chissà se Bash è disponibile su OS X?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.