Como compor funções bash usando pipes?

18

Eu tenho poucas funções definidas dessa maneira:

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

Quero compor eles juntos para que a invocação pareça f | g | h.

Qual idioma devo usar para converter a função que trabalha nos argumentos em argumentos de leitura de stdin? É possível ler pares, tuplas de argumentos do fluxo sem precisar escapar deles (por exemplo, terminação nula)?

Rumca
fonte
Você quer algo parecido h(g(f(...)))ou cada uma das funções lê da entrada padrão ( read x; ...) e grava na saída padrão ( echo ...).
vonbrand

Respostas:

21

Uma abordagem potencial seria colocar while...readdentro de suas funções uma construção que processasse todos os dados que entrassem na função através do STDIN, operassem nela e depois emitissem os dados resultantes de volta via STDOUT.

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

É preciso ter cuidado com a forma como você configura seus while ..read..componentes, pois eles dependem muito dos tipos de dados que poderão consumir com segurança. Pode haver uma configuração ideal que você possa criar.

Exemplo

$ 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; }

Aqui está cada função por si só.

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

$ echo "hi" | logG
G:hi

$ echo "hi" | logH
H:hi

Aqui estão eles quando os usamos juntos.

$ 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

Eles podem receber vários estilos de entrada.

#-- 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
fonte
4

Como um adendo à resposta do slm , fiz algumas experiências com tuplas separadas por nulo como argumentos de função:

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

Notas: sayTuplelê duas vezes um registro terminado por nulo, -d $'\0'manipulando qualquer espaço ao redor da entrada IFS=. echoregistros anteriores rodeados por-

O resultado mostra que ele lida corretamente com entradas terminadas em nulo contendo \ne \t:

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

Por favor, adicione sugestões para melhorias nos comentários, é um tópico interessante.

Grebneke
fonte
+1 como a sua ideia. Poderíamos colocar um loop dentro dele, o que permitiria que ele usasse # arbitrários de args. sayTuple() { arr=() ; while IFS= read -r -d $'\0' arg; do arr+="$arg"; done; echo "sayTuple: ${arr[@]}"; }.
slm
Parece que você deveria poder fazer um, IFS= read -r -d $'\0' -a argmas não consegui fazer isso funcionar. Isso permitiria a remoção do while, o que parece desnecessário, mas era o único padrão que eu poderia trabalhar.
Slm