Wie erhalte ich eine Dialogbox-Eingabe für eine Variable?

18

Ich habe mir selbst das Bash-Scripting beigebracht und bin auf ein Problem gestoßen. Ich habe ein Skript geschrieben, um mithilfe des Befehls "read" Eingaben vom Benutzer zu übernehmen und diese Eingabe zu einer Variablen zu machen, die später im Skript verwendet werden soll. Das Skript funktioniert, aber ...

Ich möchte es mit 'dialog' einrichten können. Ich habe herausgefunden, dass

'dialog --inputbox' leitet die Ausgabe an 'stderr' weiter. Um diese Eingabe als Variable zu erhalten, müssen Sie sie in eine Datei umleiten und dann abrufen. Der Code, den ich gefunden habe, um dies zu erklären, ist:

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

Ich sehe, dass sdterr mit 2> an /tmp/inputbox.tmp.$$ gesendet wird, aber die Ausgabedatei sieht aus wie 'inputbox.tmp.21661'. Wenn ich versuche, die Datei zu durchsuchen, erhalte ich eine Fehlermeldung. Daher kann ich die Benutzereingabe immer noch nicht als Variable aus der --Eingabebox abrufen.

Beispielskript:

echo "  What app would you like to remove? "

read dead_app

sudo apt-get remove --purge $dead_app

Wie Sie sehen, handelt es sich um ein einfaches Skript. Ist es überhaupt möglich, die Variable als Wort zu erhalten dialog --inputbox?

emerikanbloke
quelle
Nach meiner Erfahrung funktioniert das Skript einwandfrei, wenn Sie die Leerzeile nach der 2. Zeile entfernen. Alternativ können Sie den mktempBefehl verwenden, um eine temporäre Datei zu erstellen.
Jarno

Antworten:

16

: DI kann es nicht erklären !!! Wenn Sie verstehen können, was sie im Advanced Bash-Scripting Guide sagen : Kapitel 20. E / A-Umleitung , schreiben Sie eine neue Antwort und ich gebe Ihnen 50rep :

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

Referenz: Der Dialog in der Bash erfasst die Variablen nicht richtig

^ Antwort von @Sneetsher (4. Juli 2014)

Wie gewünscht werde ich versuchen zu erklären, was dieses Snippet Zeile für Zeile macht.

Beachten Sie, dass ich es vereinfachen werde, indem ich alle ;Semikolons an den Zeilenenden weglasse , da sie nicht erforderlich sind, wenn wir einen Befehl pro Zeile schreiben.

I / O-Streams:

Zunächst müssen Sie die Kommunikationsströme verstehen. Es gibt 10 Streams, die von 0 bis 9 nummeriert sind:

  • Stream 0 ("STDIN"):
    "Standardeingabe", der Standardeingabestream zum Lesen von Daten von der Tastatur.

  • Stream 1 ("STDOUT"):
    "Standardausgabe", der Standardausgabestream, der verwendet wird, um normalen Text im Terminal anzuzeigen .

  • Stream 2 ("STDERR"): "Standardfehler", der Standardausgabestream, der zum Anzeigen von Fehlern oder anderem Text für spezielle Zwecke im Terminal verwendet wird.

  • Streams 3-9:
    Zusätzliche, frei verwendbare Streams. Sie werden standardmäßig nicht verwendet und existieren erst, wenn versucht wird, sie zu verwenden.

Beachten Sie, dass alle "Streams" intern durch Dateideskriptoren dargestellt werden /dev/fd(dies ist ein symbolischer Link, /proc/self/fdder für jeden Stream einen anderen symbolischen Link enthält ... dies ist etwas kompliziert und für ihr Verhalten nicht wichtig, daher höre ich hier auf.). Die Standard-Streams haben /dev/stdinauch /dev/stdoutund /dev/stderr(die wiederum symbolische Links sind, etc ...).

Das Drehbuch:

  • exec 3>&1

    Die integrierte Bash-Funktion execkann verwendet werden, um eine Stream-Umleitung auf die Shell anzuwenden. Dies bedeutet, dass alle folgenden Befehle davon betroffen sind. Weitere Informationen erhalten Sie help execin Ihrem Terminal.

    In diesem speziellen Fall wird der Stream 3 auf Stream 1 (STDOUT) umgeleitet. Das bedeutet, dass alles, was wir später an Stream 3 senden, in unserem Terminal so angezeigt wird, als ob es normalerweise auf STDOUT gedruckt worden wäre.

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

    Diese Zeile besteht aus vielen Teilen und syntaktischen Strukturen:

    • result=$(...)
      Diese Struktur führt den Befehl in Klammern aus und weist der bash-Variablen die Ausgabe (STDOUT) zu result. Es ist lesbar durch $result. Das alles ist irgendwie schon sehr lange beschrieben man bash.

    • dialog --inputbox TEXT HEIGHT WIDTH
      Dieser Befehl zeigt eine TUI-Box mit dem angegebenen TEXT, einem Texteingabefeld und zwei Schaltflächen OK und CANCEL. Wenn OK ausgewählt wird, wird der Befehl mit dem Status 0 beendet und der eingegebene Text an STDERR ausgegeben. Wenn ABBRECHEN ausgewählt wird, wird der Befehl mit dem Code 1 beendet und es wird nichts gedruckt. Weitere Informationen finden Sie unter man dialog.

    • 2>&1 1>&3
      Dies sind zwei Umleitungsbefehle. Sie werden von rechts nach links interpretiert:

      1>&3 Leitet den Stream 1 (STDOUT) des Befehls zum benutzerdefinierten Stream 3 um.

      2>&1 leitet danach den Stream 2 (STDERR) des Befehls zu Stream 1 (STDOUT) um.

      Das bedeutet, dass alles, was der Befehl an STDOUT ausgibt, jetzt in Stream 3 angezeigt wird, während alles, was in STDERR angezeigt werden soll, jetzt an STDOUT umgeleitet wird.

    Die gesamte Zeile zeigt also eine Eingabeaufforderung an (auf STDOUT, die auf Stream 3 umgeleitet wurde, die die Shell am Ende erneut auf STDOUT umleitet - siehe exec 3>&1Befehl) und weist die eingegebenen Daten zu (über STDERR zurückgegeben, dann auf STDOUT umgeleitet). auf die Variable Bash result.

  • exitcode=$?

    Dieser Code ruft den Exit-Code des zuvor ausgeführten Befehls (hier von dialog) über die reservierte Bash-Variable ab $?(enthält immer den letzten Exit-Code) und speichert ihn einfach in unserer eigenen Bash-Variablen exitcode. Es kann wieder durchgelesen $exitcodewerden. Sie können in nach weiteren Informationen suchen man bash, aber das kann eine Weile dauern ...

  • exec 3>&-

    Die integrierte Bash-Funktion execkann verwendet werden, um eine Stream-Umleitung auf die Shell anzuwenden. Dies bedeutet, dass alle folgenden Befehle davon betroffen sind. Weitere Informationen erhalten Sie help execin Ihrem Terminal.

    In diesem speziellen Fall wird der Stream 3 zu "stream -" umgeleitet, was nur bedeutet, dass er geschlossen werden sollte. An Stream 3 gesendete Daten werden ab sofort nirgendwo mehr weitergeleitet.

  • echo $result $exitcode

    Dieser einfache echoBefehl (weitere Informationen zu man echo) druckt nur den Inhalt der beiden Bash-Variablen resultund exitcodean das STDOUT. Da wir hier keine expliziten oder impliziten Stream-Weiterleitungen mehr haben, werden sie wirklich auf STDOUT angezeigt und werden daher einfach im Terminal angezeigt. Was ein Wunder! ;-)

Zusammenfassung:

Zuerst richten wir die Shell so ein, dass alles, was wir an den benutzerdefinierten Stream 3 senden, an STDOUT zurückgeleitet wird, damit es in unserem Terminal angezeigt wird.

Dann führen wir den dialogBefehl aus, leiten sein ursprüngliches STDOUT zu unserem benutzerdefinierten Stream 3 um, da es am Ende angezeigt werden muss, aber wir müssen den STDOUT-Stream vorübergehend für etwas anderes verwenden.
Wir leiten das ursprüngliche STDERR des Befehls, bei dem die Benutzereingabe des Dialogfensters zurückgegeben wird, anschließend zu STDOUT um.
Jetzt können wir das STDOUT (das die umgeleiteten Daten von STDERR enthält) erfassen und in unserer Variablen speichern $result. Es enthält jetzt die gewünschte Benutzereingabe!

Wir wollen auch den dialogExit-Code des Befehls, der uns anzeigt, ob OK oder CANCEL angeklickt wurde. Dieser Wert wird in der reservierten Bash-Variablen dargestellt $?und wir kopieren ihn einfach in unsere eigene Variable $exitcode.

Danach schließen wir Stream 3 wieder, da wir ihn nicht mehr benötigen, um weitere Umleitungen zu stoppen.

Schließlich geben wir normalerweise den Inhalt beider Variablen $result(die Benutzereingabe des Dialogfensters) und $exitcode(0 für OK, 1 für ABBRECHEN) an das Terminal aus.

Byte Commander
quelle
Ich denke, die Verwendung execist unnötig kompliziert. Warum nicht einfach die --stdoutOption dialogoder Umleitung der Ausgabe durch uns 2>&1 >/dev/tty?
Jarno
Bitte sehen Sie meine Antwort .
Jarno
3
Gute Antwort! Ich glaube jedoch, dass Sie eine falsche Anmerkung haben - Sie sagen, dass "sie von rechts nach links interpretiert werden", aber ich glaube, dass das nicht wahr ist. Von der bash Handbuch gnu.org/software/bash/manual/html_node/Redirections.html zeigt es an, dass Umleitungen stattfinden , wie sie angetroffen werden (dh von links nach rechts)
ralfthewise
14

Dialogeigene Tools verwenden: --output-fd flag

Wenn Sie die Manpage für den Dialog lesen, gibt es eine Option --output-fd, mit der Sie explizit festlegen können, wohin die Ausgabe geht (STDOUT 1, STDERR 2), anstatt standardmäßig zu STDERR.

Unten sehen Sie, wie ich den Beispielbefehl dialogausführe, wobei explizit angegeben wird, dass die Ausgabe in Dateideskriptor 1 erfolgen muss, damit ich sie in MYVAR speichern kann.

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

Bildbeschreibung hier eingeben

Named Pipes verwenden

Ein alternativer Ansatz, der viel verborgenes Potenzial birgt, ist die Verwendung einer sogenannten Named Pipe .

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

Bildbeschreibung hier eingeben

Eine detailliertere Übersicht über die Antwort von user.dz mit alternativen Ansätzen

Die ursprüngliche Antwort von user.dz und die Erklärung von ByteCommander bieten eine gute Lösung und einen Überblick über die Funktionsweise. Ich glaube jedoch, dass eine tiefere Analyse hilfreich sein könnte, um zu erklären, warum es funktioniert.

Zuallererst ist es wichtig, zwei Dinge zu verstehen: Was ist das Problem, das wir lösen wollen, und wie funktionieren die Shell-Mechanismen, mit denen wir es zu tun haben? Die Aufgabe besteht darin, die Ausgabe eines Befehls durch Befehlsersetzung zu erfassen. Unter einem einfachen Überblick, den jeder kennt, erfassen Befehlsersetzungen den stdouteines Befehls und lassen ihn von etwas anderem wiederverwenden. In diesem Fall sollte das result=$(...)Teil die Ausgabe des Befehls, der von bezeichnet wird, ...in einer aufgerufenen Variablen speichern result.

Unter der Haube wird die Befehlsersetzung tatsächlich als Pipe implementiert, wobei ein untergeordneter Prozess (der aktuelle ausgeführte Befehl) und ein Leseprozess (der die Ausgabe in einer Variablen speichert) vorhanden sind. Dies wird anhand einer einfachen Verfolgung von Systemaufrufen deutlich. Beachten Sie, dass der Dateideskriptor 3 das Leseende der Pipe ist, während 4 das Schreibende ist. Für den untergeordneten Prozess von echo, der in seinen stdout- Dateideskriptor 1 schreibt , ist dieser Dateideskriptor tatsächlich eine Kopie des Dateideskriptors 4, der das Schreibende der Pipe ist. Beachten Sie, dass dies stderrhier keine Rolle spielt, da es sich stdoutlediglich um eine Rohrverbindung handelt .

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

Kehren wir für eine Sekunde zur ursprünglichen Antwort zurück. Da wir jetzt wissen, dass dialogdas TUI-Feld an eine andere Stelle geschrieben stdout, beantwortet stderrund innerhalb der Befehlsersetzung an eine stdoutandere Stelle weitergeleitet wird, haben wir bereits einen Teil der Lösung - wir müssen die Dateideskriptoren so umverdrahten, dass sie an den Leseprozess weitergeleitet stderrwerden. Dies ist der 2>&1Teil der Antwort. Was machen wir aber mit der TUI Box?

Hier kommt der Dateideskriptor 3 ins dup2()Spiel . Der Syscall ermöglicht es uns, Dateideskriptoren zu duplizieren, sodass sie effektiv auf denselben Ort verweisen, aber wir können sie separat bearbeiten. Dateideskriptoren von Prozessen, an die ein steuerndes Terminal angeschlossen ist, verweisen tatsächlich auf ein bestimmtes Terminalgerät. Dies ist offensichtlich, wenn Sie dies tun

$ 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

Wo /dev/pts/5ist mein aktuelles Pseudo-Endgerät? Wenn wir dieses Ziel also irgendwie speichern können, können wir die TUI-Box immer noch auf den Terminalbildschirm schreiben. Das ist was exec 3>&1tut. Wenn Sie beispielsweise einen Befehl mit Umleitung aufrufen command > /dev/null, übergibt die Shell den stdout-Dateideskriptor und dup2()schreibt diesen Dateideskriptor dann an /dev/null. Der execBefehl führt für die gesamte Shell-Sitzung etwas Ähnliches wiedup2() Dateideskriptoren aus, sodass alle Befehle bereits umgeleitete Dateideskriptoren übernehmen. Gleiche mit exec 3>&1. Der Dateideskriptor 3verweist nun auf das steuernde Terminal, und jeder Befehl, der in dieser Shell-Sitzung ausgeführt wird, weiß davon.

Wenn dies result=$(dialog --inputbox test 0 0 2>&1 1>&3);auftritt, erstellt die Shell eine Pipe für den Dialog zum Schreiben, lässt jedoch 2>&1zuerst den Dateideskriptor 2 des Befehls auf den Dateideskriptor dieser Pipe duplizieren (wodurch die Ausgabe zum Lesen des Endes der Pipe und in die Variable erfolgt). Der Dateideskriptor 1 wird auf 3 dupliziert. Dadurch verweist der Dateideskriptor 1 weiterhin auf das steuernde Terminal, und der TUI-Dialog wird auf dem Bildschirm angezeigt.

Nun, es gibt tatsächlich eine Abkürzung für das derzeitige steuernde Terminal des Prozesses /dev/tty. Somit kann die Lösung ohne Verwendung von Dateideskriptoren vereinfacht werden:

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

Wichtige Dinge, an die Sie sich erinnern sollten:

  • Dateideskriptoren werden von jedem Befehl von der Shell geerbt
  • Die Befehlssubstitution wird als Pipe implementiert
  • doppelte Dateideskriptoren beziehen sich auf dieselbe Stelle wie die ursprüngliche, aber wir können jeden Dateideskriptor separat bearbeiten

Siehe auch

Sergiy Kolodyazhnyy
quelle
Die Manpage sagt auch, dass die --stdoutOption gefährlich sein kann und auf manchen Systemen leicht ausfällt, und ich denke --output-fd 1, dasselbe gilt: --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.- Die Named-Pipe-Idee ist jedoch cool!
Byte Commander
@ByteCommander "Kann fehlschlagen" ist nicht sehr überzeugend, da dies keine Beispiele liefert. Außerdem erwähnen sie nichts darüber --output-fd, was die Option ist, die ich hier verwendet habe, nicht --stdout. Zweitens wird der Dialog zuerst auf stdout gezeichnet, die zurückgegebene Ausgabe ist second. Wir machen diese beiden Dinge nicht gleichzeitig. Es ist --output-fd jedoch nicht ausdrücklich erforderlich, dass fd 1 (STDOUT) verwendet wird. Es kann leicht zu einem anderen Dateideskriptor umgeleitet werden
Sergiy Kolodyazhnyy
Ich bin nicht sicher, vielleicht funktioniert es überall, vielleicht funktioniert es nur auf den meisten Systemen. Es funktioniert bei mir und die Manpage sagt, dass ich mit Sicherheit nur mit Vorsicht eine ähnliche Option verwenden kann. Aber wie ich bereits sagte, ist die +1 für die Named Pipes trotzdem verdient.
Byte Commander
Ich sollte mich hier äußern, um ein gewisses Gleichgewicht zu halten. Für mich ist dies die einzige direkte kanonische Antwort. (1) Es wird nur dasselbe Tool verwendet und es wurden Optionen ohne externes Tool implementiert. (2) Es funktioniert in Ubuntu und all das, worum es bei AU geht. : / Leider scheint das OP diese Frage aufzugeben.
user.dz
Was ist der Vorteil der Verwendung von Named Pipes anstelle von regulären Dateien? Möchten Sie die Pipe nach dem Gebrauch nicht löschen?
Jarno
7

: DI kann es nicht erklären !!! Wenn Sie verstehen können, was sie in der Referenz sagen: Erweitertes Bash-Scripting-Handbuch: Kapitel 20. E / A-Umleitung , schreiben Sie eine neue Antwort und ich gebe Ihnen 50rep

Für eine Erklärung siehe die Antwort von ByteCommander . :) Dies ist ein Teil der Geschichte.

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

Quelle: Dialog in Bash erfasst Variablen nicht korrekt.
Referenz: Erweitertes Bash-Scripting-Handbuch: Kapitel 20. E / A-Umleitung

user.dz
quelle
ist das angebot noch gültig? Ich glaube, ich könnte erklären, was Sie vor eineinhalb Jahren dort gefunden haben ... :-)
Byte Commander
@ByteCommander, aber wenn Sie das zur Verfügung stellen können, werde ich Ihnen das geben, ich werde bei meinen Worten sein: D.
user.dz
@ByteCommander, bitte, ping mich an, nachdem du es gepostet hast.
user.dz
1
Fertig! askubuntu.com/a/704616/367990 Ich hoffe du verstehst alles und genießt die "Eureka!" Moment. :-D Hinterlasse einen Kommentar, wenn etwas unklar blieb.
Byte Commander
4

Das funktioniert bei mir:

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

Die Handbuchseite von dialogerzählt über --stdout:

Direkte Ausgabe auf die Standardausgabe. Diese Option wird aus Gründen der Kompatibilität mit Xdialog bereitgestellt. Die Verwendung in portablen Skripten wird jedoch nicht empfohlen, da Flüche normalerweise die Bildschirmaktualisierungen in die Standardausgabe schreiben. Wenn Sie diese Option verwenden, versucht der Dialog, das Terminal erneut zu öffnen, damit es in die Anzeige schreiben kann. Abhängig von der Plattform und Ihrer Umgebung kann dies fehlschlagen.

Kann jemand sagen, in welcher Plattform oder Umgebung es nicht funktioniert? Funktioniert die Umleitung der dialogAusgabe auf 2>&1 >/dev/ttybesser?

jarno
quelle
4

Für den Fall, dass jemand anderes von Google hier gelandet ist und diese Frage speziell nach bash fragt, gibt es hier eine andere Alternative:

Sie können Zenity verwenden . Zenity ist eine grafische Dienstprogramm , das kann innerhalb Bash - Skripte verwendet. Aber dies würde natürlich einen X-Server erfordern, wie user877329 zu Recht betont.

sudo apt-get install zenity

Dann in Ihrem Skript:

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

Nützlicher Link .

Wtower
quelle
3
Es sei denn, es gibt keinen X-Server
user877329
1
OP will wissen dialog. Es ist so, als würde ich Sie fragen: "Wie schreibe ich dies und das in Python?", Aber Sie geben mir Bash - ich bin sehr froh, dass dies auf andere Weise gemacht werden kann, aber das
frage
@Serg Ihr Kommentar ist ungültig, meine Antwort lautet nicht: Das Dienstprogramm bietet eine absolut gültige und einfache Alternative zu der vom OP gewünschten Lösung.
Wtower
3

Die Antwort von Sneetsher ist etwas eleganter, aber ich kann erklären, was falsch ist: Der Wert von $$ist in den Backticks unterschiedlich (weil es eine neue Shell startet und $$die PID der aktuellen Shell ist). Sie möchten den Dateinamen in eine Variable einfügen und sich stattdessen durchgehend auf diese Variable beziehen.

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

In diesem Fall ist das Vermeiden der temporären Datei eine bessere Lösung. In vielen Situationen können Sie eine temporäre Datei jedoch nicht vermeiden.

Tripleee
quelle