Bash: cria um fifo anônimo

29

Todos conhecemos mkfifo e pipelines. O primeiro cria um canal nomeado , portanto, é necessário selecionar um nome, provavelmente com mktemp e, mais tarde, lembre-se de desvincular. O outro cria um pipe anônimo, sem problemas com nomes e remoção, mas as extremidades do pipe ficam ligadas aos comandos no pipeline, não é realmente conveniente de alguma forma obter um controle dos descritores de arquivo e usá-los no restante do roteiro. Em um programa compilado, gostaria apenas de fazer ret=pipe(filedes) ; no Bash existe exec 5<>file , então alguém esperaria algo como "exec 5<> -" ou "pipe <5 >6" -há algo parecido no Bash?

    
por Adrian Panasiuk 03.09.2010 / 17:18

5 respostas

35

Você pode desvincular um canal nomeado imediatamente após anexá-lo ao processo atual, o que praticamente resulta em um canal anônimo:

# create a temporary named pipe
PIPE=$(mktemp -u)
mkfifo $PIPE
# attach it to file descriptor 3
exec 3<>$PIPE
# unlink the named pipe
rm $PIPE
...
# anything we write to fd 3 can be read back from it
echo 'Hello world!' >&3
head -n1 <&3
...
# close the file descriptor when we are finished (optional)
exec 3>&-

Se você realmente quiser evitar pipes nomeados (por exemplo, o sistema de arquivos é somente leitura), sua ideia de "obter um controle sobre os descritores de arquivo" também funciona. Observe que isso é específico do Linux devido ao uso de procfs.

# start a background pipeline with two processes running forever
tail -f /dev/null | tail -f /dev/null &
# save the process ids
PID2=$!
PID1=$(jobs -p %+)
# hijack the pipe's file descriptors using procfs
exec 3>/proc/$PID1/fd/1 4</proc/$PID2/fd/0
# kill the background processes we no longer need
# (using disown suppresses the 'Terminated' message)
disown $PID2
kill $PID1 $PID2
...
# anything we write to fd 3 can be read back from fd 4
echo 'Hello world!' >&3
head -n1 <&4
...
# close the file descriptors when we are finished (optional)
exec 3>&- 4<&-
    
por 18.08.2013 / 01:22
22

Embora nenhum dos shells que eu conheço possa fazer pipes sem bifurcar, alguns têm melhor do que o pipeline básico do shell.

No bash, ksh e zsh, supondo que seu sistema suporta /dev/fd (a maioria faz hoje em dia), você pode ligar a entrada ou a saída de um comando a um nome de arquivo: <(command) expande para um nome de arquivo que designa um pipe conectado à saída de command e >(command) expande para um nome de arquivo que designa um canal conectado à entrada de command . Esse recurso é chamado de substituição de processo . Seu objetivo principal é canalizar mais de um comando para dentro ou para fora de outro, por exemplo,

diff <(transform <file1) <(transform <file2)
tee >(transform1 >out1) >(transform2 >out2)

Isso também é útil para combater algumas das deficiências dos canos básicos de shell. Por exemplo, command2 < <(command1) é equivalente a command1 | command2 , exceto que seu status é command2 . Outro caso de uso é exec > >(postprocessing) , que é equivalente, mas mais legível, colocando todo o resto do script dentro de { ... } | postprocessing .

    
por 03.09.2010 / 21:01
10

O Bash 4 tem coprocessos .

A coprocess is executed asynchronously in a subshell, as if the command had been terminated with the ‘&’ control operator, with a two-way pipe established between the executing shell and the coprocess.

The format for a coprocess is:

coproc [NAME] command [redirections] 
    
por 03.09.2010 / 18:00
3

A partir de outubro de 2012, essa funcionalidade ainda parece não existir no Bash, mas o coproc pode ser usado se todos os pipes não nomeados / anônimos forem para conversar com um processo filho. Problema com coproc neste ponto é que aparentemente apenas um é suportado por vez. Eu não consigo entender porque o coproc tem essa limitação. Eles deveriam ter sido um aprimoramento do código de tarefa existente (o & op), mas isso é uma questão para os autores do bash.

    
por 08.10.2012 / 03:49
0

Usando a grande e brilhante resposta de htamas, eu modifiquei um pouco para usá-lo em um único liner, aqui está:

# create a temporary named pipe
PIPE=('(exec 0</dev/null 1</dev/null; (( read -d \  e < /proc/self/stat ; echo $e >&2 ; exec tail -f /dev/null 2> /dev/null ) | ( read -d \  e < /proc/self/stat ; echo $e  >&2 ; exec tail -f /dev/null 2> /dev/null )) &) 2>&1 | for ((i=0; i<2; i++)); do read e; printf "$e "; done')
# attach it to file descriptors 3 and 4
exec 3>/proc/${PIPE[0]}/fd/1 4</proc/${PIPE[1]}/fd/0
...
# kill the temporary pids
kill ${PIPE[@]}
...
# anything we write to fd 3 can be read back from fd 4
echo 'Hello world!' >&3
head -n1 <&4
...
# close the file descriptor when we are finished (optional)
exec 3>&- 4<&-
    
por 06.07.2016 / 00:00