Come posso inviare lo stdout di un processo a più processi utilizzando pipe (preferibilmente senza nome) in Unix (o Windows)?

StackOverflow https://stackoverflow.com/questions/60942

Domanda

Vorrei reindirizzare lo stdout del processo proc1 a due processi proc2 e proc3:

         proc2 -> stdout
       /
 proc1
       \ 
         proc3 -> stdout

ho provato

 proc1 | (proc2 & proc3)

ma non sembra funzionare, ad es.

 echo 123 | (tr 1 a & tr 1 b)

scrive

 b23

a stdout invece di

 a23
 b23
È stato utile?

Soluzione

Nota dell'editore:
- >(…) è un sostituzione del processo cioè un funzionalità di shell non standard Di Alcuni Shell compatibili con POSIX: bash, ksh, zsh.
- Questa risposta invia accidentalmente l'output della sostituzione del processo di output attraverso la pipeline pure: echo 123 | tee >(tr 1 a) | tr 1 b.
- L'output delle sostituzioni del processo sarà interlacciato in modo imprevedibile e, tranne in zsh, la pipeline potrebbe terminare prima dei comandi interni >(…) Fare.

In Unix (o su un Mac), utilizzare il file tee comando:

$ echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
b23
a23

Di solito useresti tee Per reindirizzare l'output a più file, ma usando> (...) è possibile reindirizzare a un altro processo.Quindi, in generale,

$ proc1 | tee >(proc2) ... >(procN-1) >(procN) >/dev/null

farà quello che vuoi.

Sotto Windows, non penso che la shell integrata abbia un equivalente.Quello di Microsoft Windows PowerShell ha un tee comando però.

Altri suggerimenti

Come ha detto dF, bash consente di utilizzare il >(…) costrutto che esegue un comando al posto di un nome file.(C'è anche il <(…) costruire per sostituire il produzione di un altro comando al posto del nome di un file, ma questo ora è irrilevante, lo cito solo per completezza).

Se non hai bash, o stai utilizzando un sistema con una versione precedente di bash, puoi fare manualmente ciò che fa bash, utilizzando i file FIFO.

Il modo generico per ottenere ciò che desideri è:

  • decidi quanti processi dovrebbero ricevere l'output del tuo comando e crea altrettante FIFO, preferibilmente su una cartella temporanea globale:
    subprocesses="a b c d"
    mypid=$$
    for i in $subprocesses # this way we are compatible with all sh-derived shells  
    do
        mkfifo /tmp/pipe.$mypid.$i
    done
  • avvia tutti i tuoi sottoprocessi in attesa di input dalle FIFO:
    for i in $subprocesses
    do
        tr 1 $i </tmp/pipe.$mypid.$i & # background!
    done
  • esegui il tuo comando collegandoti alle FIFO:
    proc1 | tee $(for i in $subprocesses; do echo /tmp/pipe.$mypid.$i; done)
  • infine, rimuovi le FIFO:
    for i in $subprocesses; do rm /tmp/pipe.$mypid.$i; done

NOTA:per ragioni di compatibilità, farei il $(…) con virgolette inverse, ma non ho potuto farlo scrivendo questa risposta (le virgolette inverse sono usate in SO).Normalmente, il $(…) è abbastanza vecchio da funzionare anche nelle vecchie versioni di ksh, ma in caso contrario, allega il file parte tra virgolette.

Unix (bash, ksh, zsh)

La risposta di dF contiene il seme di una risposta basata su tee E produzione sostituzioni di processo
(>(...)) Quello può o non può lavoro, a seconda delle vostre esigenze:

Si noti che le sostituzioni di processo sono a non standard caratteristica che (per lo più) gusci di sola caratteristiche posix come dash (che agisce come /bin/sh su ubuntu, per esempio), do non supporto.Targeting degli script di shell /bin/sh Dovrebbe non fare affidamento su di loro.

echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null

IL insidie di questo approccio sono:

  • comportamento di output imprevedibile e asincrono:l'output fluisce dai comandi all'interno delle sostituzioni del processo di output >(...) intercalare in modi imprevedibili.

  • In bash E ksh (al contrario di zsh - ma vedere l'eccezione di seguito):

    • l'output potrebbe arrivare Dopo il comando è terminato.
    • i comandi successivi potrebbero iniziare l'esecuzione Prima i comandi nelle sostituzioni del processo sono terminati - bash E ksh Fare non attendere il completamento dei processi generati dalla sostituzione del processo di output, almeno per impostazione predefinita.
    • jmb lo spiega bene in un commento alla risposta di dF.:

tieni presente che i comandi sono iniziati all'interno >(...) sono dissociati dalla shell originale e non puoi determinare facilmente quando finiscono;IL tee terminerà dopo aver scritto tutto, ma i processi sostituiti continueranno a consumare i dati dai vari buffer nel kernel e dall'I/O dei file, oltre al tempo impiegato dalla gestione interna dei dati.Puoi incontrare condizioni di competizione se il tuo involucro esterno continua a fare affidamento su qualsiasi cosa prodotta dai sottoprocessi.

  • zsh è l'unica shell che fa per impostazione predefinita, attende il completamento dei processi eseguiti nelle sostituzioni dei processi di output, tranne se è stderr che viene reindirizzato a uno (2> >(...)).

  • ksh (almeno dalla versione 93u+) consente l'uso senza argomenti wait per attendere il completamento dei processi generati dalla sostituzione del processo di output.
    Tieni presente che in una sessione interattiva ciò potrebbe comportare l'attesa di eventuali in sospeso lavori in sottofondo anche, però.

  • bash v4.4+ può aspettare il più recentemente ha avviato la sostituzione del processo di output con wait $!, ma senza argomenti wait fa non funzionare, rendendolo inadatto per un comando con multiplo sostituzioni del processo di output.

  • Tuttavia, bash E ksh può essere costretto aspettare reindirizzando il comando a | cat, ma tieni presente che questo fa sì che il comando venga eseguito in a sottoguscio. Avvertenze:

    • ksh (come di ksh 93u+) non supporta l'invio stderr ad una sostituzione del processo di output (2> >(...));un tale tentativo è silenziosamente ignorato.

    • Mentre zsh è (lodevolmente) sincrono per impostazione predefinita con il (molto più comune) stdout sostituzioni del processo di output, anche il | cat la tecnica non può renderli sincroni con stderr sostituzioni del processo di output (2> >(...)).

  • Tuttavia, anche se lo assicuri esecuzione sincrona, il problema di output interlacciato in modo imprevedibile resti.

Il seguente comando, una volta eseguito bash O ksh, illustra i comportamenti problematici (potresti doverlo eseguire più volte per vedere Entrambi sintomi):IL AFTER in genere verrà stampato Prima l'output delle sostituzioni di output e l'output di queste ultime può essere interlacciato in modo imprevedibile.

printf 'line %s\n' {1..30} | tee >(cat -n) >(cat -n) >/dev/null; echo AFTER

In breve:

  • Garantire una particolare sequenza di output per comando:

    • Nessuno dei due bashkshzsh supportarlo.
  • Esecuzione sincrona:

    • Fattibile, tranne che con stderr-sostituzioni del processo di output di origine:
      • In zsh, Loro sono invariabilmente asincrono.
      • In ksh, Essi non funzionano affatto.

Se riesci a convivere con queste limitazioni, l'utilizzo delle sostituzioni del processo di output è un'opzione praticabile (ad esempio, se tutti scrivono su file di output separati).


Notare che tzot è la soluzione molto più ingombrante, ma potenzialmente compatibile con POSIX mostra anche un comportamento di output imprevedibile;tuttavia, utilizzando wait puoi assicurarti che i comandi successivi non inizino l'esecuzione fino al termine di tutti i processi in background.
Vedi in basso per un'implementazione più robusta, sincrona e con output serializzato.


L'unica semplice bash soluzione con un comportamento di output prevedibile è il seguente, che, tuttavia, è proibitivamente lento con grandi set di input, perché i cicli della shell sono intrinsecamente lenti.
Tieni inoltre presente che questo si alterna le righe di output dai comandi di destinazione.

while IFS= read -r line; do 
  tr 1 a <<<"$line"
  tr 1 b <<<"$line"
done < <(echo '123')

Unix (usando GNU Parallel)

Installazione GNU parallel consente a soluzione robusta con output serializzato (per comando). ciò consente inoltre esecuzione parallela:

$ echo '123' | parallel --pipe --tee {} ::: 'tr 1 a' 'tr 1 b'
a23
b23

parallel per impostazione predefinita garantisce che l'output dei diversi comandi non venga intercalato (questo comportamento può essere modificato - vedere man parallel).

Nota:Alcune distribuzioni Linux sono dotate di a diverso parallel utility, che non funzionerà con il comando sopra;utilizzo parallel --version per determinare quale, se presente, hai.


finestre

La risposta utile di Jay Bazuzi mostra come farlo PowerShell.Detto ciò:la sua risposta è l'analogo del looping bash risposta sopra, lo sarà proibitivamente lento con grandi set di input e anche si alterna le righe di output dai comandi di destinazione.



bashSoluzione Unix basata su PC, ma per il resto portabile, con esecuzione sincrona e serializzazione dell'output

Quella che segue è un'implementazione semplice, ma ragionevolmente solida dell'approccio presentato in la risposta di tzot che prevede inoltre:

  • esecuzione sincrona
  • output serializzato (raggruppato).

Sebbene non sia strettamente conforme a POSIX, poiché è un file bash sceneggiatura, dovrebbe essere portabile su qualsiasi piattaforma Unix che abbia bash.

Nota:Puoi trovare un'implementazione più completa rilasciata sotto la licenza MIT in questo succo.

Se salvi il codice qui sotto come script fanout, rendilo eseguibile e inserisci il tuo file PATH, il comando dalla domanda funzionerebbe come segue:

$ echo 123 | fanout 'tr 1 a' 'tr 1 b'
# tr 1 a
a23
# tr 1 b
b23

fanout codice sorgente dello script:

#!/usr/bin/env bash

# The commands to pipe to, passed as a single string each.
aCmds=( "$@" )

# Create a temp. directory to hold all FIFOs and captured output.
tmpDir="${TMPDIR:-/tmp}/$kTHIS_NAME-$$-$(date +%s)-$RANDOM"
mkdir "$tmpDir" || exit
# Set up a trap that automatically removes the temp dir. when this script
# exits.
trap 'rm -rf "$tmpDir"' EXIT 

# Determine the number padding for the sequential FIFO / output-capture names, 
# so that *alphabetic* sorting, as done by *globbing* is equivalent to
# *numerical* sorting.
maxNdx=$(( $# - 1 ))
fmtString="%0${#maxNdx}d"

# Create the FIFO and output-capture filename arrays
aFifos=() aOutFiles=()
for (( i = 0; i <= maxNdx; ++i )); do
  printf -v suffix "$fmtString" $i
  aFifos[i]="$tmpDir/fifo-$suffix"
  aOutFiles[i]="$tmpDir/out-$suffix"
done

# Create the FIFOs.
mkfifo "${aFifos[@]}" || exit

# Start all commands in the background, each reading from a dedicated FIFO.
for (( i = 0; i <= maxNdx; ++i )); do
  fifo=${aFifos[i]}
  outFile=${aOutFiles[i]}
  cmd=${aCmds[i]}
  printf '# %s\n' "$cmd" > "$outFile"
  eval "$cmd" < "$fifo" >> "$outFile" &
done

# Now tee stdin to all FIFOs.
tee "${aFifos[@]}" >/dev/null || exit

# Wait for all background processes to finish.
wait

# Print all captured stdout output, grouped by target command, in sequences.
cat "${aOutFiles[@]}"

Da @dF:menzionato che PowerShell ha tee, ho pensato di mostrare un modo per farlo in PowerShell.

PS > "123" | % { 
    $_.Replace( "1", "a"), 
    $_.Replace( "2", "b" ) 
}

a23
1b3

Tieni presente che ogni oggetto proveniente dal primo comando viene elaborato prima della creazione dell'oggetto successivo.Ciò può consentire il ridimensionamento di input molto grandi.

un altro modo per farlo sarebbe:

 eval `echo '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp'`

produzione:

a23
b23

non è necessario creare una subshell qui

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top