Como obter entrada de caixa de diálogo direcionada para uma variável?

18

Eu tenho me ensinado a bash scripts e me deparei com um problema. Eu escrevi um script para receber a entrada do usuário, usando o comando 'read', e faço dessa entrada uma variável para usar posteriormente no script. O script funciona, mas ....

Gostaria de conseguir configurá-lo usando o 'diálogo'. Eu descobri que

'dialog --inputbox' direcionará a saída para 'stderr' e, para obter essa entrada como uma variável, você deve direcioná-la para um arquivo e recuperá-la. O código que encontrei para explicar isso é:

#!/bin/bash
dialog --inputbox \

"What is your username?" 0 0 2> /tmp/inputbox.tmp.$$

retval=$?

input=`cat /tmp/inputbox.tmp.$$`

rm -f /tmp/inputbox.tmp.$$

case $retval in
0)

echo "Your username is '$input'";;
1)

echo "Cancel pressed.";;

esac

Vejo que ele está enviando o sdterr para o /tmp/inputbox.tmp.$$ com 2>, mas o arquivo de saída se parece com 'inputbox.tmp.21661'. Quando tento capturar o arquivo, ocorre um erro. Portanto, ainda não consigo obter a entrada do usuário da --inputbox como uma variável.

Script de exemplo:

echo "  What app would you like to remove? "

read dead_app

sudo apt-get remove --purge $dead_app

Então, como você pode ver, é um script básico. É possível obter a variável como uma palavra dialog --inputbox?

emerikanbloke
fonte
Na minha experiência, o script funciona bem, se você remover a linha vazia após a 2ª linha. Como alternativa, você pode usar o mktempcomando para criar um arquivo temporário.
jarno 16/07/2016

Respostas:

16

: DI não pode explicar !!! Se você conseguir entender o que eles estão dizendo no Advanced Bash-Scripting Guide: Capítulo 20. Redirecionamento de E / S , escreva uma nova resposta e eu darei 50rep :

exec 3>&1;
result=$(dialog --inputbox test 0 0 2>&1 1>&3);
exitcode=$?;
exec 3>&-;
echo $result $exitcode;

Referência: a caixa de diálogo no bash não está capturando variáveis ​​corretamente

^ resposta de @Sneetsher (4 de julho de 2014)

Conforme solicitado, tentarei explicar o que esse snippet está fazendo linha por linha.

Observe que simplificarei omitindo todos os ;pontos e vírgulas nas extremidades da linha, porque eles não são necessários se escrevermos um comando por linha.

E / S - Fluxos:

Primeiro, você precisa entender os fluxos de comunicação. Existem 10 fluxos, numerados de 0 a 9:

  • Fluxo 0 ("STDIN"):
    "Entrada padrão", o fluxo de entrada padrão para ler dados do teclado.

  • Fluxo 1 ("STDOUT"):
    "Saída padrão", o fluxo de saída padrão usado para mostrar texto normal no terminal.

  • Fluxo 2 ("STDERR"): "Erro padrão", o fluxo de saída padrão usado para exibir erros ou outro texto para fins especiais no terminal.

  • Fluxos 3-9: Fluxos
    adicionais, livremente utilizáveis. Eles não são usados ​​por padrão e não existem até que algo tente usá-los.

Observe que todos os "fluxos" são representados internamente pelos descritores de arquivo em /dev/fd(que é um link simbólico para o /proc/self/fdqual contém outro link simbólico para cada fluxo ... é um pouco complicado e não é importante para o comportamento deles, então paro aqui.). Os fluxos padrão também têm /dev/stdin, /dev/stdoute /dev/stderr(que são links simbólicos novamente, etc ...).

O script:

  • exec 3>&1

    O Bash interno execpode ser usado para aplicar um redirecionamento de fluxo ao shell, o que significa que afeta todos os seguintes comandos. Para mais informações, corra help execno seu terminal.

    Nesse caso especial, o fluxo 3 é redirecionado para o fluxo 1 (STDOUT), ou seja, tudo o que enviamos ao fluxo 3 mais tarde aparecerá em nosso terminal como se fosse normalmente impresso em STDOUT.

  • result=$(dialog --inputbox test 0 0 2>&1 1>&3)

    Esta linha consiste em muitas partes e estruturas sintáticas:

    • result=$(...)
      Essa estrutura executa o comando entre colchetes e atribui a saída (STDOUT) à variável bash result. É legível $result. Tudo isso é descrito de alguma forma no muito obscuro man bash.

    • dialog --inputbox TEXT HEIGHT WIDTH
      Este comando mostra uma caixa TUI com o TEXTO fornecido, um campo de entrada de texto e dois botões OK e CANCEL. Se OK for selecionado, o comando sairá com o status 0 e imprimirá o texto digitado em STDERR; se CANCEL for selecionado, ele sairá com o código 1 e não imprimirá nada. Para mais informações, leia man dialog.

    • 2>&1 1>&3
      Estes são dois comandos de redirecionamento. Eles serão interpretados da direita para a esquerda:

      1>&3 redireciona o fluxo 1 do comando (STDOUT) para o fluxo personalizado 3.

      2>&1 redireciona depois o fluxo 2 do comando (STDERR) para o fluxo 1 (STDOUT).

      Isso significa que tudo o que o comando imprime no STDOUT agora aparece no fluxo 3, enquanto tudo o que se destinava a aparecer no STDERR agora é redirecionado para o STDOUT.

    Portanto, a linha inteira exibe um prompt de texto (no STDOUT, que foi redirecionado para o fluxo 3, que o shell redireciona novamente para o STDOUT no final - veja o exec 3>&1comando) e atribui os dados inseridos (retornados por STDERR e depois redirecionados para STDOUT) para a variável Bash result.

  • exitcode=$?

    Esse código recupera o código de saída do comando executado anteriormente (daqui de dialog) através da variável Bash reservada $?(sempre mantém o último código de saída) e simplesmente o armazena em nossa própria variável Bash exitcode. Pode ser lido $exitcodenovamente. Você pode procurar mais informações sobre isso em man bash, mas isso pode demorar um pouco ...

  • exec 3>&-

    O Bash interno execpode ser usado para aplicar um redirecionamento de fluxo ao shell, o que significa que afeta todos os seguintes comandos. Para mais informações, corra help execno seu terminal.

    Nesse caso especial, o fluxo 3 é redirecionado para "fluxo -", o que significa apenas que deve ser fechado. Os dados enviados para o fluxo 3 não serão mais redirecionados a partir de agora.

  • echo $result $exitcode

    Este echocomando simples (mais informações man echo) apenas imprime o conteúdo das duas variáveis ​​Bash resulte exitcodeno STDOUT. Como não temos mais redirecionamentos explícitos ou implícitos de stream aqui, eles realmente aparecerão no STDOUT e, portanto, serão exibidos no terminal. Que milagre! ;-)

Resumo:

Primeiro, configuramos o shell para redirecionar tudo o que enviamos ao fluxo personalizado 3 de volta ao STDOUT, para que ele apareça em nosso terminal.

Em seguida, executamos o dialogcomando, redirecionamos seu STDOUT original para nosso fluxo personalizado 3, porque ele precisa ser exibido no final, mas precisamos temporariamente usar o fluxo STDOUT para outra coisa.
Nós redirecionamos o STDERR original do comando, onde a entrada do usuário da janela de diálogo é retornada, para STDOUT posteriormente.
Agora podemos capturar o STDOUT (que contém os dados redirecionados do STDERR) e armazená-lo em nossa variável $result. Ele contém a entrada de usuário desejada agora!

Também queremos o dialogcódigo de saída do comando, que mostra se OK ou CANCEL foi clicado. Este valor é apresentado na variável Bash reservada $?e apenas o copiamos para nossa própria variável $exitcode.

Depois disso, fechamos o fluxo 3 novamente, pois não precisamos mais dele, para impedir novos redirecionamentos.

Finalmente, normalmente emitimos o conteúdo de ambas as variáveis $result(a entrada do usuário da janela de diálogo) e $exitcode(0 para OK, 1 para CANCEL) para o terminal.

Byte Commander
fonte
Eu acho que usar execé desnecessariamente complicado. Por que não apenas --stdoutoptar por nós dialogou redirecionar sua saída 2>&1 >/dev/tty?
Jarno
Por favor, veja minha resposta .
Jarno
3
Ótima resposta! No entanto, acredito que você tenha uma nota incorreta - você diz que "Eles serão interpretados da direita para a esquerda", mas acredito que isso não é verdade. A partir do manual do bash gnu.org/software/bash/manual/html_node/Redirections.html indica que redirecionamentos ter lugar como eles são encontrados (ie esquerda para a direita)
ralfthewise
14

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-fdque 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 dialogcomando 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)

insira a descrição da imagem aqui

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 

insira a descrição da imagem aqui

Uma visão geral mais detalhada da resposta do user.dz com abordagem alternativa

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 stdoutcomando 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 stderrnão está desempenhando um papel aqui, simplesmente porque é stdoutapenas 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 dialoga caixa TUI é gravada stdout, respondida stderre 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 stderrsejam canalizados para o processo do leitor. Esta é a 2>&1parte 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/5está 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>&1faz. Quando você chama um comando com redirecionamento, command > /dev/nullpor exemplo, o shell passa seu descritor de arquivo stdout e, em seguida, usa dup2()para gravar esse descritor de arquivo /dev/null. O execcomando 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 3agora 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>&1primeiro 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

Sergiy Kolodyazhnyy
fonte
A página de manual também diz que a --stdoutopção pode ser perigosa e falhar facilmente em alguns sistemas, e acho que --output-fd 1está fazendo o mesmo: --stdout: Direct output to the standard output. This option is provided for compatibility with Xdialog, however using it in portable scripts is not recommended, since curses normally writes its screen updates to the standard output. If you use this option, dialog attempts to reopen the terminal so it can write to the display. Depending on the platform and your environment, that may fail.- No entanto, a ideia de pipe nomeado é legal!
Byte Commander
@ByteCommander "Pode falhar" não é muito convincente, pois isso não fornece exemplos. Além disso, eles não mencionam nada sobre --output-fd, que é a opção que usei aqui, não --stdout. Segundo, o diálogo está sendo desenhado primeiro no stdout, a saída retornada é o segundo. Nós não fazemos essas duas coisas ao mesmo tempo. No entanto, --output-fd não requer especificamente o uso de fd 1 (STDOUT). Ele pode ser facilmente redirecionado para outro descritor de arquivo #
Sergiy Kolodyazhnyy
Não tenho certeza, talvez funcione em todos os lugares, talvez funcione apenas na maioria dos sistemas. Ele funciona no meu e a página de manual diz que usar uma opção semelhante com cautela é tudo o que tenho certeza. Mas como eu já disse, o +1 é merecido para os pipes nomeados de qualquer maneira.
Byte Commander
Eu deveria comentar aqui, para manter um pouco de equilíbrio. Para mim, esta pode ser a única resposta canônica direta (1) que usa apenas a mesma ferramenta e implementou opções sem nenhuma ferramenta externa (2) Funciona no Ubuntu e é disso que trata a AU. : / infelizmente o OP parece abandonar esta questão.
user.dz
Qual é a vantagem de usar pipe nomeado em vez de arquivo regular aqui? Você não deseja excluir o tubo após o uso?
jarno 16/07/16
7

: DI não pode explicar !!! Se você puder entender o que eles estão dizendo na referência: Guia Avançado de Scripts em Bash: Capítulo 20. Redirecionamento de E / S , escreva uma nova resposta e eu darei 50rep

Recompensa foi dada, para obter explicação, consulte a resposta do ByteCommander . :) Isso faz parte da história.

exec 3>&1;
result=$(dialog --inputbox test 0 0 2>&1 1>&3);
exitcode=$?;
exec 3>&-;
echo $result $exitcode;

Fonte: Diálogo no bash não está capturando variáveis ​​corretamente
Referência: Guia Avançado de Scripts Bash: Capítulo 20. Redirecionamento de E / S

user.dz
fonte
essa oferta ainda é válida? Acho que poderia explicar o que você encontrou lá há um ano e meio ... :-)
Byte Commander
@ByteCommander, mas, no entanto, se você puder fornecer isso, eu darei a você, estarei com minhas palavras: D.
precisa saber é o seguinte
@ByteCommander, por favor, envie-me um ping depois de publicá-lo.
user.dz
11
Acabado! askubuntu.com/a/704616/367990 Espero que você entenda tudo e curta o "Eureka!" momento. :-D Deixe um comentário se algo ficou incerto.
Byte Commander
4

Isso funciona para mim:

#!/bin/bash
input=$(dialog --stdout --inputbox "What is your username?" 0 0)
retval=$?

case $retval in
${DIALOG_OK-0}) echo "Your username is '$input'.";;
${DIALOG_CANCEL-1}) echo "Cancel pressed.";;
${DIALOG_ESC-255}) echo "Esc pressed.";;
${DIALOG_ERROR-255}) echo "Dialog error";;
*) echo "Unknown error $retval"
esac

A página de manual dialogfala sobre --stdout:

Saída direta para a saída padrão. Esta opção é fornecida para compatibilidade com o Xdialog, no entanto, o uso em scripts portáteis não é recomendado, pois as maldições normalmente gravam suas atualizações de tela na saída padrão. Se você usar esta opção, a caixa de diálogo tentará reabrir o terminal para que ele possa gravar no visor. Dependendo da plataforma e do seu ambiente, isso pode falhar.

Alguém pode dizer em qual plataforma ou ambiente ele não funciona? O redirecionamento de dialogsaída para o 2>&1 >/dev/ttytrabalho funciona melhor então?

jarno
fonte
4

Caso outra pessoa tenha chegado aqui do Google e, embora essa pergunta solicite especificamente o bash, aqui está outra alternativa:

Você pode usar o zenity . O Zenity é um utilitário gráfico que pode ser usado dentro de scripts bash. Mas é claro que isso exigiria um servidor X, como indicado por user877329.

sudo apt-get install zenity

Então no seu script:

RETVAL=`zenity --entry --title="Hi" --text="What is your username"`

Link útil .

Wtower
fonte
3
A menos que não haja um servidor X
user877329
11
OP quer saber sobre dialog. É como se eu perguntasse "Como escrevo isso e aquilo em python?", Mas você me dá uma festinha - estou muito feliz que isso possa ser feito de maneira diferente, mas não é isso que estou perguntando
Sergiy Kolodyazhnyy
@Serg seu comentário é inválido, minha resposta não é: o utilitário fornece uma alternativa perfeitamente válida e simples à solução solicitada pelo OP.
Wtower 16/03/19
3

A resposta fornecida por Sneetsher é um pouco mais elegante, mas posso explicar o que está errado: O valor de $$ é diferente dentro dos backticks (porque ele inicia um novo shell e $$é o PID do shell atual). Você desejará colocar o nome do arquivo em uma variável e, em vez disso, consulte essa variável.

#!/bin/bash
t=$(mktemp -t inputbox.XXXXXXXXX) || exit
trap 'rm -f "$t"' EXIT         # remove temp file when done
trap 'exit 127' HUP STOP TERM  # remove if interrupted, too
dialog --inputbox \
    "What is your username?" 0 0 2>"$t"
retval=$?
input=$(cat "$t")  # Prefer $(...) over `...`
case $retval in
  0)    echo "Your username is '$input'";;
  1)    echo "Cancel pressed.";;
esac

Nesse caso, evitar o arquivo temporário seria uma solução melhor, mas haverá muitas situações em que você não poderá evitar um arquivo temporário.

triplo
fonte