it-swarm.it

Eseguire un comando una volta per riga di input convogliato?

Voglio eseguire un Java una volta per ogni corrispondenza di ls | grep pattern -. In questo caso, penso di poter 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.)

176
Xodarap

Questo è ciò che fa xargs.

... | xargs command
98
Keith

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

cat file... | xargs -n1 command

Oppure, per un singolo file di input è possibile evitare la pipe interamente da cat e andare semplicemente con:

<file xargs -n1 command
193

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

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

read -r legge una riga singola dall'input standard (read senza -r interpreta 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
117
Steven D

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 stringa di Nizza 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.

22
Johan

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

15
Ole Tange

Anche, while read loop in fish Shell (suppongo che tu voglia fish Shell, considerando che hai usato fish tag).

command | while read line
    command $line
end

Pochi punti da notare.

  • read non accetta -r argomento e non interpreta le tue barre rovesciate, al fine di semplificare il caso d'uso più comune.
  • Non è necessario citare $line, a differenza di bash, i pesci non separano le variabili per spazi.
  • command di per sé è un errore di sintassi (per catturare tale uso di argomenti segnaposto). Sostituiscilo con il comando reale.
10
Konrad Borowski

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 (es. Nomi utente), inserirli in un file in modo un record per riga, come questo:

johndoe  
jamessmith  
janebrown  

Quindi apro l'elenco in vim e lo sfoglio 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 il tuo regex è incompleto, 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 Edge come Van Gogh, O'Connors, St. Clair, Smith-Wesson.

Avere set hlsearch è utile per farlo in vim, poiché metterà in evidenza tutte le partite, quindi puoi facilmente individuare se non corrisponde, o partite 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.

1
Marcin

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.

1
DustWolf

Questo è possibile con xargs, come indicato da altre risposte. Dobbiamo distinguere due specifiche nella parte "una volta per riga" della domanda:

  1. Una volta: usa -n 1, questo assicura che il comando sia invocato esattamente una volta per ogni argomento. Tuttavia, per impostazione predefinita, xargs presuppone che gli argomenti siano delimitati da spazi - questo si interromperà quando i file contengono spazi.
  2. Per riga: usa -d '\n' o preelaborare l'input con tr '\n' '\0' e usa -0. Questo rende il comando robusto rispetto agli spazi nell'input.

La riga di comando finale diventa quindi:

.... | xargs -n 1 -d '\n' <command>

oppure (con tr)

.... | tr '\n' '\0' | xargs -n 1 -0 <command>

Se il tuo comando è in grado di elaborare più argomenti contemporaneamente (come grep o sed), puoi omettere -n 1 per accelerare l'operazione in molti casi.

0
krlmlr

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 traduca \n in nuove righe. Non sono sicuro del perché.

0
Rolf

Preferisco questo: consentire comandi a più righe e cancellare il codice

find -type f -name filenam-pattern* | while read -r F
do
  echo $F
  cat $F | grep 'some text'
done

rif https://stackoverflow.com/a/3891678/248616

0
Nam G VU