Come posso usare xargs per copiare file che hanno spazi e virgolette nei loro nomi?
-
02-07-2019 - |
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 .
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 difind
. -
è 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 dicp
, piuttosto che alla fine. Daman cp
:
-t, --target-directory=DIRECTORY copy all SOURCE arguments into DIRECTORY
-
Il flag
-
dice acp
di interpretare tutto ciò che segue come un nome file, non un flag, quindi i file che iniziano con-
o-
non confonderecp
; hai ancora bisogno perché i caratteri-
/-
sono interpretati dacp
, mentre tutti gli altri caratteri speciali sono interpretati dalla shell. -
La variante
find -exec command {} +
essenzialmente fa lo stesso di xargs. Daman 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?