Leitura / gravação no mesmo descritor de arquivo com redirecionamento de shell

2

Estou tentando entender os descritores de arquivos no contexto do redirecionamento de shell.

Por que não posso ter cat lido do FD 3, que está sendo gravado pelo STDOUT de ls ?

{ err=$(exec 2>&1 >&3; ls -ld /x /bin); exec 0<&3; out=$(cat); } 3>&1;

Quando tentar isso, cat ainda deseja ler no meu teclado.

Se isso não puder ser feito, por que não?

Diferenciação: Esta questão é sobre leitura / gravação no mesmo descritor de arquivo, usando o problema apresentado por Redirecionar STDERR e STDOUT para Variáveis diferentes sem arquivos temporários como um exemplo.

    
por Tom Hale 01.10.2018 / 16:17

2 respostas

0

Parece uma limitação básica da sintaxe de atribuição de variáveis e os efeitos colaterais das subcamadas de geração de shell. Você pode capturar stderr ou stdout, mas não ambos: o outro fluxo precisa ser redirecionado para o arquivo (talvez um FIFO).

# a function for testing
your_command() { sh -c 'echo "this is stdout"; echo "this is stderr" >&2'; }

errfile=$(mktemp)
out=$( your_command 2>|"$errfile" )
err=$(< "$errfile")
rm "$errfile"

echo "out: $out"
echo "err: $err"
    
por 01.10.2018 / 17:01
0

Em

{ err=$(exec 2>&1 >&3; ls -ld /x /bin); exec 0<&3; out=$(cat); } 3>&1

O { ... } 3>&1 clona o fd 1 para o fd 3. Isso significa que o fd3 agora aponta para o mesmo recurso (a mesma descrição do arquivo aberto ) que o fd 1 apontou. Se você executou isso de um terminal, provavelmente será um fd aberto no modo de leitura + gravação para um dispositivo de terminal.

Depois de exec 0<&3 , os fds 0, 1 e 3 estão apontando para a mesma descrição do arquivo aberto (criada quando o emulador de terminal abriu o lado escravo do par de pseudo-terminais criado antes executando seu shell no caso do comando executado no terminal acima).

Em seguida, em out=$(cat) , para o processo que está executando cat , o $(...) altera o fd 1 para a extremidade de gravação de um canal, enquanto 0 ainda é o dispositivo tty. Então, cat lerá do dispositivo terminal, então as coisas que você está digitando no teclado (e se não fosse um dispositivo terminal, você provavelmente receberia um erro, já que o fd provavelmente estava aberto no modo somente gravação).

Para cat ler o que ls grava em seu stdout, você precisaria que ls stdout e cat stdin tenham duas extremidades em um mecanismo IPC, como pipe, socketpair ou pseudo-terminal. Por exemplo, ls stdout é o fim de gravação de um pipe e cat stdin é o final da leitura.

Mas você também precisaria que ls e cat sejam executados simultaneamente, não um após o outro, pois é um mecanismo de IPC (comunicação entre processos).

Como canos podem reter alguns dados (64 KiB por padrão nas versões atuais do Linux), você sairia com saídas curtas se você conseguisse criar aquele segundo canal, mas para saídas maiores, você teríamos deadlocks, ls iria travar quando o pipe estivesse cheio e travaria até que algo esvaziasse o pipe, mas cat só pode esvaziar o pipe quando ls retornar.

Além disso, somente yash tem uma interface bruta para pipe() , que seria necessário para criar o segundo canal para ler ls stdout (o outro canal para stderr sendo criado pela $(...) construct).

No yash, você faria:

{ out=$(ls -d / /x 2>&3); exec 3>&-; err=$(exec cat <&4); } 3>>|4

Onde 3>>|4 (um recurso específico do yash) cria o segundo pipe com o fim da escrita em fd 3 e o final da leitura em fd 4.

Mas, novamente, se a saída stderr for maior que o tamanho do tubo, ela será interrompida. Estamos efetivamente usando o pipe como um arquivo temporário na memória, não um pipe.

Para realmente usar pipes, precisamos iniciar ls com stdout sendo a extremidade de gravação de um pipe e stderr sendo a extremidade de gravação de outro canal, e então o shell lê as outras extremidades desses pipes simultaneamente, como os dados vêm (não um após o outro ou novamente você se depararia com bloqueios mortos) para armazenar nas duas variáveis.

Para conseguir ler esses dois fds à medida que os dados chegam, você precisaria de um shell com select() / poll() support. zsh é um shell desse tipo, mas não possui o recurso redirecionamento de pipeline do yash , portanto, é necessário usar pipes nomeados (para gerenciar sua criação, permissões e limpeza) e use um loop complexo com zselect / sysread ...

¹ Se no Linux, porém, você seria capaz de usar o fato de que /proc/self/fd/x em um pipe se comporta como um pipe nomeado, então você poderia fazer:

#! /bin/zsh
zmodload zsh/zselect
zmodload zsh/system

(){exec {wo}>$1 {ro}<$1} <(:) # like yash's wo>>|ro (but on Linux only)
(){exec {we}>$1 {re}<$1} <(:)

ls -d / /x >&$wo 2>&$we &
exec {wo}>&- {we}>&-
out= err=
o_done=0 e_done=0

while ((! (o_done && e_done))) && zselect -A ready $ro $re; do
  if ((${#ready[$ro]})); then
    sysread -i $ro && out+=$REPLY || o_done=1
  fi
  if ((${#ready[$re]})); then
    sysread -i $re && err+=$REPLY || e_done=1
  fi
done
    
por 05.10.2018 / 10:50