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 dialog
ausfü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)
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
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 stdout
eines 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 stderr
hier keine Rolle spielt, da es sich stdout
lediglich 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 dialog
das TUI-Feld an eine andere Stelle geschrieben stdout
, beantwortet stderr
und innerhalb der Befehlsersetzung an eine stdout
andere Stelle weitergeleitet wird, haben wir bereits einen Teil der Lösung - wir müssen die Dateideskriptoren so umverdrahten, dass sie an den Leseprozess weitergeleitet stderr
werden. Dies ist der 2>&1
Teil 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/5
ist 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>&1
tut. 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 exec
Befehl 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 3
verweist 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>&1
zuerst 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
mktemp
Befehl verwenden, um eine temporäre Datei zu erstellen.