Jeśli wywołam jakieś polecenie, na przykład echo
mogę użyć wyników tego polecenia w kilku innych poleceniach za pomocą tee
. Przykład:
echo "Hello world!" | tee >(command1) >(command2) >(command3)
Z catem mogę zebrać wyniki kilku poleceń. Przykład:
cat <(command1) <(command2) <(command3)
Chciałbym móc wykonywać obie rzeczy jednocześnie, aby móc tee
wywoływać te polecenia na wyjściu czegoś innego (na przykład echo
napisałem), a następnie zebrać wszystkie ich wyniki na jednym wyjściu za pomocą cat
.
Ważne jest, aby zachować wyniki w porządku, oznacza to, że linie na wyjściu command1
, command2
i command3
nie powinny być ze sobą powiązane, ale zamawiać polecenia są (jak to się dzieje z cat
).
Nie może być lepsze opcje niż cat
i tee
ale są to te, które znam tak daleko.
Chcę uniknąć używania plików tymczasowych, ponieważ rozmiar danych wejściowych i wyjściowych może być duży.
Jak mogłem to zrobić?
PD: innym problemem jest to, że dzieje się to w pętli, co utrudnia obsługę plików tymczasowych. To jest obecny kod, który mam i działa on na małe przypadki testowe, ale tworzy nieskończone pętle podczas odczytu i zapisu z pliku pomocniczego w sposób, którego nie rozumiem.
somefunction()
{
if [ $1 -eq 1 ]
then
echo "Hello world!"
else
somefunction $(( $1 - 1 )) > auxfile
cat <(command1 < auxfile) \
<(command2 < auxfile) \
<(command3 < auxfile)
fi
}
Odczyty i zapisy w pliku pomocniczym wydają się nakładać na siebie, powodując, że wszystko eksploduje.
źródło
echo HelloWorld > file; (command1<file;command2<file;command3<file)
albo na wyjściuecho | tee cmd1 cmd2 cmd3; cat cmd1-output cmd2-output cmd3-output
. Tak to działa - tee może rozwidlać dane wejściowe tylko wtedy, gdy wszystkie polecenia działają i przetwarzają równolegle. jeśli jeden śpi Command (bo nie chcesz przeplatanie) będzie po prostu blokować wszystkie polecenia, tak aby zapobiec zapełnieniu pamięci z wejściem ...Odpowiedzi:
Można użyć kombinacji GNU stdbuf i
pee
od moreutils :sika
popen(3)
te 3 wiersze poleceń powłoki, a następniefread
s dane wejściowe ifwrite
s to wszystkie trzy, które będą buforowane do 1M.Chodzi o to, aby bufor był co najmniej tak duży jak dane wejściowe. W ten sposób, mimo że trzy polecenia są uruchamiane w tym samym czasie, będą widzieć wejście przychodzące tylko wtedy, gdy
pee
pclose
trzy polecenia będą kolejno.Po każdej
pclose
,pee
opróżnia bufor do polecenia i czeka na jego zakończenie. To gwarantuje, że dopóki tecmdx
polecenia nie zaczną wypisywać niczego, zanim nie otrzymają żadnych danych wejściowych (i nie rozwidlają procesu, który może kontynuować wysyłanie po powrocie ich rodzica), dane wyjściowe trzech poleceń nie będą przeplatane.W rzeczywistości przypomina to użycie pliku tymczasowego w pamięci, z tą wadą, że 3 polecenia są uruchamiane jednocześnie.
Aby uniknąć jednoczesnego uruchamiania poleceń, możesz napisać
pee
jako funkcję powłoki:Ale uwaga, że powłoki inne niż
zsh
zawiodłyby dla wejścia binarnego ze znakami NUL.Pozwala to uniknąć używania plików tymczasowych, ale oznacza to, że całe wejście jest przechowywane w pamięci.
W każdym razie będziesz musiał gdzieś zapisać dane wejściowe, w pamięci lub pliku tymczasowym.
W rzeczywistości jest to dość interesujące pytanie, ponieważ pokazuje limit idei uniksowej polegającej na współpracy kilku prostych narzędzi w jednym zadaniu.
W tym miejscu chcielibyśmy mieć kilka narzędzi współpracujących z zadaniem:
echo
)tee
)cmd1
,cmd2
,cmd3
)cat
).Byłoby miło, gdyby wszyscy mogli pracować razem w tym samym czasie i ciężko pracować na danych, które mają przetwarzać, gdy tylko będą dostępne.
W przypadku jednego polecenia filtru jest to łatwe:
Wszystkie polecenia są uruchamiane jednocześnie,
cmd1
zaczynają munchować dane,src
gdy tylko będą dostępne.Teraz, dzięki trzem poleceniom filtrowania, nadal możemy zrobić to samo: uruchomić je jednocześnie i połączyć za pomocą potoków:
Co możemy stosunkowo łatwo zrobić za pomocą nazwanych potoków :
(powyżej
} 3<&0
to obejść, że&
przekierowaniastdin
z/dev/null
, i użyć<>
w celu uniknięcia otwarcia rury do bloku aż do drugiego końca (cat
) jest otwarty, a)Lub, aby uniknąć nazwanych potoków, nieco bardziej boleśnie z
zsh
coproc:Teraz pytanie brzmi: kiedy wszystkie programy zostaną uruchomione i połączone, czy dane przepłyną?
Mamy dwa przeciwwskazania:
tee
przesyła wszystkie swoje dane wyjściowe z tą samą prędkością, dzięki czemu może wysyłać dane tylko z prędkością najwolniejszej rury wyjściowej.cat
zacznie czytać od drugiej rury (rura 6 na powyższym rysunku) dopiero po odczytaniu wszystkich danych z pierwszej (5).Oznacza to, że dane nie będą płynąć w rurze 6, dopóki
cmd1
nie zostaną zakończone. I, podobnie jak wtr b B
powyższym przypadku, może to oznaczać, że dane również nie będą płynąć w rurze 3, co oznacza, że nie będzie płynąć w żadnej z rur 2, 3 lub 4, ponieważtee
przesyła je najwolniej ze wszystkich 3.W praktyce rury te mają niepustą wielkość, więc niektóre dane zdołają się przedostać, a przynajmniej w moim systemie mogę sprawić, aby działał do:
Poza tym z
Mamy impas, w którym znajdujemy się w takiej sytuacji:
Wypełniliśmy rury 3 i 6 (64 kB każda).
tee
przeczytał ten dodatkowy bajt, nakarmił gocmd1
, alecmd2
jego opróżnieniecmd2
nie można go opróżnić, ponieważ jest zablokowany zapis na potoku 6, czekając nacat
jego opróżnieniecat
nie można go opróżnić, ponieważ czeka, aż nie będzie już żadnych danych wejściowych w rurze 5.cmd1
nie mogę powiedzieć,cat
że nie ma już danych wejściowych, ponieważ sam oczekuje na więcej danych wejściowychtee
.tee
nie mogę powiedzieć,cmd1
że nie ma już danych wejściowych, ponieważ jest zablokowany ... i tak dalej.Mamy pętlę zależności, a zatem impas.
Jakie jest rozwiązanie? Zrobiłyby to większe rury 3 i 4 (wystarczająco duże, aby pomieścić całość danych
src
wyjściowych). Możemy to zrobić na przykład, wstawiającpv -qB 1G
pomiędzytee
icmd2/3
gdziepv
można przechowywać do 1G danych oczekującychcmd2
icmd3
odczytujących je. Oznaczałoby to dwie rzeczy:cmd2
w rzeczywistości zacząłby przetwarzać dane dopiero po zakończeniu cmd1.Rozwiązaniem drugiego problemu byłoby zwiększenie również rur 6 i 7. Zakładając to
cmd2
icmd3
wytwarzając tyle danych, ile zużywają, nie zużyłoby to więcej pamięci.Jedynym sposobem uniknięcia duplikowania danych (w pierwszym problemie) byłoby zaimplementowanie zatrzymywania danych w samym dyspozytorze, czyli wprowadzenie wariantu,
tee
który może przesyłać dane z prędkością najszybszego wyjścia (przechowywanie danych w celu dostarczenia wolniejsze we własnym tempie). Niezbyt trywialne.Ostatecznie najlepsze, co możemy rozsądnie uzyskać bez programowania, to prawdopodobnie coś w rodzaju (składnia Zsh):
źródło
+1
do ładnej sztuki ASCII :-)To, co zaproponujesz, nie może być łatwo wykonane za pomocą żadnego istniejącego polecenia i i tak nie ma większego sensu. Cała koncepcja potoków (
|
w systemach Unix / Linux) polega na tym,cmd1 | cmd2
żecmd1
zapisuje dane wyjściowe (co najwyżej), aż bufor pamięci się zapełni, a następniecmd2
uruchamia odczyt danych z bufora (co najwyżej), dopóki nie będzie pusty. To znaczy ,cmd1
icmd2
działając jednocześnie, nigdy nie jest potrzebne, aby między nimi znajdowała się więcej niż ograniczona ilość danych. Jeśli chcesz podłączyć kilka wejść do jednego wyjścia, jeśli jeden z czytników pozostaje w tyle za innymi, możesz zatrzymać inne (jaki jest sens równoległego uruchamiania?) Lub ukryć wyjście, którego laggard jeszcze nie odczytał (po co więc nie mieć pliku pośredniego?). bardziej złożony.W ciągu prawie 30 lat doświadczenia w Uniksie nie pamiętam żadnej sytuacji, która naprawdę przyniosłaby korzyści w przypadku potoku z wieloma wyjściami.
Można łączyć wiele wyjść do jednego strumienia dzisiaj, ale nie w każdym przeplatanego sposób (jak powinno Wyjścia
cmd1
icmd2
być przeplatane? Jeden wiersz z kolei? Po kolei pisanie 10 bajtów? Alternate „paragrafy” zdefiniowane w jakiś sposób? A jeśli po prostu nie robi” nie piszesz nic przez długi czas? to wszystko jest skomplikowane w obsłudze). Odbywa się to poprzez, na przykład(cmd1; cmd2; cmd3) | cmd4
, programówcmd1
,cmd2
icmd3
są prowadzone jedna po drugiej, to wyjście jest wysyłany jako dane wejściowecmd4
.źródło
W przypadku nakładającego się problemu w systemie Linux (z
bash
lubzsh
bezksh93
) możesz to zrobić jako:Zwróć uwagę na użycie
(...)
zamiast,{...}
aby uzyskać nowy proces przy każdej iteracji, abyśmy mogli mieć nowy fd 3 wskazujący na nowyauxfile
.< /dev/fd/3
jest sztuczką, aby uzyskać dostęp do tego teraz usuniętego pliku. Nie będzie działał na systemach innych niż Linux, na których< /dev/fd/3
jest podobny,dup2(3, 0)
dlatego fd 0 byłby otwarty w trybie tylko do zapisu z kursorem na końcu pliku.Aby uniknąć rozwidlenia dla funkcji zagnieżdżonej, możesz zapisać go jako:
Powłoka zajmowałaby się tworzeniem kopii zapasowej fd 3 przy każdej iteracji. Ostatecznie skończyłyby się deskryptory plików wcześniej.
Chociaż przekonasz się, że bardziej efektywne jest to zrobić:
Oznacza to, że nie zagnieżdżaj przekierowań.
źródło