Make xargs esegue il comando una volta per ogni riga di input


341

Come posso fare in modo che xargs esegua il comando esattamente una volta per ogni riga di input fornita? Il comportamento predefinito è di tagliare le linee ed eseguire il comando una volta, passando più righe a ciascuna istanza.

Da http://en.wikipedia.org/wiki/Xargs :

find / path -type f -print0 | xargs -0 rm

In questo esempio, find alimenta l'input di xargs con un lungo elenco di nomi di file. xargs quindi suddivide questo elenco in elenchi secondari e chiama rm una volta per ciascun elenco secondario. Questo è più efficiente di questa versione funzionalmente equivalente:

find / path -type f -exec rm '{}' \;

So che find ha il flag "exec". Sto solo citando un esempio illustrativo da un'altra risorsa.


4
Nell'esempio fornito, find /path -type f -deletesarebbe ancora più efficiente :)
tzot

cerca di non usare xargs ...
Naib

6
OP, so che questa domanda è molto vecchia, ma si presenta ancora su Google e IMHO la risposta accettata è sbagliata. Vedi la mia risposta più lunga di seguito.
Tobia,

Ti preghiamo di considerare di passare da accetta alla risposta di @ Tobia che è molto meglio. La risposta accettata non gestisce gli spazi nei nomi e non consente più argomenti al comando xargs, che è una delle caratteristiche principali di xargs.
Gray,

Risposte:


394

Quanto segue funzionerà solo se non hai spazi nel tuo input:

xargs -L 1
xargs --max-lines=1 # synonym for the -L option

dalla pagina man:

-L max-lines
          Use at most max-lines nonblank input lines per command line.
          Trailing blanks cause an input line to be logically continued  on
          the next input line.  Implies -x.

13
Per me può essere xargs -n 1come quello che hai dato ha mostrato "elenco argomenti troppo lungo".
Wernight,

19
Se MAX-LINESviene omesso, il valore predefinito è 1, quindi xargs -lè sufficiente. Vedere info xargs.
Thor,

3
@Wernight: "-n1" non fornisce 1 invocazione per riga di input. forse la tua riga di input era troppo lunga. demo: echo "foo bar" | xargs -n1 echo. quindi se installi cose come 'ls', non gestirà bene gli spazi.
gatoatigrado,

8
Questo è sbagliato. -L 1non risponde alla domanda originale e lo -n 1fa solo in una delle possibili interpretazioni. Vedi la mia lunga risposta di seguito.
Tobia,

2
@Tobia: risponde alla domanda originale, che era piuttosto specifica per le linee di input. Questo è esattamente ciò che -L 1fa. Per me, l'OP sembrava chiaramente tentare di evitare il comportamento di chunking predefinito, e poiché questo è stato accettato, presumo di aver ragione. La tua risposta affronta un caso d'uso leggermente diverso in cui desideri anche il comportamento in blocco.
Draemon

207

Mi sembra che tutte le risposte esistenti su questa pagina siano sbagliate, compresa quella contrassegnata come corretta. Ciò deriva dal fatto che la domanda è formulata in modo ambiguo.

Riepilogo:   se si desidera eseguire il comando "esattamente una volta per ogni riga di input fornita", passando l'intera riga (senza la nuova riga) al comando come singolo argomento, questo è il modo migliore compatibile UNIX per farlo:

... | tr '\n' '\0' | xargs -0 -n1 ...

GNU xargspuò avere o meno estensioni utili che ti consentono di eliminare tr, ma non sono disponibili su OS X e altri sistemi UNIX.

Ora per la lunga spiegazione ...


Ci sono due problemi da prendere in considerazione quando si usa xargs:

  1. come divide l'input in "argomenti"; e
  2. quanti argomenti per passare il comando figlio alla volta.

Per testare il comportamento di xargs, abbiamo bisogno di un'utilità che mostri quante volte viene eseguita e con quanti argomenti. Non so se esiste un'utilità standard per farlo, ma possiamo codificarlo abbastanza facilmente in bash:

#!/bin/bash
echo -n "-> "; for a in "$@"; do echo -n "\"$a\" "; done; echo

Supponendo di salvarlo come shownella directory corrente e renderlo eseguibile, ecco come funziona:

$ ./show one two 'three and four'
-> "one" "two" "three and four" 

Ora, se la domanda originale riguarda davvero il punto 2. sopra (come penso, dopo averlo letto più volte) e deve essere letta in questo modo (cambia in grassetto):

Come posso fare in modo che xargs esegua il comando esattamente una volta per ogni argomento di input fornito? Il suo comportamento predefinito è di inserire l' input in argomenti ed eseguire il comando il minor numero di volte possibile , passando più argomenti a ciascuna istanza.

allora la risposta è -n 1.

Confrontiamo il comportamento predefinito di xargs, che divide l'input attorno agli spazi bianchi e chiama il comando il minor numero di volte possibile:

$ echo one two 'three and four' | xargs ./show 
-> "one" "two" "three" "and" "four" 

e il suo comportamento con -n 1:

$ echo one two 'three and four' | xargs -n 1 ./show 
-> "one" 
-> "two" 
-> "three" 
-> "and" 
-> "four" 

Se, d'altra parte, la domanda originale riguardava il punto 1. suddivisione degli input e doveva essere letta in questo modo (molte persone che vengono qui sembrano pensare che sia il caso o confondono le due questioni):

Come posso fare in modo che xargs esegua il comando con esattamente un argomento per ogni riga di input fornita? Il suo comportamento predefinito è di tagliare le linee attorno agli spazi bianchi .

allora la risposta è più sottile.

Si potrebbe pensare che -L 1possa essere di aiuto, ma si scopre che non cambia l'analisi degli argomenti. Esegue il comando solo una volta per ogni riga di input, con tutti gli argomenti presenti su quella riga di input:

$ echo $'one\ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" 
-> "two" 
-> "three" "and" "four" 

Non solo, ma se una riga termina con spazi bianchi, viene aggiunta alla seguente:

$ echo $'one \ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" "two" 
-> "three" "and" "four" 

Chiaramente, -Lnon si tratta di cambiare il modo in cui xargs divide l'input in argomenti.

L'unico argomento che lo fa in modo multipiattaforma (escluse le estensioni GNU) è -0, che divide l'input attorno ai byte NUL.

Quindi, si tratta solo di tradurre le nuove righe in NUL con l'aiuto di tr:

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 ./show 
-> "one " "two" "three and four" 

Ora l'analisi dell'argomento sembra a posto, incluso lo spazio bianco finale.

Infine, se combini questa tecnica con -n 1, ottieni esattamente un'esecuzione di comando per riga di input, qualunque input tu abbia, che potrebbe essere un altro modo per guardare alla domanda originale (forse la più intuitiva, dato il titolo):

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 -n1 ./show 
-> "one " 
-> "two" 
-> "three and four" 

sembra che questa sia la risposta migliore. tuttavia, ancora non capisco bene qual è la differenza tra -L e -n ... puoi spiegarci un po 'di più?
Olala,

5
@olala -Lesegue il comando una volta per riga di input (ma uno spazio alla fine di una riga lo unisce alla riga successiva e la riga viene comunque suddivisa in argomenti in base allo spazio bianco); while -nesegue il comando una volta per argomento di input. Se si conta il numero di ->negli esempi di output, si tratta del numero di volte in cui lo script ./showviene eseguito.
Tobia,

Vedo! non ha realizzato uno spazio alla fine di una linea che lo unisce alla riga successiva. Grazie!
Olala,

4
GNU xargspuò avere o meno estensioni utili che ti permettono di eliminare.tr Ha un'estensione così utile; da xargs --help- -d, --delimiter = Gli elementi CHARACTER nel flusso di input sono separati da CHARACTER, non da spazi bianchi; disabilita l'elaborazione delle virgolette e della barra rovesciata e l'elaborazione logica EOF
Piotr Dobrogost

Questa risposta sembra confusa riguardo -L. -Lnon dice quante volte eseguire lo script per riga, indica quante righe di dati di input consumare alla volta.
Moberg,

22

Se vuoi eseguire il comando per ogni riga (es. Risultato) proveniente find, allora di cosa hai bisogno xargs?

Provare:

find percorso -type f -exec tuo comando {} \;

dove il valore letterale {}viene sostituito dal nome file e il valore letterale \;è necessario per findsapere che il comando personalizzato termina lì.

MODIFICARE:

(dopo la modifica della tua domanda chiarendo che conosci -exec)

Da man xargs:

-L max line
Utilizzare al massimo max line righe di input non vuote per riga di comando. Gli spazi vuoti finali fanno sì che una riga di input continui logicamente sulla riga di input successiva. Implica -x.

Si noti che i nomi di file che terminano con spazi vuoti potrebbero causare problemi se si utilizza xargs:

$ mkdir /tmp/bax; cd /tmp/bax
$ touch a\  b c\  c
$ find . -type f -print | xargs -L1 wc -l
0 ./c
0 ./c
0 total
0 ./b
wc: ./a: No such file or directory

Quindi se non ti interessa l' -execopzione, è meglio usare -print0e -0:

$ find . -type f -print0 | xargs -0L1 wc -l
0 ./c
0 ./c
0 ./b
0 ./a

18

Come posso fare in modo che xargs esegua il comando esattamente una volta per ogni riga di input fornita?

-L 1è la soluzione semplice ma non funziona se uno dei file contiene spazi. Questa è una funzione chiave -print0dell'argomento find : separare gli argomenti dal carattere '\ 0' invece che dagli spazi bianchi. Ecco un esempio:

echo "file with space.txt" | xargs -L 1 ls
ls: file: No such file or directory
ls: with: No such file or directory
ls: space.txt: No such file or directory

Una soluzione migliore consiste nell'utilizzare trper convertire le nuove righe in caratteri null ( \0) e quindi utilizzare l' xargs -0argomento. Ecco un esempio:

echo "file with space.txt" | tr '\n' '\0' | xargs -0 ls
file with space.txt

Se è quindi necessario limitare il numero di chiamate, è possibile utilizzare l' -n 1argomento per effettuare una chiamata al programma per ciascun input:

echo "file with space.txt" | tr '\n' '\0' | xargs -0 -n 1 ls

Ciò consente anche di filtrare l'output di find prima di convertire le interruzioni in valori null.

find . -name \*.xml | grep -v /target/ | tr '\n' '\0' | xargs -0 tar -cf xml.tar

1
C'è un errore di sintassi nel secondo blocco di codice tr '\ n' '\ 0 \ => tr' \ n '' \ 0 ', ho provato a risolvere questo problema ma "Le modifiche devono essere di almeno 6 caratteri" (sembra che stupido come git che rifiuta di impegnarsi perché il mio cambio è stato inferiore a 6 caratteri)
htaccess

1
Cosa significa: "Un altro problema con l'utilizzo -Lè che non consente più argomenti per ogni xargschiamata di comando."?
Moberg,

Ho migliorato la mia risposta per rimuovere quelle informazioni estranee @Moberg.
Grigio

11

Un'altra alternativa ...

find /path -type f | while read ln; do echo "processing $ln"; done

9

Questi due modi funzionano anche e funzioneranno per altri comandi che non usano find!

xargs -I '{}' rm '{}'
xargs -i rm '{}'

esempio caso d'uso:

find . -name "*.pyc" | xargs -i rm '{}'

eliminerà tutti i file pyc in questa directory anche se i file pyc contengono spazi.


Questo genera una chiamata di utilità per ogni elemento che non è ottimale.
Gray,

7
find path -type f | xargs -L1 command 

é tutto quello di cui hai bisogno.


4

Il seguente comando troverà tutti i file (-type f) /pathe li copia usando cpnella cartella corrente. Si noti l'uso se -I %per specificare un carattere segnaposto nella cpriga di comando in modo che gli argomenti possano essere inseriti dopo il nome del file.

find /path -type f -print0 | xargs -0 -I % cp % .

Testato con xargs (GNU findutils) 4.4.0


2

Puoi limitare il numero di linee o argomenti (se ci sono spazi tra ogni argomento) usando rispettivamente i flag --max-lines o --max-args.

  -L max-lines
         Use at most max-lines nonblank input lines per command line.  Trailing blanks cause an input line to be logically continued on the next  input
         line.  Implies -x.

  --max-lines[=max-lines], -l[max-lines]
         Synonym  for  the -L option.  Unlike -L, the max-lines argument is optional.  If max-args is not specified, it defaults to one.  The -l option
         is deprecated since the POSIX standard specifies -L instead.

  --max-args=max-args, -n max-args
         Use at most max-args arguments per command line.  Fewer than max-args arguments will be used if the size (see  the  -s  option)  is  exceeded,
         unless the -x option is given, in which case xargs will exit.

0

Sembra che non abbia abbastanza reputazione per aggiungere un commento alla risposta di Tobia sopra , quindi sto aggiungendo questa "risposta" per aiutare coloro che vogliono sperimentare xargsallo stesso modo sulle piattaforme Windows.

Ecco un file batch di Windows che fa la stessa cosa dello script "show" codificato rapidamente di Tobia:

@echo off
REM
REM  cool trick of using "set" to echo without new line
REM  (from:  http://www.psteiner.com/2012/05/windows-batch-echo-without-new-line.html)
REM
if "%~1" == "" (
    exit /b
)

<nul set /p=Args:  "%~1"
shift

:start
if not "%~1" == "" (
    <nul set /p=, "%~1"
    shift
    goto start
)
echo.

0

Le risposte di @Draemon sembrano essere corrette con "-0" anche con spazio nel file.

Stavo provando il comando xargs e ho scoperto che "-0" funziona perfettamente con "-L". anche gli spazi vengono trattati (se l'input è stato annullato con null). Quanto segue è un esempio :

#touch "file with space"
#touch "file1"
#touch "file2"

Quanto segue dividerà i valori null ed eseguirà il comando su ciascun argomento nell'elenco:

 #find . -name 'file*' -print0 | xargs -0 -L1
./file with space
./file1
./file2

quindi -L1eseguirà l'argomento su ogni carattere nullo se usato con "-0". Per vedere la differenza prova:

 #find . -name 'file*' -print0 | xargs -0 | xargs -L1
 ./file with space ./file1 ./file2

anche questo verrà eseguito una volta:

 #find . -name 'file*' -print0  | xargs -0  | xargs -0 -L1
./file with space ./file1 ./file2

Il comando verrà eseguito una volta poiché "-L" ora non si divide su byte null. devi fornire sia "-0" che "-L" per funzionare.


-3

Nel tuo esempio, il punto di reindirizzare l'output di find a xargs è che il comportamento standard dell'opzione -exec di find è quello di eseguire il comando una volta per ogni file trovato. Se stai usando find e vuoi il suo comportamento standard, allora la risposta è semplice: non usare xargs per cominciare.


In realtà, ciò che posso implicare dalle modifiche del PO è che i dati di input non hanno nulla a che fare con questo find, ed è per questo che non preferiscono l' -execopzione.
decreto

-3

esegui il task di form clean-all su ogni build.xml nella cartella corrente o in quella secondaria.

find . -name 'build.xml' -exec ant -f {} clean-all \;

Non tutti hanno antinstallato.
Gray,
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.