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)?


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

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.


+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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.