Como fazer um loop de redirecionamento

6

Este é principalmente por razões acadêmicas. ; -)

Considere a seguinte declaração de Bash:

foo | bar

Isso executa dois processos, foo e bar , de modo que o padrão de saída de foo esteja conectado ao padrão de entrada de bar . Até agora tudo bem.

Existe uma maneira de invocar foo e bar para que nós também tenham uma conexão na direção oposta?

foo.stdout -> bar.stdin
foo.stdin  <- bar.stdout

Pelo que entendi, Bash chama pipe(2) para criar um par de FDs conectados uns aos outros, que podem então ser usados para substituir stdin / stdout nos respectivos processos filhos. No nível do kernel, não vejo razão para você não poder chamar pipe(2) duas vezes e organizar o arranjo circular mostrado acima.

Mas não consigo descobrir a sintaxe do shell para fazer isso

.

Até agora, a melhor idéia que posso fazer é usar pipes nomeados, ou talvez lançar um subshell e fazer algum redirecionamento FD-to-FD de alguma forma ...

    
por MathematicalOrchid 08.07.2016 / 21:12

3 respostas

3

No bash, você pode fazer isso com um coproc (o bash tem um péssimo suporte para múltiplos coprocs, mas você só precisa de um aqui):

#!/bin/bash
set -e
coproc { while read -r line; do echo "$BASHPID read: $line";  done; }
i=0; while :; do
    echo "$BASHPID writing>> $i"
    echo $i >&"${COPROC[1]}"
    read -r line <&"${COPROC[0]}"
    echo "$BASHPID coproc produced>> $line"
    i=$((i+1))
done

ou named pipes (também funcionam com shells POSIX simples):

#!/bin/bash
set -e
trap 'rm -rf "$tmpd"' EXIT
tmpd=$(mktemp -d)

mkfifo "$tmpd/p0" "$tmpd/p1"
exec 3<>"$tmpd/p0"
exec 4<>"$tmpd/p1"
rm -rf "$tmpd"

( while read -r line; do echo "$BASHPID read: $line";  done; ) <&3 >&4  &
i=0; while :; do
    echo "$BASHPID writing>> $i" 
    echo $i >&3
    read -r line <&4
    echo "$BASHPID coproc produced>> $line"
    i=$((i+1))
done 

Ambos podem parecer feios se você não está acostumado a lidar com fd em shells.

Além disso, devido aos canais de efeito no planejamento (gravar em pipes com um buffer de canal completo bloqueia a leitura de um canal com um vazio), você pode ficar em deadlock com determinados padrões de leitura / gravação.

As saídas dos dois exemplos acima podem ser assim:

32435 writing>> 0
32435 coproc produced>> 32441 read: 0
32435 writing>> 1
32435 coproc produced>> 32441 read: 1
32435 writing>> 2
32435 coproc produced>> 32441 read: 2
32435 writing>> 3
32435 coproc produced>> 32441 read: 3
32435 writing>> 4
32435 coproc produced>> 32441 read: 4
    
por 08.07.2016 / 21:53
3

Eu não acho que você pode fazer isso com um pipe ( | ), mas é fácil fazer com processos assíncronos escrevendo e lendo uns aos outros.

Este script ksh93 ( bash versão mais abaixo ) inicia dois while -loops que lançam um número entre eles, adicionando um ao número em cada transação:

while read data; do
    print $(( data + 1 ))
done |&

print -p 1

while read -p data; do
    print $(( data + 1 ))
done >&p
  1. O primeiro loop é iniciado como um co-processo. Aguarda a entrada em seu read .
  2. O número 1 é dado ao read do co-processo para iniciar tudo.
  3. O segundo loop é iniciado fazendo a mesma coisa que o primeiro loop, mas não como um co-processo.

Executando com xtrace enabled (e com um script modificado que define PS4 , o prompt de rastreio, para "& " para o primeiro loop e para "> " para o segundo loop, apenas para mostrar o que é ):

$ ksh -x script.sh
& PS4='& '
> PS4='> '
> print -p 1
> 1>& p
> read -p data
& read data
& print 2
& read data
> print 3
> read -p data
& print 4
> print 5
> read -p data
& read data
& print 6
& read data
> print 7
> read -p data
(etc.)

bash também co-processa (procure o coproc builtin), mas eu tenho menos familiaridade com eles. Você provavelmente pode fazer isso em um shell sem co-processos também.

EDIT : algo assim para bash :

coproc while read data; do
    echo $(( data + 1 ))
done

echo 1 >&${COPROC[1]}

while read -u ${COPROC[0]} data; do
    echo $(( data + 1 ))
done >&${COPROC[1]}

Executando-o (com prompt de rastreamento modificado como acima):

$ bash -x script.sh
+ PS4='& '
& PS4='> '
> echo 1
> read -u 63 data
& read data
& echo 2
> echo 3
> read -u 63 data
& read data
& echo 4
> echo 5
> read -u 63 data
& read data
& echo 6
> echo 7
> read -u 63 data
(etc.)
    
por 08.07.2016 / 21:45
1

Para árvores mais complicadas de redirecionamento de descritores de arquivo, também há pipexec , que chegou ao .

Na verdade, é uma limitação da sintaxe em BASH e em shells semelhantes a Bourne em geral.

    
por 11.07.2016 / 22:50