Fai in modo che xargs gestisca nomi di file che contengono spazi


252
$ ls *mp3 | xargs mplayer  

Playing Lemon.  
File not found: 'Lemon'  
Playing Tree.mp3.  
File not found: 'Tree.mp3'  

Exiting... (End of file)  

Il mio comando fallisce perché il file "Lemon Tree.mp3" contiene spazi e quindi xargs pensa che siano due file. Posso far funzionare find + xargs con nomi di file come questo?


Invece di ls |grep mp3 |sed -n "7p"te puoi semplicemente usare echo "Lemon Tree.mp3".
Micha Wiedenmann,


Risposte:


256

Il xargscomando accetta caratteri di spazi bianchi (tabulazioni, spazi, nuove righe) come delimitatori. Puoi restringerlo solo per i nuovi caratteri di riga ('\ n') con -dun'opzione come questa:

ls *.mp3 | xargs -d '\n' mplayer

Funziona solo con xargs GNU. Per i sistemi BSD, utilizzare l' -0opzione in questo modo:

ls *.mp3 | xargs -0 mplayer

Questo metodo è più semplice e funziona anche con gli xarg GNU.


6
La migliore risposta per uso generale! Funziona anche se il tuo comando precedente non è "trova"
nexayq,

28
Sfortunatamente, questa opzione non è disponibile su OS X.
Thomas Tempelmann,

25
@Thomas Per OS X, la bandiera è -E, es:xargs -E '\n'

30
Su OS X, -E '\ n' non ha avuto alcun effetto per me, né me lo sarei aspettato poiché ha modificato l'eofstr e non il separatore dei record. Tuttavia, sono stato in grado di utilizzare il flag -0 come soluzione, anche se il comando precedente non è 'trova', simulando l'effetto del flag -print0 di find nel mio input, ad esempio: ls * mp3 | tr '\ n' '\ 0' | xargs -0 mplayer
biomiker

10
Per OS X, è possibile "Brew installare Findutils", che vi dà il comando "gxargs" che fa avere l'opzione -d.
Tom De Leu,

213

L'utilità xargs legge stringhe delimitate di spazio, tabulazione, newline e fine file dall'input standard ed esegue l'utilità con le stringhe come argomenti.

Vuoi evitare di usare lo spazio come delimitatore. Questo può essere fatto modificando il delimitatore per xargs. Secondo il manuale:

 -0      Change xargs to expect NUL (``\0'') characters as separators,
         instead of spaces and newlines.  This is expected to be used in
         concert with the -print0 function in find(1).

Ad esempio:

 find . -name "*.mp3" -print0 | xargs -0 mplayer

Per rispondere alla domanda sulla riproduzione del settimo mp3; è più semplice da eseguire

 mplayer "$(ls *.mp3 | sed -n 7p)"

10
Questo sta usando GNU finde GNU xargs; non tutte le versioni di quei programmi supportano quelle opzioni (anche se c'è un caso da fare che dovrebbero).
Jonathan Leffler,

1
@JonathanLeffler s / GNU / FreeBSD / g; POSIX purtroppo ha paura dei caratteri NUL nei file di testo e non ha ancora avuto abbastanza terapia :-) Il mio consiglio in effetti ricorre a opzioni non portatili.
Jens,

6
E Mac OS X (un derivato BSD) ha findcon -print0e xargscon -0. AFAIK, HP-UX, AIX e Solaris non lo fanno, tuttavia (ma sto per essere corretto: HP-UX 11i no; Solaris 10 no; AIX 5.x no; ma non sono le versioni attuali ). Ad sedesempio, non sarebbe difficile modificare le "linee" che terminano '\0'invece di '\n', e POSIX 2008 getdelim()ne semplificherebbe la gestione.
Jonathan Leffler,

2
+1 + 1 trucco da utilizzare con percorsi di file contenenti file elenco: cat $ file_paths_list_file | perl -ne 's | \ n | \ 000 | g; print' | xargs -0 zip $ zip_package
Yordan Georgiev

2
Buona idea per sostituire le nuove righe con NUL - ho dovuto farlo su un sistema embedded che non aveva GNU find né GNU xargs né perl - ma il comando tr può essere sfruttato per fare lo stesso: cat $ file_paths_list_file | tr '\ n' '\ 0' | xargs -0 du -hms
joensson

28

Provare

find . -name \*.mp3 -print0 | xargs -0 mplayer

invece di

ls | grep mp3 

16

xargs su MacOS non ha l'opzione -d, quindi questa soluzione usa -0 invece.

Chiedi a ls di produrre un file per riga, quindi traduci le nuove righe in valori nulli e comunica a xargs di utilizzare i valori null come delimitatore:

ls -1 *mp3 | tr "\n" "\0" | xargs -0 mplayer


8
find . -name 'Lemon*.mp3' -print0 | xargs 0 -i mplayer '{}' 

Questo ha aiutato nel mio caso a eliminare diversi file con spazi. Dovrebbe funzionare anche con mplayer. Il trucco necessario sono le virgolette. (Testato su Linux Xubuntu 14.04.)


7

La risposta di Dick.Guertin [1] suggeriva che si potesse sfuggire agli spazi in un nome di file è una valida alternativa ad altre soluzioni qui suggerite (come usare un carattere nullo come separatore piuttosto che uno spazio). Ma potrebbe essere più semplice: non hai davvero bisogno di un personaggio unico. Puoi solo fare in modo che sed aggiunga direttamente gli spazi di escape:

ls | grep ' ' | sed 's| |\\ |g' | xargs ...

Inoltre, grep è necessario solo se si desidera solo file con spazi nei nomi. Più genericamente (ad esempio, quando si elabora un batch di file alcuni dei quali hanno spazi, altri no), basta saltare il grep:

ls | sed 's| |\\ |g' | xargs ...

Quindi, ovviamente, il nome file può avere spazi bianchi diversi dagli spazi (ad esempio, una scheda):

ls | sed -r 's|[[:blank:]]|\\\1|g' | xargs ...

Ciò presuppone che tu abbia una sed che supporti -r (regex esteso) come GNU sed o versioni recenti di bsd sed (ad es. FreeBSD che originariamente scriveva l'opzione "-E" prima di FreeBSD 8 e supporta sia -r che -E per la compatibilità almeno tramite FreeBSD 11). Altrimenti è possibile utilizzare un'espressione di parentesi della classe di caratteri regex di base e immettere manualmente lo spazio e i caratteri di tabulazione in[] delimitatori.

[1] Questo è forse più appropriato come commento o modifica a quella risposta, ma al momento non ho abbastanza reputazione per commentare e posso solo suggerire modifiche. Poiché quest'ultima forma sopra (senza grep) altera il comportamento della risposta originale di Dick.Guertin, una modifica diretta forse non è appropriata comunque.


1
pazzi ragazzi unix che eseguono script che nominano i file senza considerare il loro output, ecco chi
andrew lorien

4

ls | grep mp3 | sed -n "7p" | xargs -i mplayer {}

Si noti che nel comando sopra, xargschiamerà di mplayernuovo per ogni file. Questo può essere indesiderabile per mplayer, ma può essere a posto per altri obiettivi.


1
Un'utile aggiunta alle risposte esistenti, ma varrebbe la pena notare che questo farà sì mplayerche venga chiamato di nuovo per ogni file. È importante se provi ad esempio ... | xargs -I{} mplayer -shuffle {}: questo suonerà in un ordine completamente deterministico, nonostante -shuffle.

1
Di solito non è l'intento. xargsviene utilizzato principalmente con comandi che accettano un elenco di nomi di file (esempio semplice:) rme tenta di passare il maggior numero di nomi di file che può adattarsi a ciascuna chiamata, suddividendosi in più chiamate solo se necessario. Puoi vedere la differenza quando usi un comando in cui ogni chiamata è visibile, come echo(impostazione predefinita): seq 0 100000 | xargsstampa tutti i numeri da 0 a 23695 (specifici della piattaforma, ma è quello che succede sul mio sistema) sulla prima riga, a 45539 sulla linea 2, ecc. E hai ragione, per la maggior parte dei comandi, non importa.

4

Su macOS 10.12.x (Sierra), se hai spazi nei nomi di file o nelle sottodirectory, puoi usare quanto segue:

find . -name '*.swift' -exec echo '"{}"' \; |xargs wc -l

2

Dipende da (a) quanto sei attaccato al numero 7 invece di, diciamo, Limoni, e (b) se uno qualsiasi dei tuoi nomi di file contiene nuove righe (e se sei disposto a rinominarli se lo fanno).

Esistono molti modi per gestirlo, ma alcuni sono:

mplayer Lemon*.mp3

find . -name 'Lemon*.mp3' -exec mplayer {} ';'

i=0
for mp3 in *.mp3
do
    i=$((i+1))
    [ $i = 7 ] && mplayer "$mp3"
done

for mp3 in *.mp3
do
    case "$mp3" in
    (Lemon*) mplayer "$mp3";;
    esac
done

i=0
find . -name *.mp3 |
while read mp3
do
    i=$((i+1))
    [ $i = 7 ] && mplayer "$mp3"
done

Il readciclo non funziona se i nomi dei file contengono nuove righe; gli altri funzionano correttamente anche con le nuove righe nei nomi (per non parlare degli spazi). Per quanto mi riguarda, se hai nomi di file contenenti una nuova riga, dovresti rinominare il file senza la nuova riga. L'uso delle virgolette doppie attorno al nome del file è la chiave per il corretto funzionamento dei loop.

Se hai GNU finde GNU xargs(o FreeBSD (* BSD?) O Mac OS X), puoi anche usare le opzioni -print0e -0, come in:

find . -name 'Lemon*.mp3' -print0 | xargs -0 mplayer

Funziona indipendentemente dal contenuto del nome (gli unici due caratteri che non possono apparire in un nome file sono slash e NUL e la barra non causa problemi in un percorso di file, quindi l'utilizzo di NUL come delimitatore di nomi copre tutto). Tuttavia, se è necessario filtrare le prime 6 voci, è necessario un programma che gestisca le "linee" terminate da NUL anziché da Newline ... e non sono sicuro che ce ne siano.

Il primo è di gran lunga il più semplice per il caso specifico a portata di mano; tuttavia, potrebbe non generalizzare per coprire altri scenari che non hai ancora elencato.


2

So che non sto rispondendo xargsdirettamente alla domanda, ma vale la pena menzionare findl' -execopzione.

Dato il seguente file system:

[root@localhost bokeh]# tree --charset assci bands
bands
|-- Dream\ Theater
|-- King's\ X
|-- Megadeth
`-- Rush

0 directories, 4 files

Il comando find può essere fatto per gestire lo spazio in Dream Theater e King's X. Quindi, per trovare i batteristi di ogni band usando grep:

[root@localhost]# find bands/ -type f -exec grep Drums {} +
bands/Dream Theater:Drums:Mike Mangini
bands/Rush:Drums: Neil Peart
bands/King's X:Drums:Jerry Gaskill
bands/Megadeth:Drums:Dirk Verbeuren

Nella -execscelta {}sta per il nome del file incluso il percorso. Nota che non devi scappare o metterlo tra virgolette.

La differenza tra -execi terminatori ( +e \;) è che +raggruppa quanti più nomi di file è possibile su una riga di comando. Considerando \;che eseguirà il comando per ogni nome di file.

Quindi, find bands/ -type f -exec grep Drums {} +comporterà:

grep Drums "bands/Dream Theater" "bands/Rush" "bands/King's X" "bands/Megadeth"

e find bands/ -type f -exec grep Drums {} \;comporterà:

grep Drums "bands/Dream Theater"
grep Drums "bands/Rush"
grep Drums "bands/King's X"
grep Drums "bands/Megadeth"

In questo caso grepha l'effetto collaterale di stampare il nome del file o meno.

[root@localhost bokeh]# find bands/ -type f -exec grep Drums {} \;
Drums:Mike Mangini
Drums: Neil Peart
Drums:Jerry Gaskill
Drums:Dirk Verbeuren

[root@localhost bokeh]# find bands/ -type f -exec grep Drums {} +
bands/Dream Theater:Drums:Mike Mangini
bands/Rush:Drums: Neil Peart
bands/King's X:Drums:Jerry Gaskill
bands/Megadeth:Drums:Dirk Verbeuren

Naturalmente, greple opzioni -he -Hcontrolleranno se il nome del file viene stampato o meno indipendentemente da come grepviene chiamato.


xargs

xargs può anche controllare come sono i file man sulla riga di comando.

xargsper impostazione predefinita raggruppa tutti gli argomenti su una riga. Per fare la stessa cosa che -exec \;usa xargs -l. Si noti che l' -topzione dice xargsdi stampare il comando prima di eseguirlo.

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n' -l -t grep Drums
grep Drums ./bands/Dream Theater 
Drums:Mike Mangini
grep Drums ./bands/Rush 
Drums: Neil Peart
grep Drums ./bands/King's X 
Drums:Jerry Gaskill
grep Drums ./bands/Megadeth 
Drums:Dirk Verbeuren

Vedi che l' -lopzione dice a xargs di eseguire grep per ogni nome di file.

Contro il valore predefinito (ovvero nessuna -lopzione):

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n'  -t grep Drums
grep Drums ./bands/Dream Theater ./bands/Rush ./bands/King's X ./bands/Megadeth 
./bands/Dream Theater:Drums:Mike Mangini
./bands/Rush:Drums: Neil Peart
./bands/King's X:Drums:Jerry Gaskill
./bands/Megadeth:Drums:Dirk Verbeuren

xargsha un migliore controllo su quanti file possono essere presenti nella riga di comando. Dai -lall'opzione il numero massimo di file per comando.

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n'  -l2 -t grep Drums
grep Drums ./bands/Dream Theater ./bands/Rush 
./bands/Dream Theater:Drums:Mike Mangini
./bands/Rush:Drums: Neil Peart
grep Drums ./bands/King's X ./bands/Megadeth 
./bands/King's X:Drums:Jerry Gaskill
./bands/Megadeth:Drums:Dirk Verbeuren
[root@localhost bokeh]# 

Vedi che è grepstato eseguito con due nomi di file a causa di -l2.


1

Dato il titolo specifico di questo post, ecco il mio suggerimento:

ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g'

L'idea è di convertire gli spazi vuoti in qualsiasi carattere univoco, come '<', e poi cambiarlo in '\', una barra rovesciata seguita da uno spazio vuoto. È quindi possibile reindirizzarlo a qualsiasi comando desiderato, ad esempio:

ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g' | xargs -L1 GetFileInfo

La chiave qui sta nei comandi 'tr' e 'sed'; e puoi usare qualsiasi carattere oltre a "<", come "?" o persino un carattere di tabulazione.


Qual è lo scopo della deviazione tramite tr? Perché non solo ls *.mp3 | sed -n '7!b;s/\([[:space:]]\)/\\\1/g;p'?
triplo

1
Ho scoperto che "tr '' '?'" Elimina la necessità di "sed". Il singolo "?" il carattere non è vuoto, ma corrisponde a QUALSIASI carattere singolo, in questo caso: vuoto. Le probabilità che si tratti di qualcos'altro sono piuttosto piccole e accettabili poiché stai cercando di elaborare TUTTI i file che terminano con .mp3: "ls | grep '' | tr '' '?' | xargs -L1 GetFileInfo "
Dick Guertin,

Puoi anche gestire "tab" allo stesso tempo: tr '\ t' '??' gestisce entrambi.
Dick Guertin,

1

Soluzioni alternative possono essere utili ...

Puoi anche aggiungere un carattere null alla fine delle tue linee usando Perl, quindi usa l' -0opzione in xargs. A differenza di xargs -d '\ n' (nella risposta approvata) - funziona ovunque, incluso OS X.

Ad esempio, per elencare in modo ricorsivo (eseguire, spostare, ecc.) I file MPEG3 che possono contenere spazi o altri personaggi divertenti - userò:

find . | grep \.mp3 | perl -ne 'chop; print "$_\0"' | xargs -0  ls

(Nota: per il filtraggio, preferisco la sintassi "| grep" più facile da ricordare rispetto agli argomenti "find's" --name.)

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.