Domanda

Sto cercando di copiare un mucchio di file sotto una directory e un certo 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 & nbsp; OS & nbsp; X 10.5.3 (Leopard) con codice BSD < > xargs .

È stato utile?

Soluzione

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

Altri suggerimenti

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

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

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

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

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

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 (`` & amp; '') che appare nella sostituzione viene sostituita dalla stringa corrispondente a RE ... " - in questo caso, . * , l'intera riga.

Questo risolve l'errore xargs: citazione non terminata .

Questo metodo funziona su Mac & nbsp; OS & nbsp; X & nbsp; 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.

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 soluzione che non richiede find , xargs o cp estensioni specifiche GNU:

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

Nota il + finale invece del ; più normale.

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 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 altre risposte o commenti che citano {} , è inutile (a meno che tu non stia usando la shell fish esotica).

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

Per coloro che si affidano a comandi diversi da find, ad esempio ls :

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar
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 questo). Non richiede finduils GNU, solo Perl, quindi dovrebbe funzionare praticamente ovunque.

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 liner line Perl tra " find " e "xargs" sfugge / cita ogni spazio vuoto così "xargs" passa qualsiasi nome di file con spazi vuoti incorporati a " ls " come unico argomento.

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, per esempio). Consulta le 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 sono separate - sebbene la combinazione 'find ... -print0' e 'xargs -0' sia piuttosto ordinata dato che i nomi dei file non possono contenere caratteri ASCII NUL '\ 0'. Il mio xargl non è completo come dovrebbe essere degno di essere pubblicato, soprattutto perché GNU ha strutture almeno altrettanto buone.

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)

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/

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

Ho creato un piccolo script wrapper portatile chiamato " xargsL " intorno 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 eccetto (ovviamente) newline o byte NUL.

Nessuna citazione è consentita o supportata 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 il " xargsL " Script shell POSIX:

#! /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 "<*> failed!" >& 2' 0

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

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

$ chmod + x xargsL

lo script lì per renderlo eseguibile.

versione Perl di bill_starr non funzionerà bene per le nuove linee 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 / tab incorporati.

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 in quanto voglio rivedere ciò che è stato trovato prima di iniziare a cancellarli in modo massiccio tramite xargs.

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 l'intera faccenda significa che è facile capire cosa sta succedendo, e se è richiesto un comportamento diverso, puoi semplicemente hackerarlo in un nuovo script in meno tempo di esso richiede una risposta in un posto come Stack & nbsp; Overflow.

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

Con gli yarg scritti (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.

Potrebbe essere necessario grep directory di Foobar come:

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

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

Il commentato da user80168 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 -t permette di dare alla directory di destinazione vicino all'inizio di cp , piuttosto che alla fine. Da man cp :
   -t, --target-directory=DIRECTORY
         copy all SOURCE arguments into DIRECTORY
  • Il flag - dice a cp di interpretare tutto ciò che segue come un nome file, non un flag, quindi i file che iniziano con - o - non confondere cp ; hai ancora bisogno perché i caratteri - / - sono interpretati da cp , mentre tutti gli altri caratteri speciali sono interpretati dalla shell.

  • La variante find -exec command {} + 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 che non devi preoccuparti di alcun brutto personaggio nei nomi dei file.

Se stai usando Bash, puoi convertire stdout in un array di linee mediante 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.
  • Puoi 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 provato con 10 & nbsp; file di elenco MB che include oltre 10000 nomi di file senza problemi, in Debian)

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top