¿Cómo obtener la entrada del cuadro de diálogo dirigida a una variable?

18

Me he estado enseñando a mí mismo bash scripting y me he encontrado con un problema. He escrito una secuencia de comandos para recibir información del usuario, utilizando el comando 'leer', y convertir esa entrada en una variable para usar más adelante en la secuencia de comandos. El guión funciona, pero ...

Me gustaría poder configurarlo usando 'dialog'. Descubrí que

'dialog --inputbox' dirigirá la salida a 'stderr' y para obtener esa entrada como una variable, debe dirigirla a un archivo y luego recuperarla. El código que encontré para explicar esto es:

#!/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

Veo que está enviando el sdterr a /tmp/inputbox.tmp.$$ con 2>, pero el archivo de salida se ve como 'inputbox.tmp.21661'. Cuando intento capturar el archivo, me da un error. Por lo tanto, todavía no puedo obtener la entrada del usuario desde --inputbox como una variable.

Script de ejemplo:

echo "  What app would you like to remove? "

read dead_app

sudo apt-get remove --purge $dead_app

Como puede ver, es un script básico. ¿Es posible obtener la variable como una palabra dialog --inputbox?

emerikanbloke
fuente
En mi experiencia, el script funciona bien, si elimina la línea vacía después de la segunda línea. Alternativamente, puede usar el mktempcomando para crear un archivo temporal.
jarno

Respuestas:

16

: DI no puedo explicarlo !!! Si puede entender lo que dicen en la Guía avanzada de secuencias de comandos Bash: Capítulo 20. Redirección de E / S , escriba una nueva respuesta y le daré 50 rep :

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

Referencia: El diálogo en bash no está tomando variables correctamente

^ respuesta de @Sneetsher (4 de julio de 2014)

Según lo solicitado, intentaré explicar lo que está haciendo este fragmento línea por línea.

Tenga en cuenta que lo simplificaré omitiendo todos los ;puntos y comas en los extremos de la línea, porque no son necesarios si escribimos un comando por línea.

E / S - Streams:

Primero, debe comprender los flujos de comunicación. Hay 10 transmisiones, numeradas del 0 al 9:

  • Secuencia 0 ("STDIN"):
    "Entrada estándar", la secuencia de entrada predeterminada para leer datos desde el teclado.

  • Secuencia 1 ("STDOUT"):
    "Salida estándar", la secuencia de salida predeterminada utilizada para mostrar texto normal en el terminal.

  • Secuencia 2 ("STDERR"): "Error estándar", la secuencia de salida predeterminada utilizada para mostrar errores u otro texto para fines especiales en el terminal.

  • Secuencias 3-9: Secuencias
    adicionales de uso libre. No se usan por defecto y no existen hasta que algo intenta usarlos.

Tenga en cuenta que todas las "transmisiones" están representadas internamente por descriptores de archivo /dev/fd(que es un enlace simbólico /proc/self/fdque contiene otro enlace simbólico para cada transmisión ... es un poco complicado y no es importante para su comportamiento, así que me detengo aquí). Las secuencias estándar también tienen /dev/stdin, /dev/stdouty /dev/stderr(que son enlaces simbólicos de nuevo, etc.).

La secuencia de comandos:

  • exec 3>&1

    El Bash incorporado execse puede usar para aplicar una redirección de flujo al shell, lo que significa que afecta a todos los siguientes comandos. Para más información, corre help execen tu terminal.

    En este caso especial, la secuencia 3 se redirige a la secuencia 1 (STDOUT), lo que significa que todo lo que enviemos a la secuencia 3 más tarde aparecerá en nuestro terminal como si normalmente se imprimiera en STDOUT.

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

    Esta línea consta de muchas partes y estructuras sintácticas:

    • result=$(...)
      Esta estructura ejecuta el comando entre paréntesis y asigna la salida (STDOUT) a la variable bash result. Es legible a través de $result. Todo esto se describe de alguna manera en muuuucho muuuucho man bash.

    • dialog --inputbox TEXT HEIGHT WIDTH
      Este comando muestra un cuadro TUI con el TEXTO dado, un campo de entrada de texto y dos botones Aceptar y CANCELAR. Si se selecciona OK, el comando sale con el estado 0 e imprime el texto ingresado en STDERR, si CANCEL se selecciona, saldrá con el código 1 y no imprimirá nada. Para más información, lea man dialog.

    • 2>&1 1>&3
      Estos son dos comandos de redireccionamiento. Serán interpretados de derecha a izquierda:

      1>&3 redirige la secuencia del comando 1 (STDOUT) a la secuencia personalizada 3.

      2>&1 luego redirige la secuencia 2 del comando (STDERR) a la secuencia 1 (STDOUT).

      Eso significa que todo lo que el comando imprime en STDOUT ahora aparece en la secuencia 3, mientras que todo lo que estaba destinado a aparecer en STDERR ahora se redirige a STDOUT.

    Entonces, la línea completa muestra un mensaje de texto (en STDOUT, que se redirigió a la secuencia 3, que el shell vuelve a redirigir nuevamente a STDOUT al final - vea el exec 3>&1comando) y asigna los datos ingresados ​​(devueltos a través de STDERR, luego redirigidos a STDOUT) a la variable Bash result.

  • exitcode=$?

    Este código recupera el código de salida del comando ejecutado previamente (aquí desde dialog) a través de la variable Bash reservada $?(siempre contiene el último código de salida) y simplemente lo almacena en nuestra propia variable Bash exitcode. Se puede leer de $exitcodenuevo. Puede buscar más información sobre esto en man bash, pero eso puede llevar un tiempo ...

  • exec 3>&-

    El Bash incorporado execse puede usar para aplicar una redirección de flujo al shell, lo que significa que afecta a todos los siguientes comandos. Para más información, corre help execen tu terminal.

    En este caso especial, la secuencia 3 se redirige a "secuencia -", lo que significa que debe cerrarse. Los datos enviados a la transmisión 3 ya no se redirigirán a ninguna parte a partir de ahora.

  • echo $result $exitcode

    Este echocomando simple (más información man echo) simplemente imprime el contenido de las dos variables Bash resulty exitcodeSTDOUT. Como ya no tenemos redirecciones de flujo explícitas o implícitas aquí, realmente aparecerán en STDOUT y, por lo tanto, simplemente se mostrarán en el terminal. ¡Que milagro! ;-)

Resumen:

Primero, configuramos el shell para redirigir todo lo que enviamos a la secuencia personalizada 3 de vuelta a STDOUT, para que aparezca en nuestro terminal.

Luego ejecutamos el dialogcomando, redirigimos su STDOUT original a nuestra secuencia personalizada 3, porque debe mostrarse al final, pero necesitamos usar temporalmente la secuencia STDOUT para otra cosa.
Redirigimos el STDERR original del comando, donde se devuelve la entrada del usuario de la ventana de diálogo, a STDOUT después.
Ahora podemos capturar el STDOUT (que contiene los datos redirigidos de STDERR) y almacenarlo en nuestra variable $result. Contiene la entrada de usuario deseada ahora!

También queremos el dialogcódigo de salida del comando, que nos muestra si se hizo clic en Aceptar o CANCELAR. Este valor se presenta en la variable Bash reservada $?y simplemente lo copiamos a nuestra propia variable $exitcode.

Después de eso, cerramos la transmisión 3 nuevamente, ya que ya no la necesitamos, para detener futuras redirecciones de la misma.

Finalmente, normalmente enviamos el contenido de ambas variables $result(la entrada del usuario de la ventana de diálogo) y $exitcode(0 para OK, 1 para CANCEL) al terminal.

Byte Commander
fuente
Creo que usar execes innecesariamente complicado. ¿Por qué no solo nosotros --stdoutoptamos por dialogo redirigimos su salida 2>&1 >/dev/tty?
jarno
Por favor mira mi respuesta .
jarno
3
¡Gran respuesta! Sin embargo, creo que tiene una nota que es incorrecta: dice que "se interpretarán de derecha a izquierda", pero creo que eso no es cierto. Desde el manual de bash gnu.org/software/bash/manual/html_node/Redirections.html indica que las redirecciones tienen lugar a medida que se encuentran (es decir, de izquierda a derecha)
ralfthewise
14

Usando las herramientas propias del diálogo: --output-fd flag

Si lee la página de manual para el diálogo, hay una opción --output-fdque le permite establecer explícitamente dónde va la salida (STDOUT 1, STDERR 2), en lugar de ir por defecto a STDERR.

A continuación puede verme ejecutando un dialogcomando de muestra , indicando explícitamente que la salida debe ir al descriptor de archivo 1, lo que me permite guardarlo en MYVAR.

MYVAR=$(dialog --inputbox "THIS OUTPUT GOES TO FD 1" 25 25 --output-fd 1)

ingrese la descripción de la imagen aquí

Usar tuberías con nombre

El enfoque alternativo que tiene mucho potencial oculto es usar algo conocido como tubería con nombre .

#!/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 

ingrese la descripción de la imagen aquí

Una descripción más detallada de la respuesta de user.dz con un enfoque alternativo

La respuesta original de user.dz y la explicación de ByteCommander de eso proporcionan una buena solución y una visión general de lo que hace. Sin embargo, creo que un análisis más profundo podría ser beneficioso para explicar por qué funciona.

En primer lugar, es importante comprender dos cosas: cuál es el problema que estamos tratando de resolver y cuáles son los mecanismos subyacentes de los mecanismos de shell con los que estamos tratando. La tarea es capturar la salida de un comando mediante la sustitución de comandos. Bajo una visión general simplista que todos conocen, las sustituciones de comandos capturan el stdoutcomando y lo dejan ser reutilizado por otra cosa. En este caso, la result=$(...)parte debe guardar la salida de cualquier comando designado por ...en una variable llamada result.

Debajo del capó, la sustitución de comandos se implementa realmente como una tubería, donde hay un proceso secundario (el comando real que se ejecuta) y el proceso de lectura (que guarda la salida en variable). Esto es evidente con un simple rastro de llamadas al sistema. Observe que el descriptor de archivo 3 es el final de lectura de la tubería, mientras que 4 es el final de escritura. Para el proceso secundario de echo, que escribe en él stdout: el descriptor de archivo 1, ese descriptor de archivo es en realidad una copia del descriptor de archivo 4, que es el final de escritura de la tubería. Tenga en cuenta que stderrno está jugando un papel aquí, simplemente porque es una tubería que stdoutsolo se conecta .

$ 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 +++

Volvamos a la respuesta original por un segundo. Como ahora sabemos que dialogescribe el cuadro TUI stdout, responde stderry dentro de la sustitución de comandos stdoutse canaliza a otro lugar, ya tenemos parte de la solución: necesitamos volver a cablear los descriptores de archivos de tal manera que stderrse canalicen al proceso del lector. Esta es la 2>&1parte de la respuesta. Sin embargo, ¿qué hacemos con la caja TUI?

Ahí es donde entra en dup2()juego el descriptor de archivos 3. La llamada al sistema nos permite duplicar los descriptores de archivos, haciéndolos referir efectivamente al mismo lugar, sin embargo, podemos manipularlos por separado. Los descriptores de archivos de procesos que tienen un terminal de control conectado en realidad apuntan a un dispositivo terminal específico. Esto es evidente si lo haces

$ 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

¿Dónde /dev/pts/5está mi dispositivo pseudo-terminal actual? Por lo tanto, si de alguna manera podemos guardar este destino, aún podemos escribir el cuadro TUI en la pantalla del terminal. Eso es lo que exec 3>&1hace. Cuando llama a un comando con redirección, command > /dev/nullpor ejemplo, el shell pasa su descriptor de archivo estándar y luego lo utiliza dup2()para escribir ese descriptor de archivo /dev/null. El execcomando realiza algo similar adup2() los descriptores de archivo para toda la sesión de shell, haciendo que cualquier comando herede el descriptor de archivo ya redirigido. Lo mismo con exec 3>&1. El descriptor de archivo 3ahora se referirá / señalará al terminal de control, y cualquier comando que se ejecute en esa sesión de shell lo sabrá.

Entonces, cuando result=$(dialog --inputbox test 0 0 2>&1 1>&3);ocurre, el shell crea una tubería para que el diálogo escriba, pero también 2>&1hará que el descriptor de archivo 2 del comando se duplique en el descriptor de archivo de escritura de esa tubería (haciendo que la salida vaya al extremo de lectura de la tubería y dentro de la variable) , mientras que el descriptor de archivo 1 se duplicará en 3. Esto hará que el descriptor de archivo 1 todavía se refiera al terminal de control, y el cuadro de diálogo TUI aparecerá en la pantalla.

Ahora, en realidad hay una abreviatura para el terminal de control actual del proceso, que es /dev/tty. Por lo tanto, la solución se puede simplificar sin el uso de descriptores de archivo, simplemente en:

result=$(dialog --inputbox test 0 0 2>&1 1>/dev/tty);
echo "$result"

Cosas clave para recordar:

  • los descriptores de archivo son heredados de la shell por cada comando
  • la sustitución de comandos se implementa como tubería
  • los descriptores de archivos duplicados se referirán al mismo lugar que los originales, pero podemos manipular cada descriptor de archivo por separado

Ver también

Sergiy Kolodyazhnyy
fuente
La página de manual también dice que la --stdoutopción puede ser peligrosa y está fallando fácilmente en algunos sistemas, y creo que --output-fd 1está haciendo lo mismo: --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.- Sin embargo, ¡la idea de tubería nombrada es genial!
Byte Commander
@ByteCommander "Puede fallar" no es muy convincente, ya que esto no proporciona ejemplos. Además, no mencionan nada sobre --output-fd, que es la opción que usé aquí, no --stdout. En segundo lugar, el cuadro de diálogo se dibuja en stdout primero, el resultado devuelto es el segundo. No hacemos estas dos cosas al mismo tiempo. Sin embargo, --output-fd no requiere específicamente que uno use fd 1 (STDOUT). Se puede redirigir fácilmente a otro descriptor de archivo
Sergiy Kolodyazhnyy
No estoy seguro, tal vez funcione en todas partes, tal vez solo funcione en la mayoría de los sistemas. Funciona en la mía y la página de manual dice que usar una opción similar con precaución es todo lo que sé con certeza. Pero como ya dije, el +1 se merece para las tuberías con nombre de todos modos.
Byte Commander
Debo comentar aquí, para mantener un cierto equilibrio. Para mí, esta puede ser la única respuesta canónica directa (1) usa solo la misma herramienta e implementó opciones sin ninguna herramienta externa (2) Funciona en Ubuntu y de eso se trata AU. : / lamentablemente el OP parece abandonar esta pregunta.
user.dz
¿Cuál es la ventaja de usar una tubería con nombre en lugar de un archivo normal aquí? ¿No quieres eliminar la tubería después de usarla?
jarno
7

: DI no puedo explicarlo !!! Si puede entender lo que están diciendo en la referencia: Guía avanzada de secuencias de comandos Bash: Capítulo 20. Redirección de E / S , escriba una nueva respuesta y le daré 50 rep.

Se dio recompensa, para una explicación, ver la respuesta de ByteCommander . :) Esto es parte de la historia.

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

Fuente: Diálogo en bash no está tomando variables correctamente
Referencia: Guía avanzada de secuencias de comandos Bash: Capítulo 20. Redirección de E / S

user.dz
fuente
¿Esa oferta sigue siendo válida? Creo que podría explicar lo que encontraste allí hace un año y medio ... :-)
Byte Commander
@ByteCommander, pero si puedes proporcionar eso, te lo daré, estaré en mis palabras: D.
user.dz
@ByteCommander, por favor, envíame un ping después de publicarlo.
user.dz
1
¡Terminado! askubuntu.com/a/704616/367990 Espero que entiendas todo y disfrutes el "Eureka!" momento. :-D Deja un comentario si algo quedó claro.
Byte Commander
4

Esto funciona para mi:

#!/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

La página de manual de dialoginforma sobre --stdout:

Salida directa a la salida estándar. Esta opción se proporciona para compatibilidad con Xdialog, sin embargo, no se recomienda su uso en scripts portátiles, ya que curses normalmente escribe sus actualizaciones de pantalla en la salida estándar. Si usa esta opción, el diálogo intenta volver a abrir el terminal para que pueda escribir en la pantalla. Dependiendo de la plataforma y su entorno, eso puede fallar.

¿Alguien puede decir en qué plataforma o entorno no funciona? ¿Redirigir la dialogsalida para 2>&1 >/dev/ttyque funcione mejor entonces?

jarno
fuente
4

En caso de que alguien más haya aterrizado aquí desde Google, y aunque esta pregunta pide específicamente bash, aquí hay otra alternativa:

Puedes usar zenity . Zenity es una utilidad gráfica que se puede usar dentro de los scripts de bash. Pero, por supuesto, esto requeriría un servidor X como el usuario 877329 señaló con razón.

sudo apt-get install zenity

Luego en tu guión:

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

Enlace útil .

Wtower
fuente
3
A menos que no haya un servidor X
user877329
1
OP quiere saber sobre dialog. Es como si venga y te pregunte "¿Cómo escribo esto y aquello en Python?", Pero me das un golpe, estoy muy feliz de que esto se pueda hacer de otra manera, pero eso no es lo que te pido
Sergiy Kolodyazhnyy
@Serg su comentario no es válido, mi respuesta no es: La utilidad proporciona una alternativa perfectamente válida y simple a la solución solicitada por el OP.
Wtower
3

La respuesta proporcionada por Sneetsher es algo más elegante, pero puedo explicar lo que está mal: el valor de $$es diferente dentro de los backticks (porque inicia un nuevo shell y $$es el PID del shell actual). Deberá poner el nombre del archivo en una variable, luego, consulte esa variable en su lugar.

#!/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

En este caso, evitar el archivo temporal sería una mejor solución, pero habrá muchas situaciones en las que no podrá evitar un archivo temporal.

tripleee
fuente