Comment obtenir une entrée de boîte de dialogue dirigée vers une variable?

18

Je me suis enseigné moi-même le script bash et j'ai rencontré un problème. J'ai écrit un script pour prendre les entrées de l'utilisateur, en utilisant la commande 'read', et faire de cette entrée une variable à utiliser plus tard dans le script. Le script fonctionne, mais ...

Je voudrais pouvoir l'installer en utilisant 'dialogue'. J'ai découvert que

'dialog --inputbox' dirigera la sortie vers 'stderr' et pour obtenir cette entrée en tant que variable, vous devez la diriger vers un fichier puis la récupérer. Le code que j'ai trouvé pour expliquer cela est:

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

Je vois qu'il envoie le sdterr à /tmp/inputbox.tmp.$$ avec 2>, mais le fichier de sortie ressemble à 'inputbox.tmp.21661'. Lorsque j'essaie de récupérer le fichier, cela me donne une erreur. Je ne suis donc toujours pas en mesure d'obtenir l'entrée utilisateur de la --inputbox en tant que variable.

Exemple de script:

echo "  What app would you like to remove? "

read dead_app

sudo apt-get remove --purge $dead_app

Comme vous pouvez le voir, c'est un script de base. Est-il même possible d'obtenir la variable sous forme de mot dialog --inputbox?

emerikanbloke
la source
D'après mon expérience, le script fonctionne bien, si vous supprimez la ligne vide après la 2e ligne. Vous pouvez également utiliser la mktempcommande pour créer un fichier temporaire.
jarno

Réponses:

16

: DI ne peut pas l'expliquer !!! Si vous pouvez comprendre ce qu'ils disent dans Advanced Bash-Scripting Guide: Chapter 20. Redirection d'E / S , écrivez une nouvelle réponse et je vous donnerai 50rep :

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

Référence: la boîte de dialogue dans bash ne récupère pas correctement les variables

^ réponse de @Sneetsher (4 juil.2014 )

Comme demandé, j'essaierai d'expliquer ce que cet extrait de code fait ligne par ligne.

Notez que je vais le simplifier en omettant tous les ;points - virgules aux extrémités de la ligne, car ils ne sont pas nécessaires si nous écrivons une commande par ligne.

E / S - Flux:

Tout d'abord, vous devez comprendre les flux de communication. Il existe 10 flux, numérotés de 0 à 9:

  • Stream 0 ("STDIN"):
    "Entrée standard", le flux d'entrée par défaut pour lire les données du clavier.

  • Flux 1 ("STDOUT"):
    "Sortie standard", le flux de sortie par défaut utilisé pour afficher du texte normal dans le terminal.

  • Flux 2 ("STDERR"): "Erreur standard", le flux de sortie par défaut utilisé pour afficher les erreurs ou tout autre texte à des fins spéciales dans le terminal.

  • Streams 3-9: Streams
    supplémentaires et librement utilisables. Ils ne sont pas utilisés par défaut et n'existent que lorsque quelque chose tente de les utiliser.

Notez que tous les "flux" sont représentés en interne par des descripteurs de fichiers dans /dev/fd(qui est un lien symbolique vers /proc/self/fdlequel contient un autre lien symbolique pour chaque flux ... c'est un peu compliqué et pas important pour leur comportement, donc je m'arrête ici.). Les flux standard ont également /dev/stdin, /dev/stdoutet /dev/stderr(qui sont à nouveau des liens symboliques, etc ...).

Le script:

  • exec 3>&1

    Le Bash intégré execpeut être utilisé pour appliquer une redirection de flux au shell, ce qui signifie qu'il affecte toutes les commandes suivantes. Pour plus d'informations, exécutez help execdans votre terminal.

    Dans ce cas particulier, le flux 3 est redirigé vers le flux 1 (STDOUT), ce qui signifie que tout ce que nous envoyons au flux 3 plus tard apparaîtra dans notre terminal comme s'il était normalement imprimé sur STDOUT.

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

    Cette ligne se compose de plusieurs parties et structures syntaxiques:

    • result=$(...)
      Cette structure exécute la commande entre crochets et affecte la sortie (STDOUT) à la variable bash result. C'est lisible $result. Tout cela est décrit d'une manière ou d'une autre dans le looong veeeery man bash.

    • dialog --inputbox TEXT HEIGHT WIDTH
      Cette commande affiche une boîte TUI avec le TEXTE donné, un champ de saisie de texte et deux boutons OK et ANNULER. Si OK est sélectionné, la commande se termine avec le statut 0 et imprime le texte saisi dans STDERR, si CANCEL est sélectionné, il quittera avec le code 1 et n'imprimera rien. Pour plus d'informations, lisez man dialog.

    • 2>&1 1>&3
      Ce sont deux commandes de redirection. Ils seront interprétés de droite à gauche:

      1>&3 redirige le flux 1 de la commande (STDOUT) vers le flux personnalisé 3.

      2>&1 redirige ensuite le flux de commande 2 (STDERR) vers le flux 1 (STDOUT).

      Cela signifie que tout ce que la commande imprime sur STDOUT apparaît maintenant dans le flux 3, tandis que tout ce qui devait apparaître sur STDERR est maintenant redirigé vers STDOUT.

    Ainsi, toute la ligne affiche une invite de texte (sur STDOUT, qui a été redirigé vers le flux 3, que le shell redirige à nouveau vers STDOUT à la fin - voir la exec 3>&1commande) et attribue les données entrées (renvoyées via STDERR, puis redirigées vers STDOUT) à la variable Bash result.

  • exitcode=$?

    Ce code récupère le code de sortie de la commande précédemment exécutée (ici depuis dialog) via la variable Bash réservée $?(contient toujours le dernier code de sortie) et le stocke simplement dans notre propre variable Bash exitcode. Il peut être relu $exitcode. Vous pouvez rechercher plus d'informations à ce sujet dans man bash, mais cela peut prendre un certain temps ...

  • exec 3>&-

    Le Bash intégré execpeut être utilisé pour appliquer une redirection de flux au shell, ce qui signifie qu'il affecte toutes les commandes suivantes. Pour plus d'informations, exécutez help execdans votre terminal.

    Dans ce cas particulier, le flux 3 est redirigé vers "flux -", ce qui signifie simplement qu'il doit être fermé. Désormais, les données envoyées au flux 3 ne seront plus redirigées nulle part.

  • echo $result $exitcode

    Cette simple echocommande (plus d'informations sur man echo) imprime simplement le contenu des deux variables Bash resultet exitcodedans STDOUT. Comme nous n'avons plus de redirections de flux explicites ou implicites ici, elles apparaîtront vraiment sur STDOUT et seront donc simplement affichées dans le terminal. Quel miracle! ;-)

Sommaire:

Tout d'abord, nous configurons le shell pour rediriger tout ce que nous envoyons vers le flux personnalisé 3 vers STDOUT, afin qu'il apparaisse dans notre terminal.

Ensuite, nous exécutons la dialogcommande, redirige son STDOUT d'origine vers notre flux personnalisé 3, car il doit être affiché à la fin, mais nous devons temporairement utiliser le flux STDOUT pour autre chose.
Nous redirige le STDERR d'origine de la commande, où l'entrée utilisateur de la fenêtre de dialogue est retournée, vers STDOUT par la suite.
Maintenant, nous pouvons capturer le STDOUT (qui contient les données redirigées de STDERR) et le stocker dans notre variable $result. Il contient maintenant l'entrée utilisateur souhaitée!

Nous voulons également le dialogcode de sortie de la commande, qui nous indique si OK ou CANCEL a été cliqué. Cette valeur est présentée dans la variable Bash réservée $?et nous la copions simplement dans notre propre variable $exitcode.

Après cela, nous fermons à nouveau le flux 3, car nous n'en avons plus besoin, pour arrêter d'autres redirections.

Enfin, nous sortons normalement le contenu des deux variables $result(l'entrée utilisateur de la fenêtre de dialogue) et $exitcode(0 pour OK, 1 pour CANCEL) sur le terminal.

Byte Commander
la source
Je pense que l'utilisation execest inutilement compliquée. Pourquoi ne pas simplement --stdoutchoisir dialogou rediriger sa sortie par 2>&1 >/dev/tty?
jarno
Veuillez voir ma réponse .
jarno
3
Très bonne réponse! Cependant, je crois que vous avez une note qui est incorrecte - vous dites que "Ils seront interprétés de droite à gauche" mais je crois que ce n'est pas vrai. D'après le manuel bash gnu.org/software/bash/manual/html_node/Redirections.html, il indique que les redirections ont lieu au fur et à mesure qu'elles sont rencontrées (c'est-à-dire de gauche à droite)
ralfthewise
14

Utilisation des propres outils de la boîte de dialogue: --output-fd flag

Si vous lisez la page de manuel pour la boîte de dialogue, il existe une option --output-fdqui vous permet de définir explicitement où va la sortie (STDOUT 1, STDERR 2), au lieu d'aller par défaut à STDERR.

dialogCi-dessous, vous pouvez me voir exécuter un exemple de commande, en précisant explicitement que la sortie doit aller au descripteur de fichier 1, ce qui me permet de l'enregistrer dans MYVAR.

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

entrez la description de l'image ici

Utilisation de canaux nommés

Une approche alternative qui a beaucoup de potentiel caché consiste à utiliser quelque chose appelé pipe nommée .

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

entrez la description de l'image ici

Un aperçu plus approfondi de la réponse de user.dz avec une approche alternative

La réponse originale de user.dz et l'explication de ByteCommander à ce sujet fournissent toutes deux une bonne solution et un aperçu de ce qu'il fait. Cependant, je crois qu'une analyse plus approfondie pourrait être bénéfique pour expliquer pourquoi cela fonctionne.

Tout d'abord, il est important de comprendre deux choses: quel est le problème que nous essayons de résoudre et quels sont les rouages ​​sous-jacents des mécanismes shell avec lesquels nous avons affaire. La tâche consiste à capturer la sortie d'une commande via la substitution de commande. Sous une vue simpliste que tout le monde connaît, les substitutions de commande capturent la stdoutcommande et la laissent être réutilisée par autre chose. Dans ce cas, la result=$(...)partie doit enregistrer la sortie de la commande désignée par ...dans une variable appelée result.

Sous le capot, la substitution de commandes est en fait implémentée sous forme de canal, où il existe un processus enfant (la commande réelle qui s'exécute) et un processus de lecture (qui enregistre la sortie dans une variable). Cela est évident avec une simple trace d'appels système. Notez que le descripteur de fichier 3 est l'extrémité de lecture du canal, tandis que 4 est l'extrémité d'écriture. Pour le processus enfant de echo, qui écrit dans son stdout- le descripteur de fichier 1, ce descripteur de fichier est en fait une copie du descripteur de fichier 4, qui est la fin d'écriture du canal. Notez que cela stderrne joue pas de rôle ici, simplement parce que c'est un tuyau qui se connecte stdoutuniquement.

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

Revenons à la réponse originale pendant une seconde. Puisque nous savons maintenant que l' dialogécriture de la zone TUI stdout, la réponse stderret la substitution de commandes stdoutsont canalisées ailleurs, nous avons déjà une partie de la solution - nous devons recâbler les descripteurs de fichiers de manière à ce stderrqu'ils soient canalisés vers le processus de lecture. C'est la 2>&1partie de la réponse. Cependant, que faisons-nous avec la boîte TUI?

C'est là que le descripteur de fichier 3 entre en jeu. L' dup2()appel système nous permet de dupliquer les descripteurs de fichier, en les faisant effectivement référence au même endroit, mais nous pouvons les manipuler séparément. Les descripteurs de fichiers des processus auxquels un terminal de contrôle est connecté pointent en fait vers un périphérique terminal spécifique. Cela est évident si vous le faites

$ 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

/dev/pts/5est mon pseudo-terminal actuel. Ainsi, si nous pouvons en quelque sorte enregistrer cette destination, nous pouvons toujours écrire la boîte TUI sur l'écran du terminal. Voilà ce qui exec 3>&1fait. Lorsque vous appelez une commande avec redirection command > /dev/nullpar exemple, le shell transmet son descripteur de fichier stdout puis l'utilise dup2()pour écrire ce descripteur de fichier /dev/null. La execcommande exécute quelque chose de similaire auxdup2() descripteurs de fichiers pour toute la session shell, ce qui fait que toute commande hérite d'un descripteur de fichier déjà redirigé. Même chose avec exec 3>&1. Le descripteur de fichier 3fera désormais référence à / pointera vers le terminal de contrôle, et toute commande exécutée dans cette session shell le saura.

Donc, quand cela result=$(dialog --inputbox test 0 0 2>&1 1>&3);se produit, le shell crée un tube pour que la boîte de dialogue écrive, mais aussi 2>&1fera d'abord dupliquer le descripteur de fichier de la commande 2 sur le descripteur de fichier d'écriture de ce tube (faisant ainsi sortir la sortie à la fin du tube et dans la variable) , tandis que le descripteur de fichier 1 sera dupliqué sur 3. Cela fera que le descripteur de fichier 1 se référera toujours au terminal de contrôle, et la boîte de dialogue TUI s'affichera à l'écran.

Maintenant, il y a en fait un raccourci pour le terminal de contrôle actuel du processus, qui est /dev/tty. Ainsi, la solution peut être simplifiée sans utiliser de descripteurs de fichiers, simplement en:

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

Points clés à retenir:

  • les descripteurs de fichiers sont hérités du shell par chaque commande
  • la substitution de commande est implémentée en tant que pipe
  • les descripteurs de fichiers dupliqués feront référence au même endroit que l'original, mais nous pouvons manipuler chaque descripteur de fichier séparément

Voir également

Sergiy Kolodyazhnyy
la source
La page de manuel indique également que cette --stdoutoption peut être dangereuse et échoue facilement sur certains systèmes, et je pense que --output-fd 1c'est la même chose: --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.- Cependant, l'idée de pipe nommée est cool!
Byte Commander
@ByteCommander "May fail" n'est pas très convaincant, car cela ne fournit pas d'exemples. De plus, ils ne mentionnent rien --output-fd, ce qui est l'option que j'ai utilisée ici, non --stdout. Deuxièmement, la boîte de dialogue est dessinée en premier sur stdout, la sortie renvoyée est la deuxième. Nous ne faisons pas ces deux choses en même temps. Cependant, --output-fd n'exige pas spécifiquement que l'on utilise fd 1 (STDOUT). Il peut facilement être redirigé vers un autre descripteur de fichier
Sergiy Kolodyazhnyy
Je ne suis pas sûr, peut-être que cela fonctionne partout, peut-être que cela ne fonctionne que sur la plupart des systèmes. Cela fonctionne sur le mien et la page de manuel dit d'utiliser une option similaire avec prudence est tout ce que je sais avec certitude. Mais comme je l'ai déjà dit, le +1 est de toute façon mérité pour les pipes nommées.
Byte Commander
Je devrais commenter ici, pour maintenir un certain équilibre. Pour moi, cela peut être la seule réponse canonique directe (1) il utilise uniquement le même outil et il a implémenté des options sans aucun outil externe (2) Cela fonctionne dans Ubuntu et c'est tout ce que l'AU est. : / malheureusement l'OP semble abandonner cette question.
user.dz
Quel est l'avantage d'utiliser le canal nommé au lieu du fichier normal ici? Vous ne souhaitez pas supprimer le tuyau après utilisation?
jarno
7

: DI ne peut pas l'expliquer !!! Si vous pouvez comprendre ce qu'ils disent dans la référence: Advanced Bash-Scripting Guide: Chapter 20. Redirection d'E / S , écrivez une nouvelle réponse et je vous donnerai 50rep

Une prime a été donnée, pour des explications, voir la réponse de ByteCommander . :) Ceci fait partie de l'histoire.

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

La source: boîte de dialogue dans bash n'extrait pas correctement les variables
Référence: Guide avancé de script de Bash: Chapitre 20. Redirection d'E / S

user.dz
la source
cette offre est-elle toujours valable? Je pense que je pourrais expliquer ce que vous y avez trouvé il y a un an et demi ... :-)
Byte Commander
@ByteCommander, mais si vous pouvez fournir cela, je vous le donnerai, je serai sur mes mots: D.
user.dz
@ByteCommander, s'il vous plaît, envoyez-moi un ping après l'avoir posté.
user.dz
1
Fini! askubuntu.com/a/704616/367990 J'espère que vous comprenez tout et que vous appréciez le "Eureka!" moment. :-D Laisser un commentaire si quelque chose n'était pas clair.
Byte Commander
4

Cela fonctionne pour moi:

#!/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 page de manuel de dialograconte --stdout:

Sortie directe vers la sortie standard. Cette option est fournie pour la compatibilité avec Xdialog, cependant son utilisation dans des scripts portables n'est pas recommandée, car curses écrit normalement ses mises à jour d'écran sur la sortie standard. Si vous utilisez cette option, la boîte de dialogue tente de rouvrir le terminal pour qu'il puisse écrire sur l'écran. Selon la plate-forme et votre environnement, cela peut échouer.

Quelqu'un peut-il dire dans quelle plate-forme ou environnement cela ne fonctionne pas? La redirection de la dialogsortie vers 2>&1 >/dev/ttyfonctionne-t-elle mieux alors?

jarno
la source
4

Dans le cas où quelqu'un d'autre a atterri ici de Google et bien que cette question demande spécifiquement bash, voici une autre alternative:

Vous pouvez utiliser la zénité . Zenity est un utilitaire graphique qui peut être utilisé dans des scripts bash. Mais bien sûr, cela nécessiterait un serveur X, comme l'a souligné à juste titre user877329.

sudo apt-get install zenity

Puis dans votre script:

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

Lien utile .

Wtower
la source
3
Sauf s'il n'y a pas de serveur X
user877329
1
OP veut savoir dialog. C'est comme si je venais vous demander "Comment puis-je écrire ceci et cela en python?", Mais vous me donnez bash - je suis très heureux que cela puisse être fait de manière différente, mais ce n'est pas ce que je demande
Sergiy Kolodyazhnyy
@Serg votre commentaire n'est pas valide, ma réponse n'est pas: l'utilitaire fournit une alternative parfaitement valable et simple à la solution demandée par l'OP.
Wtower
3

La réponse fournie par Sneetsher est un peu plus élégante, mais je peux expliquer ce qui ne va pas: la valeur de $$est différente à l'intérieur des astuces (car elle démarre un nouveau shell et $$est le PID du shell actuel). Vous voudrez mettre le nom du fichier dans une variable, puis vous référer à cette variable partout à la place.

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

Dans ce cas, éviter le fichier temporaire serait une meilleure solution, mais il y aura de nombreuses situations où vous ne pourrez pas éviter un fichier temporaire.

tripleee
la source