komunikacja między wieloma procesami

13

Mam skrypt bash, który uruchamia funkcję manager () jako osobny proces dla x-razy. Jak można przekazywać wiadomości do wszystkich procesów menedżera () z poziomu skryptu?

Czytałem o anonimowych potokach, ale nie mam pojęcia, jak udostępniać im wiadomości. Próbowałem to zrobić z nazwanymi potokami, ale wygląda na to, że musiałbym utworzyć osobny potok nazwany dla każdego procesu?

Jaki jest najbardziej elegancki sposób to zrobić?

Oto mój kod do tej pory:

#!/bin/bash

manager () {
    while :
    do
        echo "read what has been passed to \$line"
    done
}

x=1
while [ $x -le 5 ]
do
  manager x &
  x=$(( $x + 1 ))
done


while :
do
while read line
do
  echo "What has been passed through the pipe is ${line}"
  # should pass $line to every manager process

done < $1
done

exit 0
Aljaz
źródło

Odpowiedzi:

25

Terminem, który próbujesz osiągnąć, jest multipleksowanie .

Można to zrobić dość łatwo w bash, ale wymaga to bardziej zaawansowanych funkcji bash.

Stworzyłem skrypt oparty na twoim, który moim zdaniem robi to, co próbujesz osiągnąć. Wyjaśnię to poniżej.

#!/bin/bash
manager() {
  while IFS= read -r line; do
    echo "manager[$1:$BASHPID]: $line"
  done
}

fds=()
for (( i=0; i<5; i++ )); do
  exec {fd}> >(manager $i)
  fds+=( $fd )
done

while IFS= read -r line; do
  echo "master: $line"
  for fd in "${fds[@]}"; do
    printf -- '%s\n' "$line" >&$fd
  done
done

managerto funkcja bash, która po prostu odczytuje ze STDIN i zapisuje swój identyfikator i linię do STDOUT. Używamy $BASHPIDzamiast, $$ponieważ $$nie jest aktualizowany dla podpowłoki (co będziemy używać do uruchomienia manager.

fdsto tablica, która będzie przechowywać deskryptory plików wskazujące na potoki STDIN różnych managers spawnowanych.
Następnie wykonujemy pętlę i tworzymy 5 procesów menedżera. Używam for (( ))składni zamiast tego, jak to robiłeś, ponieważ jest czystsza. Jest to specyficzne dla bash, ale kilka rzeczy, które robi ten skrypt, jest specyficzne dla bash, więc równie dobrze może pójść na całość.
 

Następnie przechodzimy do exec {fd}> >(manager $i). Robi to jeszcze kilka rzeczy związanych z bash.
Pierwszy z nich to {fd}>. Spowoduje to pobranie następnego dostępnego deskryptora pliku na numer 10 lub później, otwarcie potoku ze stroną zapisującą potoku przypisaną do tego deskryptora pliku i przypisanie numeru deskryptora pliku do zmiennej $fd.

Do >(manager $i)premiery manager $ii zasadniczo substytuty >(manager $i)o ścieżkę do standardowego wejścia tego procesu. Więc jeśli managerzostał uruchomiony jako PID 1234, >(manager $i)może zostać zastąpiony /proc/1234/fd/0(to zależy od systemu operacyjnego).

Zakładając, że następnym dostępnym numerem deskryptora pliku jest 10, a menedżer jest uruchamiany z PID 1234, polecenie w exec {fd}> >(manager $i)zasadzie staje się exec 10>/proc/1234/fd/0, a bash ma teraz deskryptor pliku wskazujący STDIN tego menedżera.
Następnie, ponieważ bash wstawia numer tego deskryptora pliku $fd, dodajemy ten deskryptor do tablicy w fdscelu późniejszego użycia.
 

Reszta jest dość prosta. Master odczytuje wiersz ze STDIN, iteruje wszystkie deskryptory plików $fdsi wysyła wiersz do tego desciptor pliku ( printf ... >&$fd).

 

Wynik wygląda następująco:

$ /tmp/test.sh
hello
master: hello
manager[0:8876]: hello
manager[1:8877]: hello
manager[4:8880]: hello
manager[2:8878]: hello
manager[3:8879]: hello
world
master: world
manager[0:8876]: world
manager[1:8877]: world
manager[3:8879]: world
manager[2:8878]: world
manager[4:8880]: world

Gdzie wpisałem helloi world.

Patrick
źródło
@Patrick jest działa, ale masz literówkę {fd} i powinna ona wynosić $ {fd}
c4f4t0r
3
@ c4f4t0r To nie jest literówka
Patrick
@Patrick on suse 11 "bash type.bash type.bash: line 10: exec: {fd}: not found" Zmieniłem na exec $ {fd} i tak to działa
c4f4t0r
2
@ c4f4t0r Wersja bash w open suse 11 jest dość stara (3.2). Ta funkcja została zaimplementowana w bash 4.0.
Patrick
Dzięki za wiele dobrych informacji! Nitpick: Rozumiem, dlaczego powiedziałbyś echo -- "$line"lub printf "%s\n" "$line"- ale dlaczego miałbyś użyć, --gdy następny argument jest zakodowany na stałe (i nie zaczyna się od -)?
Scott
0

teei bash:

cat foo | tee >(manager) >(manager) >(manager) >(manager) >(manager) >/dev/null

Jeśli liczba menedżerów powinna być konfigurowalna lub jeśli nie chcesz mieszać wyników różnych menedżerów:

export -f manager
cat foo | parallel --pipe --tee manager ::: {1..10}
Ole Tange
źródło