Come posso inviare stdout a più comandi?


186

Ci sono alcuni comandi che filtrano o agiscono sull'input, e poi lo passano come output, penso di solito a stdout- ma alcuni comandi prenderanno semplicemente stdine faranno tutto ciò che fanno con esso, e non produrranno nulla.

Conosco molto bene OS X e quindi ce ne sono due che mi vengono subito in mente pbcopye pbpasteche sono mezzi per accedere agli Appunti di sistema.

Comunque, so che se voglio prendere stdout e sputare l'output per andare su entrambi stdoute su un file, allora posso usare il teecomando. E ne so un po ' xargs, ma non credo sia quello che sto cercando.

Voglio sapere come posso dividere stdoutper passare tra due (o più) comandi. Per esempio:

cat file.txt | stdout-split -c1 pbcopy -c2 grep -i errors

Probabilmente c'è un esempio migliore di quello, ma sono davvero interessato a sapere come posso inviare stdout a un comando che non lo inoltra e pur continuando stdouta non essere "disattivato" - non sto chiedendo come fare catun file e grepparte di esso e copiarlo negli Appunti: i comandi specifici non sono così importanti.

Inoltre - non sto chiedendo come inviarlo a un file e stdout- questa potrebbe essere una domanda "duplicata" (scusate) ma ho fatto un po 'di ricerca e ho trovato solo quelli simili che chiedevano come dividere tra stdout e un file - e le risposte a quelle domande sembravano essere tee, che non credo funzioneranno per me.

Infine, potresti chiedere "perché non rendere pbcopy l'ultima cosa nella catena di tubi?" e la mia risposta è 1) cosa succede se voglio usarlo e vedere ancora l'output nella console? 2) cosa succede se voglio usare due comandi che non vengono emessi stdoutdopo che hanno elaborato l'input?

Oh, e un'altra cosa - mi rendo conto di poter usare teee un nome pipe ( mkfifo) ma speravo in un modo che potesse essere fatto in linea, in modo conciso, senza una configurazione precedente :)


Risposte:


240

È possibile utilizzare teeed elaborare la sostituzione per questo:

cat file.txt | tee >(pbcopy) | grep errors

Questo invierà tutto l'output di cat file.txtto pbcopye otterrai solo il risultato grepsulla tua console.

È possibile inserire più processi nella teeparte:

cat file.txt | tee >(pbcopy) >(do_stuff) >(do_more_stuff) | grep errors

21
Non è un problema pbcopy, ma vale la pena ricordare in generale: qualunque sia l'output di sostituzione del processo è visto anche dal successivo segmento di pipe, dopo l'input originale; ad es .: seq 3 | tee >(cat -n) | cat -e( cat -nnumera le linee di input, cat -esegna le nuove righe $; vedrai che cat -eviene applicato sia all'input originale (prima) che (poi) all'output cat -n). L'output da più sostituzioni di processo arriverà in ordine non deterministico.
mklement0

49
L' >(unico funziona bash. Se provi che usando ad esempio shnon funzionerà. È importante dare questo avviso.
AAlvz,

10
@AAlvz: buon punto: la sostituzione del processo non è una funzione POSIX; dash, che agisce come shsu Ubuntu, non lo supporta e persino Bash stesso disattiva la funzione quando viene invocata come sho quando set -o posixè in vigore. Tuttavia, non è solo Bash a supportare le sostituzioni di processo: kshe zshsupportarle anche (non sono sicuro degli altri).
mklement0

2
@ mklement0 che non sembra essere vero. Su zsh (Ubuntu 14.04) la tua linea stampa: 1 1 2 2 3 3 1 $ 2 $ 3 $ Il che è triste, perché volevo davvero che la funzionalità fosse come dici tu.
Aktau,

2
@Aktau: In effetti, il mio comando di esempio funziona solo come descritto in bashe ksh- zshapparentemente non invia output dalle sostituzioni del processo di output attraverso la pipeline (probabilmente, è preferibile , perché non inquina ciò che viene inviato al segmento successivo della pipeline - sebbene stampa ancora ). In tutte le shell menzionate, tuttavia, in genere non è una buona idea disporre di una singola pipeline in cui si mescolano output stdout e output delle sostituzioni di processo regolari: l'ordinamento dell'output non sarà prevedibile, in modo che possa emergere solo di rado o con grandi set di dati di output.
mklement0

124

È possibile specificare più nomi di file teee, inoltre, l'output standard può essere reindirizzato in un solo comando. Per inviare l'output a più comandi, è necessario creare più pipe e specificarne ognuna come output di tee. Esistono diversi modi per farlo.

Sostituzione del processo

Se la tua shell è ksh93, bash o zsh, puoi usare la sostituzione di processo. Questo è un modo per passare una pipe a un comando che prevede un nome file. La shell crea la pipe e passa un nome di file simile /dev/fd/3al comando. Il numero è il descrittore di file a cui è connessa la pipe. Alcune varianti di unix non supportano /dev/fd; su questi viene invece utilizzata una pipa denominata (vedi sotto).

tee >(command1) >(command2) | command3

Descrittori di file

In qualsiasi shell POSIX, è possibile utilizzare esplicitamente più descrittori di file . Ciò richiede una variante unix che supporti /dev/fd, poiché tutti gli output tranne uno teedevono essere specificati per nome.

{ { { tee /dev/fd/3 /dev/fd/4 | command1 >&9;
    } 3>&1 | command2 >&9;
  } 4>&1 | command3 >&9;
} 9>&1

Pipe nominate

Il metodo più semplice e portatile è usare le pipe con nome . Il rovescio della medaglia è che è necessario trovare una directory scrivibile, creare le pipe e ripulire in seguito.

tmp_dir=$(mktemp -d)
mkfifo "$tmp_dir/f1" "$tmp_dir/f2"
command1 <"$tmp_dir/f1" & pid1=$!
command2 <"$tmp_dir/f2" & pid2=$!
tee "$tmp_dir/f1" "$tmp_dir/f2" | command3
rm -rf "$tmp_dir"
wait $pid1 $pid2

10
Grazie mille per aver fornito le due versioni alternative per coloro che non vogliono fare affidamento su bash o un certo ksh.
tr

tee "$tmp_dir/f1" "$tmp_dir/f2" | command3dovrebbe sicuramente essere command3 | tee "$tmp_dir/f1" "$tmp_dir/f2", come vuoi stdout di command3convogliato tee, no? Ho testato la tua versione sotto dashe teeblocchi in attesa indefinitamente di input, ma il cambio dell'ordine ha prodotto il risultato atteso.
Adrian Günter,

1
@ AdrianGünter No. Tutti tre esempi leggere dati standard input e inviarlo a ciascuna command, command2e command3.
Gilles

@Gilles vedo, ho frainteso l'intento e ho provato a usare lo snippet in modo errato. Grazie per il chiarimento!
Adrian Günter,

Se non hai alcun controllo sulla shell utilizzata, ma puoi usare bash esplicitamente, puoi farlo <command> | bash -c 'tee >(command1) >(command2) | command3'. Mi ha aiutato nel mio caso.
gc5,

16

Gioca con la sostituzione del processo.

mycommand_exec |tee >(grep ook > ook.txt) >(grep eek > eek.txt)

grepsono due binari che hanno lo stesso output del mycommand_execloro input specifico del processo.


16

Se lo stai usando zsh, puoi sfruttare la potenza della MULTIOSfunzione, ovvero eliminare teecompletamente il comando:

uname >file1 >file2

scriverà semplicemente l'output di unamein due file diversi: file1e file2, cosa equivale auname | tee file1 >file2

Allo stesso modo reindirizzamento di input standard

wc -l <file1 <file2

è equivalente a cat file1 file2 | wc -l(si noti che questo non è lo stesso wc -l file1 file2, il successivo conta il numero di righe in ciascun file separatamente).

Ovviamente puoi anche usare MULTIOSper reindirizzare l'output non su file ma su altri processi, usando la sostituzione dei processi, ad esempio:

echo abc > >(grep -o a) > >(tr b x) > >(sed 's/c/y/')

3
Buono a sapersi. MULTIOSè un'opzione attivata per impostazione predefinita (e può essere disattivata con unsetopt MULTIOS).
mklement0,

6

Per un output ragionevolmente piccolo prodotto da un comando, possiamo reindirizzare l'output su un file temporaneo e inviare tali file temporanei ai comandi in loop. Questo può essere utile quando l'ordine dei comandi eseguiti può essere importante.

Il seguente script, ad esempio, potrebbe farlo:

#!/bin/sh

temp=$( mktemp )
cat /dev/stdin > "$temp"

for arg
do
    eval "$arg" < "$temp"
done
rm "$temp"

Test eseguito su Ubuntu 16.04 con /bin/shcome dashshell:

$ cat /etc/passwd | ./multiple_pipes.sh  'wc -l'  'grep "root"'                                                          
48
root:x:0:0:root:/root:/bin/bash

5

Cattura il comando STDOUTsu una variabile e riutilizzalo tutte le volte che vuoi:

commandoutput="$(command-to-run)"
echo "$commandoutput" | grep -i errors
echo "$commandoutput" | pbcopy

Se è necessario acquisire STDERRanche, quindi utilizzare 2>&1alla fine del comando, in questo modo:

commandoutput="$(command-to-run 2>&1)"

3
Dove sono archiviate le variabili? Se avessi a che fare con un file di grandi dimensioni o qualcosa del genere, questo non richiederebbe molta memoria? Le variabili hanno dimensioni limitate?
Cwd

1
cosa succede se $ commandoutput è enorme ?, è meglio usare i tubi ed elaborare la sostituzione.
Nikhil Mulley

4
Ovviamente questa soluzione è possibile solo quando sai che le dimensioni dell'output si adatteranno facilmente alla memoria e sei a posto nel buffering dell'intero output prima di eseguire i comandi successivi su di esso. I tubi risolvono questi due problemi consentendo dati di lunghezza arbitraria e trasmettendoli in streaming in tempo reale al ricevitore mentre viene generato.
trr

2
Questa è una buona soluzione se hai un output piccolo e sai che l'output sarà testuale e non binario. (le variabili della shell spesso non sono binarie sicure)
Rucent88

1
Non riesco a farlo funzionare con i dati binari. Penso che sia qualcosa con l'eco che cerca di interpretare byte nulli o altri dati non caratteristici.
Rolf,


0

Ecco una soluzione parziale rapida e sporca, compatibile con qualsiasi shell inclusa busybox.

Il problema più stretto che risolve è: stampare il completo stdoutsu una console e filtrarlo su un'altra, senza file temporanei o named pipe.

  • Avvia un'altra sessione sullo stesso host. Per scoprire il nome TTY, digitare tty. Supponiamo /dev/pty/2.
  • Nella prima sessione, esegui the_program | tee /dev/pty/2 | grep ImportantLog:

Si ottiene un registro completo e uno filtrato.

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.