Usando as próprias ferramentas da caixa de diálogo: - sinalizador de saída-fd
Se você ler a página de manual para o diálogo, existe a opção --output-fd
que permite definir explicitamente para onde a saída vai (STDOUT 1, STDERR 2), em vez de, por padrão, ir para STDERR.
Abaixo, você pode me ver executando o dialog
comando de amostra , afirmando explicitamente que a saída deve ir para o descritor de arquivo 1, o que me permite salvá-la no MYVAR.
MYVAR=$(dialog --inputbox "THIS OUTPUT GOES TO FD 1" 25 25 --output-fd 1)
Usando pipes nomeados
Uma abordagem alternativa que tem muito potencial oculto é usar algo conhecido como pipe nomeado .
#!/bin/bash
mkfifo /tmp/namedPipe1 # this creates named pipe, aka fifo
# to make sure the shell doesn't hang, we run redirection
# in background, because fifo waits for output to come out
dialog --inputbox "This is an input box with named pipe" 40 40 2> /tmp/namedPipe1 &
# release contents of pipe
OUTPUT="$( cat /tmp/namedPipe1 )"
echo "This is the output " $OUTPUT
# clean up
rm /tmp/namedPipe1
A resposta original de user.dz e a explicação de ByteCommander fornecem uma boa solução e uma visão geral do que ele faz. No entanto, acredito que uma análise mais profunda pode ser benéfica para explicar por que funciona.
Antes de tudo, é importante entender duas coisas: qual é o problema que estamos tentando resolver e quais são os trabalhos subjacentes dos mecanismos shell com os quais estamos lidando. A tarefa é capturar a saída de um comando via substituição de comando. Sob uma visão geral simplista que todos sabem, as substituições de comando capturam o stdout
comando e permitem que ele seja reutilizado por outra coisa. Nesse caso, a result=$(...)
peça deve salvar a saída de qualquer comando designado por ...
uma variável chamada result
.
Debaixo do capô, a substituição de comando é realmente implementada como canal, onde há um processo filho (o comando real que é executado) e um processo de leitura (que salva a saída em variável). Isso é evidente com um rastreamento simples de chamadas do sistema. Observe que o descritor de arquivo 3 é o final da leitura do canal, enquanto 4 é o final da gravação. Para o processo filho de echo
, que grava no seu stdout
- o descritor de arquivo 1, esse descritor de arquivo é na verdade cópia do descritor de arquivo 4, que é o final da gravação do pipe. Observe que stderr
não está desempenhando um papel aqui, simplesmente porque é stdout
apenas um tubo conectado .
$ strace -f -e pipe,dup2,write,read bash -c 'v=$(echo "X")'
...
pipe([3, 4]) = 0
strace: Process 6200 attached
[pid 6199] read(3, <unfinished ...>
[pid 6200] dup2(4, 1) = 1
[pid 6200] write(1, "X\n", 2 <unfinished ...>
[pid 6199] <... read resumed> "X\n", 128) = 2
[pid 6200] <... write resumed> ) = 2
[pid 6199] read(3, "", 128) = 0
[pid 6200] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=6200, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
Vamos voltar à resposta original por um segundo. Como agora sabemos que dialog
a caixa TUI é gravada stdout
, respondida stderr
e dentro da substituição de comando stdout
é canalizada para outro lugar, já temos parte da solução - precisamos reconectar os descritores de arquivo de maneira que stderr
sejam canalizados para o processo do leitor. Esta é a 2>&1
parte da resposta. No entanto, o que fazemos com a caixa TUI?
É aí que entra o descritor de arquivo 3. O dup2()
syscall nos permite duplicar descritores de arquivo, fazendo com que eles se refiram efetivamente ao mesmo local, mas podemos manipulá-los separadamente. Os descritores de arquivo dos processos que possuem o terminal de controle conectado realmente apontam para um dispositivo de terminal específico. Isso é evidente se você fizer
$ ls -l /proc/self/fd
total 0
lrwx------ 1 user1 user1 64 Aug 20 10:30 0 -> /dev/pts/5
lrwx------ 1 user1 user1 64 Aug 20 10:30 1 -> /dev/pts/5
lrwx------ 1 user1 user1 64 Aug 20 10:30 2 -> /dev/pts/5
lr-x------ 1 user1 user1 64 Aug 20 10:30 3 -> /proc/6424/fd
onde /dev/pts/5
está meu dispositivo pseudo-terminal atual. Assim, se, de alguma forma, podemos salvar esse destino, ainda podemos escrever a caixa TUI na tela do terminal. Isso é o que exec 3>&1
faz. Quando você chama um comando com redirecionamento, command > /dev/null
por exemplo, o shell passa seu descritor de arquivo stdout e, em seguida, usa dup2()
para gravar esse descritor de arquivo /dev/null
. O exec
comando executa algo semelhante aosdup2()
descritores de arquivo para toda a sessão do shell, fazendo com que qualquer comando herde o descritor de arquivo já redirecionado. O mesmo com exec 3>&1
. O descritor de arquivo 3
agora fará referência a / apontar para o terminal de controle e qualquer comando executado nessa sessão de shell saberá sobre ele.
Então, quando result=$(dialog --inputbox test 0 0 2>&1 1>&3);
ocorre, o shell cria um canal para a caixa de diálogo ser gravada, mas também 2>&1
primeiro faz com que o descritor de arquivo do comando 2 seja duplicado no descritor de arquivo de gravação desse canal (fazendo com que a saída vá para a extremidade final do canal e para a variável) , enquanto o descritor de arquivo 1 será duplicado em 3. Isso fará com que o descritor de arquivo 1 ainda se refira ao terminal de controle, e a caixa de diálogo TUI será exibida na tela.
Agora, na verdade, existe uma mão curta para o atual terminal de controle do processo, que é /dev/tty
. Assim, a solução pode ser simplificada sem o uso de descritores de arquivo, simplesmente em:
result=$(dialog --inputbox test 0 0 2>&1 1>/dev/tty);
echo "$result"
Coisas importantes a lembrar:
- descritores de arquivo são herdados do shell por cada comando
- substituição de comando é implementada como canal
- descritores de arquivo duplicados se referirão ao mesmo local que o original, mas podemos manipular cada descritor de arquivo separadamente
Veja também
mktemp
comando para criar um arquivo temporário.