Eseguire un comando una volta per riga di input convogliato?


162

Voglio eseguire un comando java una volta per ogni partita di ls | grep pattern -. In questo caso, penso di poterlo fare, find pattern -exec java MyProg '{}' \;ma sono curioso del caso generale: esiste un modo semplice per dire "esegui un comando una volta per ogni riga di input standard"? (In pesce o bash.)

Risposte:


92

Questo è quello che xargsfa.

... | xargs command

25
Non proprio. printf "foo bar\nbaz bat" | xargs echo wheecederà whee foo bar baz bat. Forse aggiungi le opzioni -Lo -n?
Jander,

3
@Jander La domanda era piuttosto generale, quindi ho dato lo strumento generale. È vero, dovrai adattare il suo comportamento con le opzioni a seconda delle circostanze specifiche.
Keith,

4
... | tr '\ n' '\ 0' | xargs -0
vrdhn

7
come "le circostanze specifiche che danno la risposta giusta alla domanda". :)
mattdm,

7
Se vuoi vedere il modo corretto di farlo con xargs, vedi la mia risposta qui sotto.
Michael Goldshteyn,

167

La risposta accettata ha l'idea giusta, ma la chiave è passare xargsl' -n1interruttore, che significa "Esegui il comando una volta per riga di output:"

cat file... | xargs -n1 command

Oppure, per un singolo file di input, puoi evitare completamente la pipe cate andare semplicemente con:

<file xargs -n1 command

1
Interessante è anche la capacità di xargsper non correre se stdinè vuota: --no-run-if-empty -r: Se lo standard input non contiene Nonblanks, non eseguire il comando. Normalmente, il comando viene eseguito una volta anche se non è presente alcun input. Questa opzione è un'estensione GNU.
Ronan Jouchet,

4
Come accedi alla linea interna command?
BT,

Questo è l'uso corretto di xargs. Senza -n1, funziona solo su comandi che trattano elenchi di parametri come invocazioni multiple che non tutti fanno.
Masterxilo,

3
printf "foo bar \ nbaz bat" | xargs -n1 echo whee si divide per parole e non per linee
Gismo Ranas

112

In Bash o in qualsiasi altra shell in stile Bourne (ash, ksh, zsh, ...):

while read -r line; do command "$line"; done

read -rlegge una singola riga dall'input standard ( readsenza -rinterpretare le barre rovesciate, non lo vuoi). Quindi è possibile effettuare una delle seguenti operazioni:

$ command | while read -r line; do command "$line"; done  

$ while read -r line; do command "$line"; done <file

6
Quando ho provato tail -f syslog | grep -e something -e somethingelse| while read line; do echo $line; donenon ha funzionato. Funzionava con un file convogliato nel whileloop, funzionava solo con il tail -f, funzionava solo con grep, ma non con entrambe le pipe. Dare la grepl' --line-bufferedopzione ha reso il lavoro

Questo funziona anche quando ogni riga deve essere inviata a stdin:command | while read -r line; do echo "$line" | command ; done
Den

21

Sono d'accordo con Keith, xargs è lo strumento più generale per il lavoro.

Di solito uso un approccio in 3 passaggi.

  • fai le cose di base fino a quando non hai qualcosa con cui ti piacerebbe lavorare
  • preparare la linea con awk in modo che ottenga la sintassi corretta
  • poi lascia che xargs lo esegua, forse con l'aiuto di bash.

Ci sono modi più piccoli e veloci, ma questo funziona quasi sempre.

Un semplice esempio:

ls | 
grep xls | 
awk '{print "MyJavaProg --arg1 42 --arg2 "$1"\0"}' | 
xargs -0 bash -c

le prime 2 righe selezionano alcuni file con cui lavorare, quindi awk prepara una bella stringa con un comando da eseguire e alcuni argomenti e $ 1 è il primo input di colonna dalla pipe. E infine mi assicuro che xargs invia questa stringa a bash che la esegue.

È un po 'eccessivo, ma questa ricetta mi ha aiutato in molti posti poiché è molto flessibile.


6
Nota, xargs -0usa il byte null come separatore di record, quindi la tua dichiarazione di stampa awk dovrebbe essereprintf("MyJavaProg --args \"%s\"\0",$1)
glenn jackman,

@glenn: mancato il carattere nullo, aggiornerà la risposta
Johan

@Johan non è un grosso problema, ma se lo stai usando awkpuoi farlo fare lo schema e saltare l' grep esempio,ls | awk '/xls/ {print...
Eric Renouf,

15

GNU Parallel è fatto per quel tipo di compiti. L'utilizzo più semplice è:

cat stuff | grep pattern | parallel java MyProg

Guarda il video introduttivo per saperne di più: http://www.youtube.com/watch?v=OpaiGYxkSuQ


1
Non c'è davvero bisogno del catqui poiché greppuò leggere direttamente il file
Eric Renouf,


1
Grazie per il link, non sono necessariamente d'accordo sul fatto che sia più facile da leggere, ma bello sapere che è stato considerato indipendentemente. Vorrei solo ora leggermente cavillare che il link in realtà non si applica qui poiché l'alternativa non è in realtà < stuff grep patternma è grep pattern stuffsenza reindirizzamento o cat richiesto affatto. Tuttavia, ciò non cambia materialmente la tua argomentazione e se pensi che sia più chiaro usare sempre le cose in una pipa che inizia con cat, allora il potere per te
Eric Renouf

8

Inoltre, while readavvolgi in un guscio di pesce (suppongo che tu voglia un guscio di pesce, considerando che hai usato etichetta di ).

command | while read line
    command $line
end

Pochi punti da notare.

  • readnon prende -rargomento e non interpreta le tue barre rovesciate, al fine di semplificare il caso d'uso più comune.
  • Non è necessario citare $line, poiché a differenza di bash, il pesce non separa le variabili per spazi.
  • commanddi per sé è un errore di sintassi (per catturare tale uso di argomenti segnaposto). Sostituiscilo con il comando reale.

Non è whilenecessario associarlo a doe doneanziché end?
aff

@aff Questo riguarda in particolare le conchiglie di pesce, che hanno una sintassi diversa.
Konrad Borowski,

Ah, allora questo è ciò che i mezzi di pesce.
aff.

6

Se devi controllare dove esattamente l'argomento di input è inserito nella tua riga di comando o se devi ripeterlo più volte allora devi usare xargs -I{}.

ESEMPIO 1

Creare una struttura di cartelle vuota another_folderche rispecchi le sottocartelle nella directory corrente:

    ls -1d ./*/ | xargs -I{} mkdir another_folder/{}
ESEMPIO # 2

Applicare un'operazione su un elenco di file proveniente da stdin, in questo caso eseguire una copia di ciascun .htmlfile aggiungendo .bakun'estensione:

    find . -iname "*.html" | xargs -I{} cp {} {}.bak

Dalla xargspagina man per MacOS / BSD :

 -I replstr
         Execute utility for each input line, replacing one or more occurrences of
         replstr in up to replacements (or 5 if no -R flag is specified) arguments
         to utility with the entire line of input.  The resulting arguments, after
         replacement is done, will not be allowed to grow beyond 255 bytes; this is
         implemented by concatenating as much of the argument containing replstr as
         possible, to the constructed arguments to utility, up to 255 bytes.  The
         255 byte limit does not apply to arguments to utility which do not contain
         replstr, and furthermore, no replacement will be done on utility itself.
         Implies -x.

xargsPagina man di Linux :

   -I replace-str
          Replace  occurrences of replace-str in the initial-
          arguments with names read from standard input.  Al
          so,  unquoted  blanks do not terminate input items;
          instead the separator  is  the  newline  character.
          Implies -x and -L 1.

1

Quando ho a che fare con input potenzialmente non autorizzati, mi piace vedere l'intero lavoro "spiegato" riga per riga per l'ispezione visiva prima di eseguirlo (soprattutto quando è qualcosa di distruttivo come la pulizia della cassetta postale delle persone).

Quindi quello che faccio è generare un elenco di parametri (ad es. Nomi utente), inviarlo a un file in modo un record per riga, in questo modo:

johndoe  
jamessmith  
janebrown  

Quindi apro l'elenco vime lo manipolo con la ricerca e sostituisco le espressioni fino a quando non ottengo un elenco di comandi completi che devono essere eseguiti, in questo modo:

/bin/rm -fr /home/johndoe  
/bin/rm -fr /home/jamessmith 

In questo modo se la tua regex è incompleta, vedrai in quale comando avrà potenziali problemi (es. /bin/rm -fr johnnyo connor). In questo modo puoi annullare il tuo regex e riprovare con una versione più affidabile di esso. La menomazione dei nomi è nota per questo, perché è difficile occuparsi di tutti i casi limite come Van Gogh, O'Connors, St. Clair, Smith-Wesson.

Avere set hlsearchè utile per farlo in vimquanto evidenzierà tutte le partite, quindi puoi facilmente individuare se non corrisponde, o corrisponde in modo non intenzionale.

Una volta che il tuo regex è perfetto e cattura tutti i casi che puoi testare / pensare, quindi di solito lo converto in un'espressione sed in modo che possa essere completamente automatizzato per un'altra corsa.

Per i casi in cui il numero di righe di input ti impedisce di eseguire un'ispezione visiva, ti consiglio vivamente di fare eco al comando sullo schermo (o meglio ancora, un registro) prima che venga eseguito, quindi se si guasta, sai esattamente quale comando ha causato fallire. Quindi puoi tornare al regex originale e modificarlo di nuovo.


0

Se un programma ignora la pipe ma accetta i file come argomenti, puoi semplicemente puntarlo al file speciale /dev/stdin.

Non ho familiarità con Java, ma ecco un esempio di come lo faresti per Bash:

$ echo $'pwd \n cd / \n pwd' |bash /dev/stdin
/home/rolf
/

$ È necessario affinché bash si traduca \nin newline. Non sono sicuro del perché.



0

Ecco un copypaste che puoi usare immediatamente:

cat list.txt | xargs -I{} command parameter {} parameter

L'elemento dall'elenco verrà posizionato dove si trova {} e il resto del comando e dei parametri verranno utilizzati così come sono.

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.