Skip to content

Instantly share code, notes, and snippets.

@bcap
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bcap/9211633 to your computer and use it in GitHub Desktop.
Save bcap/9211633 to your computer and use it in GitHub Desktop.
Multiplos pipelines no shell

Os multiplos pipelines de shell q tava falando:

seq 1 100 \
    > >(awk '$1 % 2 == 0' | wc -l | read i && echo "$i divisiveis por 2") \
    > >(awk '$1 % 3 == 0' | wc -l | read i && echo "$i divisiveis por 3") \
    > >(awk '$1 % 5 == 0' | wc -l | read i && echo "$i divisiveis por 5")

O grande lance aqui é que voce pode duplicar os file descriptors de stdout/stderr de qualquer processo direto no bash/zsh/ksh(acho). Se voce tiver interesse, por baixo ele usa a syscall dup/dup2 (man -a dup). Na pratica o comando tee acaba sendo um wrapper pra usar essa syscall a partir do shell:

Por exemplo:

% seq 1 3 > a > b 
% cat a b
1
2
3
1
2
3

que da na mesma que:

seq 1 3 | tee a b > /dev/null

No caso do tee voce tem q jogar fora o stdout dele porque por padrao ele sempre duplica pro stdout o que chega no stdin dele. Voce poderia jogar para um dos arquivos que voce colocaria nos argumentos, mas a legibilidade fica ruim:

seq 1 3 | tee a > b

De qualquer forma, pra jogar o stdout de um processo no stdin de outro, é simplesmente fazer um pipe entre eles, o que é costume de sefazer. Porem voce pode fazer de uma forma mais indireta: jogando o stdout para um arquivo, e esse arquivo na verdade funcionando como o stdin de um processo:

seq 1 5 | grep 3

seq 1 5 > >(grep 3)

A grande diferenca de fazer esta forma é que voce pode duplicar o stdout, permitindo "forkar" a pipeline:

seq 1 5 > >(grep 1) > >(grep 3) > >(grep 5)

O >(comando) e <(comando) sao bem legais. Eles permitem executar comandos "disfarçados" de arquivos. No caso do >(comando), todo write no arquivo é na verdade um write no stdin do processo. No caso do <(comando), todo read no arquivo é na verdade um read do stdout do processo.

Por exemplo, o diff so le de arquivos. E se voce quiser fazer um diff de comandos? Voce pode jogar o stdout destes comandos pra arquivos e fazer o diff ler eles:

find arvore1 > find1
find arvore2 > find2
diff find1 find2

Porem isso usa disco a toa. Voce poderia fazer td via streams e gastar algo em torno de 8k de memoria:

diff <(find arvore1) <(find arvore2)

Desta forma o shell disponibiliza os resultados dos finds em memoria (normalmente 4k de stdout sao lidos por vez de cada processo) para serem lidos pelo diff :)

E o que acontece se arquivo a ser lido/escrito nao ser tratado como um stream, se fizer random access? Da erro. Exemplo:

% echo foobar > a

% python -c 'import sys; f = open(sys.argv[1]); f.seek(0); print "ok"' a
ok

% python -c 'import sys; f = open(sys.argv[1]); f.seek(0); print "ok"' <(cat a)
Traceback (most recent call last):
  File "<string>", line 1, in <module>
IOError: [Errno 29] Illegal seek
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment