Jak bezpiecznie „łączyć” linie drukowane przez wiele programów?


11

Załóżmy, że chcę wykonywać wiele programów równolegle i łączyć ich wyniki w jeden potok:

sh -c '
    (echo qqq; echo qqq2; echo qqq3)&
    (echo www; echo www2; echo www3)& 
    (echo eee; echo eee2; echo eee3)& 
  wait; wait; wait'

Takie podejście do powłoki działa dobrze w tym prostym przypadku, ale spodziewam się, że się nie powiedzie, jeśli programy wypiszą więcej i dłuższe linie w buforowany sposób, tak jak to (zbudowane):

qqq
qqwww
q2
qqq3www2

wwweee3

eee2
eee3

Jednym z rozwiązań, które mi zalecono użyć było tail -f:

tail -n +0 -q -f <(echo qqq; echo qqq2; echo qqq3) <(echo www; echo www2; echo www3) <(echo eee; echo eee2; echo eee3)

, ale jest to opcja nieoptymalna: powolnie wysyła dane, nie kończy się; Dane wyjściowe widzę nie w kolejności „uśpienia”, ale w kolejności argumentów w tym przypadku:

tail -n +0 -q -f <(sleep 1; echo qqq; sleep 1; echo qqq2; echo qqq3) <(echo www; echo www2; sleep 10; echo www3) <(echo eee; sleep 4; echo eee2; echo eee3) | cat

Mam wdrożony specjalny mały program, ale wierzę, że powinna istnieć jakaś norma dobrym sposobem, aby to zrobić.

Jak to zrobić przy użyciu standardowych narzędzi (i bez tail -fwad)?


Jak chcesz miksować wyjście? Najwyraźniej chcesz wymieszać dane wyjściowe, ponieważ chcesz „porządek uśpienia” zamiast „porządek argumentów”. Czy wymagasz miksowania wyników, ale nie wierszy, tzn. Aby każda linia była drukowana atomowo?
Gilles 'SO - przestań być zły'

Linewise. Wszystkie wiersze ze wszystkich uruchomionych programów powinny zostać dostarczone wcześnie, ale bez mieszania w obrębie każdego wiersza.
Vi.

Myślę, że standardowy sposób na to się nazywa, no cóż syslog...
Shadur

Czy używanie syslognie jest używane do dzienników, ale do czegoś niestandardowego, który jest w porządku?
Vi.

Nie jest to bardziej idealne niż inne dotychczas opublikowane sugestie, ale pomyślałem, że warto wspomnieć o -sopcji ogona. np. tail -f -s .1 filezmniejszy opóźnienie pętli do .1 sekundy od domyślnej 1 sekundy.
cpugeniusmv

Odpowiedzi:


4

GNU Parallel.

Z informacji o wersji z sierpnia 2013 r .:

--line-bufferbuforuje wyjście na zasadzie liniowej. --grouputrzymuje wyniki razem dla całego zadania. --ungrouppozwala na pomieszanie wyjścia z połową linii pochodzącej z jednego zadania i połową linii pochodzącej z innego zadania. --line-bufferpasuje między tymi dwoma; drukuje pełną linię, ale pozwoli na mieszanie linii różnych zadań.

Na przykład:

parallel --line-buffer <jobs

Gdzie jobszawiera:

./long.sh
./short.sh one
./short.sh two

short.sh:

#!/bin/bash

while true; do
        echo "short line $1"
        sleep .1
done

long.sh:

#!/bin/bash

count=0
while true; do
        echo -n "long line with multiple write()s "
        sleep .1
        count=$((count+1))
        if [ $count -gt 30 ]; then
                count=0
                echo
        fi
done

Wynik:

short line one
short line two
short line one
short line two
short line one
**-snip-**
short line one
short line one
short line two
short line two
short line one
short line one
short line one
long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s long line with multiple write()s 
short line two
short line two
short line two
short line one

1

Rozwiązanie wdrażające blokady:

function putlines () {
   read line || return $?
   while ! ln -s $$ lock >/dev/null 2>&1
   do
      sleep 0.05
   done
   echo "$line" 
}

function getlines () {
     while read lline
     do 
          echo "$lline"
          rm lock
     done
}

# your paralelized jobs  
(  
   job1 | putlines & 
   job2 | putlines & 
   job3 | putlines & 
   wait
) | getlines| final_processing

Powinien istnieć szybszy sposób na utworzenie blokady niż użycie systemu plików.


0

Nie mogę wymyślić niczego prostego, co pomogłoby ci, jeśli twoje linie są tak długie, że jeden program zostanie wysłany do snu, zanim będzie w stanie, aby zakończyć pisanie linii na standardowe wyjście.

Jeśli jednak twoje linie są wystarczająco krótkie, aby napisać je całkowicie przed przełączeniem procesu, a twoim problemem jest to, że generowanie jednej linii trwa bardzo długo, możesz buforować dane wyjściowe za pomocą odczytu.

Na przykład:

((./script1 | while read line1; do echo $line1; done) & \
(./script2 | while read line2; do echo $line2; done)) | doSomethingWithOutput

Nie piękny. Jest mało prawdopodobne, aby był niezawodny. Jest mało prawdopodobne, że wydajność będzie dobra.
Vi.

Masz rację. To nie jest piękne, ale wygląda bardziej jak brudny hack. Jednak nie uważam, że to wystarczy, aby ocenić wydajność i niezawodność. Ponadto chciałeś użyć „standardowych narzędzi”. Nie byłbym więc zaskoczony, gdybyś musiał zaakceptować jakąś brzydotę (w końcu). Ale może ktoś ma bardziej zadowalające rozwiązanie.
xwst

Obecnie jestem zadowolony z mojego programu (link w pytaniu) z tym wyjątkiem, że nie jest dostępny w repozytoriach, dlatego nie można go uznać za nawet „standardowy”. Rozwiązaniem może być próba pchnięcia go tam ...
Vi.

0

Możesz utworzyć nazwany potok za pomocą mkfifo, zrzucić wszystkie dane wyjściowe do nazwanego potoku i osobno odczytać z nazwanego potoku dla zebranych danych:

mkfifo /tmp/mypipe
job1 > /tmp/mypipe &
job2 > /tmp/mypipe &
job3 > /tmp/mypipe &

cat /tmp/mypipe > /path/to/final_output &

wait; wait; wait; wait

2
W jaki sposób to uchroni przed zniekształceniem job1i wypuszczeniem job2długich (> 4096 bajtów) linii? Wydaje się, że jest to nazwane odpowiednik potoku samego pierwszego kodu w pytaniu.
Vi.

Bardzo sprawiedliwy punkt. Nie zastanawiałem się nad wyjściem z dużymi kroplami, mimo że zostało to wyraźnie przywołane w twoim pytaniu. Zastanawiam się teraz, czy może nie ma jakiegoś narzędzia odwrotnego do tego tee, co brzmi dokładnie tak, jak chcesz. Prawdopodobnie spójrz na elementy wewnętrzne sysloglub inne narzędzia do rejestrowania, ponieważ zdecydowanie agregują one dane wyjściowe z kilku miejsc w jeden plik dziennika. Blokowanie może być dobrą odpowiedzią, jak sugerował @emmanual.
DopeGhoti,

0

Stare pytanie, wiem, ale zastanawiałem się nad tym samym i oto, co wymyśliłem:

garbling_job | (
    while read LINE
    do
        echo $LINE
    done
) &

Wydaje mi się, że mogę zacząć sporo z nich, nie martwiąc się o zniekształconą wydajność.

EDYCJA: Jak sugeruje Ole - powinieneś być ostrożny z długimi liniami (> 4k, po szczegóły patrz komentarz Olesa poniżej)

Oto mój program testowy

if [ "$1" = "go" ]
then
for i in 1 2
do
    printf 111112222222222223333
    sleep .01
    printf 3333333444444444444555555555555
    sleep .01
    printf 6666666666666667777
    sleep .01
    printf 777777788888888889999999999999999
    sleep .01
    echo
done
exit
fi

# running them in sequence is all very fine
for i in 1 2 3 4 5 6 7 8
do
    echo bash $0 go 
done

# now this is all garbled up
for i in 1 2 3 4 5 6 7 8
do
    bash $0 go &
done
for i in 1 2 3 4 5 6 7 8; do wait; done

# using cat inbetween does not make it better
for i in 1 2 3 4 5 6 7 8
do
    bash $0 go | cat &
done
for i in 1 2 3 4 5 6 7 8; do wait; done

# it does not help to use stdbuff after the thing that just printfs sporadicall
for i in 1 2 3 4 5 6 7 8
do
    bash $0 go | stdbuf -oL cat &
done
for i in 1 2 3 4 5 6 7 8; do wait; done

# it does not help to use stdbuff before either - or I am not understanding stdbuff
for i in 1 2 3 4 5 6 7 8
do
    stdbuf -o10000 bash $0 go | stdbuf -oL cat &
echo
done
for i in 1 2 3 4 5 6 7 8; do wait; done

# can I read - yes - they are now fine again
for i in 1 2 3 4 5 6 7 8
do
bash $0 go | (
    while read LINE
    do
        echo $LINE
    done
) &
echo
done
for i in 1 2 3 4 5 6 7 8; do wait; done

1
Musisz przetestować linie, które są większe niż rozmiar twojej strony (zwykle 4-8K). Zobacz mywiki.wooledge.org/...
Ole Tange

Nie zgadzam się lekko. Jeśli masz tak różnorodne dane wyjściowe, możesz chcieć zrobić coś innego niż multipleksowanie echa w potoku. W przypadku krótkich wierszy, takich jak staromodne dzienniki i komunikaty o stanie, powyższe wydaje się działać i jest bardzo proste.
user2692263

Jeśli już wiesz, że Twoje rozwiązanie ma ograniczenia, zastanów się nad tym. Moje doświadczenie z ludźmi z systemu UNIX polega na tym, że jeśli zobaczą rozwiązanie, które działa dla n, to zakładają, że zadziała również dla n * 10 - chyba że wyjaśniono, że istnieje granica n. Jeśli potrafisz wyjaśnić, dlaczego istnieje limit n, i jak go podnieść, jest jeszcze lepiej.
Ole Tange
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.