Jak komponować funkcje bashowe za pomocą potoków?

18

Mam kilka zdefiniowanych w ten sposób funkcji:

function f {
  read and process $1
  ...
  echo $result
}

Chcę skomponować je razem, aby wyglądało na to wywołanie f | g | h.

Jakiego idiomu używam do konwersji funkcji działającej na argumentach na jeden odczyt argumentów ze standardowego wejścia? Czy możliwe jest czytanie par, krotek argumentów ze strumienia bez konieczności ucieczki (np. Kończenie na zero)?

Rumca
źródło
Albo chcesz coś podobnego, h(g(f(...)))albo każda z funkcji czyta ze standardowego wejścia ( read x; ...) i zapisuje na standardowe wyjście ( echo ...).
vonbrand

Odpowiedzi:

21

Jednym potencjalnym podejściem byłoby umieszczenie while...readkonstrukcji w twoich funkcjach, która przetwarzałaby wszelkie dane, które weszły do ​​funkcji przez STDIN, działała na niej, a następnie wysyłała otrzymane dane z powrotem przez STDOUT.

function X {
  while read data; do
    ...process...
  done
}

Trzeba będzie zachować ostrożność podczas konfigurowania while ..read..komponentów, ponieważ będą one wysoce zależne od typów danych, które będą w stanie niezawodnie wykorzystać. Może istnieć optymalna konfiguracja, którą możesz wymyślić.

Przykład

$ logF() { while read data; do echo "[F:$(date +"%D %T")] $data"; done; }
$ logG() { while read data; do echo "G:$data";                    done; }
$ logH() { while read data; do echo "H:$data";                    done; }

Oto każda funkcja sama w sobie.

$ echo "hi" | logF
[F:02/07/14 20:01:11] hi

$ echo "hi" | logG
G:hi

$ echo "hi" | logH
H:hi

Oto one, kiedy używamy ich razem.

$ echo "hi" | logF | logG | logH
H:G:[F:02/07/14 19:58:18] hi

$ echo -e "hi\nbye" | logF | logG | logH
H:G:[F:02/07/14 19:58:22] hi
H:G:[F:02/07/14 19:58:22] bye

Mogą przyjmować różne style danych wejściowych.

#-- ex. #1
$ cat <<<"some string of nonsense" | logF | logG | logH
H:G:[F:02/07/14 20:03:47] some string of nonsense

#-- ex. #2    
$ (logF | logG | logH) <<<"Here comes another string."
H:G:[F:02/07/14 20:04:46] Here comes another string.

#-- ex. #3
$ (logF | logG | logH)
Look I can even
H:G:[F:02/07/14 20:05:19] Look I can even
type to it
H:G:[F:02/07/14 20:05:23] type to it
live
H:G:[F:02/07/14 20:05:25] live
via STDIN
H:G:[F:02/07/14 20:05:29] via STDIN
..type Ctrl + D to stop..

#-- ex. #4
$ seq 5 | logF | logG | logH
H:G:[F:02/07/14 20:07:40] 1
H:G:[F:02/07/14 20:07:40] 2
H:G:[F:02/07/14 20:07:40] 3
H:G:[F:02/07/14 20:07:40] 4
H:G:[F:02/07/14 20:07:40] 5

#-- ex. #5
$ (logF | logG | logH) < <(seq 2)
H:G:[F:02/07/14 20:15:17] 1
H:G:[F:02/07/14 20:15:17] 2
slm
źródło
4

Jako dodatek do odpowiedzi SLM przeprowadziłem eksperymenty z krotkami rozdzielonymi zerami jako argumentami funkcji:

$ sayTuple() { 
    IFS= read -r -d $'\0' d1
    IFS= read -r -d $'\0' d2
    echo "sayTuple: -$d1- -$d2-"
}

Uwagi: sayTupledwukrotnie czyta zapis zakończony zerem, -d $'\0'obsługujący dowolne miejsce otaczające dane wejściowe IFS=. echoarchiwum w otoczeniu-

Wynik pokazuje, że poprawnie obsługuje dane wejściowe zakończone znakiem null zawierające \ni \t:

$ printf "%s\0%s\0" "Hello " $' Brave\n\tWorld' | sayTuple 
sayTuple: -Hello - - Brave
        World-

Dodaj sugestie dotyczące ulepszenia komentarzy, to ciekawy temat.

grebneke
źródło
+1 jak twój pomysł. Zamiast tego moglibyśmy umieścić w środku pętlę, która pozwoliłaby jej na przyjęcie dowolnej liczby argumentów. sayTuple() { arr=() ; while IFS= read -r -d $'\0' arg; do arr+="$arg"; done; echo "sayTuple: ${arr[@]}"; }.
slm
Wydaje się, że powinieneś być w stanie to zrobić, IFS= read -r -d $'\0' -a argale nie mogłem tego zrobić. Pozwoliłoby to na usunięcie tego while, co wydaje się niepotrzebne, ale był to jedyny wzór, jaki mogłem dostać do pracy.
slm