Jak uzyskać skierowanie okna dialogowego do zmiennej?

18

Uczę się pisania skryptów bash i napotkałem problem. Napisałem skrypt, który pobiera dane wejściowe od użytkownika za pomocą polecenia „czytaj”, i zmieniam je na zmienną, która będzie używana później w skrypcie. Skrypt działa, ale ....

Chciałbym móc go skonfigurować za pomocą „okna dialogowego”. dowiedziałam się że

„dialog --inputbox” przekieruje dane wyjściowe do „stderr”, a aby uzyskać je jako zmienną, musisz skierować je do pliku, a następnie pobrać. Kod, który wyjaśniłem, to:

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

Widzę, że wysyła sdterr do /tmp/inputbox.tmp.$$ za pomocą 2>, ale plik wyjściowy wygląda jak „inputbox.tmp.21661”. Gdy próbuję wyodrębnić plik, pojawia się błąd. Więc nadal nie mogę uzyskać danych wejściowych użytkownika z --inputbox jako zmiennej.

Przykładowy skrypt:

echo "  What app would you like to remove? "

read dead_app

sudo apt-get remove --purge $dead_app

Jak widać, jest to podstawowy skrypt. Czy można w ogóle uzyskać zmienną jako słowo dialog --inputbox?

emerikanbloke
źródło
Z mojego doświadczenia wynika, że ​​skrypt działa dobrze, jeśli usuniesz pustą linię po drugiej linii. Alternatywnie możesz użyć mktemppolecenia, aby utworzyć plik tymczasowy.
jarno

Odpowiedzi:

16

: DI nie mogę tego wyjaśnić !!! Jeśli rozumiesz, co mówią w Advanced Bash-Scripting Guide: Rozdział 20. Przekierowanie I / O , napisz nową odpowiedź, a dam ci 50 powtórzeń :

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

Odniesienie: Okno dialogowe w bash nie pobiera poprawnie zmiennych

^ odpowiedź od @Sneetsher (4 lipca 2014)

Zgodnie z prośbą postaram się wyjaśnić, co robi ten fragment kodu wiersz po wierszu.

Zauważ, że uprości to, pomijając wszystkie ;średniki na końcach linii, ponieważ nie są one konieczne, jeśli napiszemy jedno polecenie w linii.

I / O - Strumienie:

Najpierw musisz zrozumieć strumienie komunikacji. Istnieje 10 strumieni, ponumerowanych od 0 do 9:

  • Strumień 0 („STDIN”):
    „Standardowe wejście”, domyślny strumień wejściowy do odczytu danych z klawiatury.

  • Strumień 1 („STDOUT”):
    „Standardowe wyjście”, domyślny strumień wyjściowy używany do wyświetlania normalnego tekstu w terminalu.

  • Strumień 2 („STDERR”): „Błąd standardowy”, domyślny strumień wyjściowy używany do wyświetlania błędów lub innego tekstu do specjalnych celów w terminalu.

  • Strumienie 3-9:
    Dodatkowe, swobodnie użyteczne strumienie. Nie są domyślnie używane i nie istnieją, dopóki coś nie spróbuje ich użyć.

Zauważ, że wszystkie „strumienie” są wewnętrznie reprezentowane przez deskryptory plików w /dev/fd(który jest dowiązaniem symbolicznym, do /proc/self/fdktórego zawiera inne dowiązanie symboliczne dla każdego strumienia ... jest to trochę skomplikowane i nie ma znaczenia dla ich zachowania, więc zatrzymuję się tutaj.). Standardowe strumienie mają również /dev/stdin, /dev/stdouti /dev/stderr(które są linki symboliczne ponownie, etc ...).

Scenariusz:

  • exec 3>&1

    Wbudowanego Bash execmożna zastosować do przekierowania strumienia do powłoki, co oznacza, że ​​wpływa on na wszystkie następujące polecenia. Aby uzyskać więcej informacji, uruchom help execw swoim terminalu.

    W tym szczególnym przypadku strumień 3 zostaje przekierowany do strumienia 1 (STDOUT), co oznacza, że ​​wszystko, co później wyślemy do strumienia 3, pojawi się w naszym terminalu, tak jakby normalnie było drukowane do STDOUT.

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

    Ta linia składa się z wielu części i struktur składniowych:

    • result=$(...)
      Ta struktura wykonuje polecenie w nawiasach i przypisuje dane wyjściowe (STDOUT) do zmiennej bash result. Jest czytelny $result. Wszystko to jest jakoś opisane w bardzo długim czasie man bash.

    • dialog --inputbox TEXT HEIGHT WIDTH
      To polecenie pokazuje pole TUI z danym TEKSTEM, pole wprowadzania tekstu oraz dwa przyciski OK i ANULUJ. Jeśli wybrano OK, polecenie kończy pracę ze statusem 0 i drukuje wprowadzony tekst do STDERR, jeśli zostanie wybrane ANULUJ, zakończy działanie z kodem 1 i nic nie wydrukuje. Aby uzyskać więcej informacji, przeczytaj man dialog.

    • 2>&1 1>&3
      Są to dwa polecenia przekierowania. Będą interpretowane od prawej do lewej:

      1>&3 przekierowuje strumień polecenia 1 (STDOUT) do strumienia niestandardowego 3.

      2>&1 następnie przekierowuje strumień polecenia 2 (STDERR) do strumienia 1 (STDOUT).

      Oznacza to, że wszystko, co polecenie wypisuje do STDOUT, pojawia się teraz w strumieniu 3, podczas gdy wszystko, co miało pojawić się na STDERR, jest teraz przekierowywane do STDOUT.

    Tak więc cała linia wyświetla monit tekstowy (na STDOUT, który został przekierowany do strumienia 3, który powłoka ponownie przekierowuje z powrotem do STDOUT na końcu - patrz exec 3>&1polecenie) i przypisuje wprowadzone dane (zwrócone przez STDERR, a następnie przekierowane do STDOUT) do zmiennej Bash result.

  • exitcode=$?

    Ten kod pobiera poprzednio wykonany kod wyjścia polecenia (tutaj z dialog) przez zarezerwowaną zmienną Bash $?(zawsze przechowuje ostatni kod wyjścia) i po prostu przechowuje go w naszej własnej zmiennej Bash exitcode. Można go $exitcodeponownie przeczytać . Możesz wyszukać więcej informacji na ten temat man bash, ale może to chwilę potrwać ...

  • exec 3>&-

    Wbudowanego Bash execmożna zastosować do przekierowania strumienia do powłoki, co oznacza, że ​​wpływa on na wszystkie następujące polecenia. Aby uzyskać więcej informacji, uruchom help execw swoim terminalu.

    W tym szczególnym przypadku strumień 3 zostaje przekierowany do „stream -”, co oznacza, że ​​powinien zostać zamknięty. Dane wysyłane do strumienia 3 nie będą już nigdzie przekierowywane.

  • echo $result $exitcode

    To proste echopolecenie (więcej informacji na temat man echo) po prostu drukuje zawartość dwóch zmiennych Bash resulti exitcodeSTDOUT. Ponieważ nie mamy już żadnych bezpośrednich lub niejawnych przekierowań strumienia, naprawdę pojawią się one w STDOUT, a zatem zostaną po prostu wyświetlone w terminalu. Co za cud! ;-)

Streszczenie:

Najpierw ustawiamy powłokę tak, aby przekierowała wszystko, co wysyłamy do niestandardowego strumienia 3 z powrotem do STDOUT, tak aby pojawiła się w naszym terminalu.

Następnie uruchamiamy dialogpolecenie, przekierowujemy jego oryginalny STDOUT do naszego niestandardowego strumienia 3, ponieważ musi on zostać wyświetlony na końcu, ale tymczasowo musimy użyć strumienia STDOUT do czegoś innego.
Przekierowujemy oryginalny STDERR polecenia, w którym następnie zwracane są dane wejściowe użytkownika okna dialogowego, do STDOUT.
Teraz możemy przechwycić STDOUT (który przechowuje przekierowane dane z STDERR) i zapisać go w naszej zmiennej $result. Zawiera teraz żądane dane wejściowe użytkownika!

Chcemy także dialogkodu wyjścia polecenia, który pokazuje nam, czy kliknięto OK czy ANULUJ. Ta wartość jest prezentowana w zarezerwowanej zmiennej Bash $?i po prostu kopiujemy ją do własnej zmiennej $exitcode.

Następnie ponownie zamykamy strumień 3, ponieważ już go nie potrzebujemy, aby zatrzymać dalsze przekierowania.

Wreszcie, zwykle wyprowadzamy zawartość obu zmiennych $result(dane użytkownika w oknie dialogowym) i $exitcode(0 dla OK, 1 dla CANCEL) do terminala.

Bajt Dowódca
źródło
Myślę, że korzystanie execjest niepotrzebnie skomplikowane. Dlaczego nie tylko my możemy --stdoutwybrać opcję dialoglub przekierować jej wynik 2>&1 >/dev/tty?
jarno
Proszę zobaczyć moją odpowiedź .
jarno
3
Świetna odpowiedź! Uważam jednak, że masz jedną nieprawidłową notatkę - mówisz, że „będą interpretowane od prawej do lewej”, ale uważam, że to nieprawda. Z podręcznika bash gnu.org/software/bash/manual/html_node/Redirections.html wskazuje, że przekierowania odbywają się w miarę ich występowania (tj. Od lewej do prawej)
ralfthewise
14

Korzystanie z własnych narzędzi okna dialogowego: --output-fd flag

Jeśli czytasz stronę --output-fdpodręcznika dla okna dialogowego, dostępna jest opcja , która pozwala ci jawnie określić, dokąd idzie wyjście (STDOUT 1, STDERR 2), zamiast domyślnie przechodzić do STDERR.

Poniżej możesz zobaczyć, jak uruchamiam przykładowe dialogpolecenie, z wyraźnym stwierdzeniem, że dane wyjściowe muszą przejść do deskryptora pliku 1, co pozwala mi zapisać je w MYVAR.

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

wprowadź opis zdjęcia tutaj

Używanie nazwanych potoków

Alternatywnym podejściem, które ma wiele ukrytego potencjału, jest użycie czegoś zwanego potokiem nazwanym .

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

wprowadź opis zdjęcia tutaj

Bardziej szczegółowy przegląd odpowiedzi user.dz z alternatywnym podejściem

Oryginalna odpowiedź user.dz i wyjaśnienie ByteCommander, że oba stanowią dobre rozwiązanie i przegląd tego, co robi. Uważam jednak, że głębsza analiza może być korzystna dla wyjaśnienia, dlaczego to działa.

Przede wszystkim ważne jest zrozumienie dwóch rzeczy: jaki jest problem, który próbujemy rozwiązać i jakie są podstawowe mechanizmy mechanizmów powłokowych, z którymi mamy do czynienia. Zadanie polega na przechwytywaniu danych wyjściowych polecenia poprzez podstawienie polecenia. W uproszczonym przeglądzie, który wszyscy znają, podstawienia poleceń przechwytują stdoutpolecenie i pozwalają na jego ponowne użycie przez coś innego. W takim przypadku result=$(...)część powinna zapisać wynik dowolnego polecenia wyznaczonego przez ...w zmiennej o nazwie result.

Pod maską zastępowanie poleceń jest faktycznie realizowane jako potok, w którym znajduje się proces potomny (aktualne polecenie, które się uruchamia) i proces odczytu (który zapisuje dane wyjściowe do zmiennej). Widać to po prostym śledzeniu wywołań systemowych. Zauważ, że deskryptor pliku 3 to koniec odczytu potoku, a 4 to koniec zapisu. W przypadku procesu potomnego echo, który zapisuje do swojego stdout- deskryptora pliku 1, ten deskryptor pliku jest w rzeczywistości kopią deskryptora pliku 4, który jest końcem zapisu potoku. Zauważ, że stderrtutaj nie gra żadnej roli, po prostu dlatego, że to stdouttylko połączenie rur .

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

Wróćmy na chwilę do pierwotnej odpowiedzi. Odkąd wiemy, że dialogzapisuje pole TUI do stdout, odpowiedź stderri w ramach zastępowania komend stdoutzostaje potokowany gdzieś indziej, mamy już część rozwiązania - musimy ponownie połączyć deskryptory plików w taki sposób, stderraby zostały przesłane do procesu czytnika. To jest 2>&1część odpowiedzi. Co jednak robimy z pudełkiem TUI?

Właśnie tam pojawia się deskryptor pliku 3. dup2()Syscall pozwala nam powielać deskryptory plików, dzięki czemu skutecznie odnoszą się do tego samego miejsca, ale możemy nimi manipulować osobno. Deskryptory plików procesów, do których przyłączony jest terminal sterujący, wskazują konkretny terminal. Jest to oczywiste, jeśli tak

$ 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

gdzie /dev/pts/5jest moje obecne urządzenie pseudo-końcowe. Tak więc, jeśli uda nam się w jakiś sposób zapisać to miejsce docelowe, nadal możemy zapisać pole TUI na ekranie terminala. Właśnie to exec 3>&1robi. Gdy command > /dev/nullna przykład wywołujesz polecenie z przekierowaniem , powłoka przekazuje swój deskryptor standardowego pliku, a następnie używa dup2()do zapisania tego deskryptora pliku /dev/null. execWykonuje polecenia coś podobnego dodup2() deskryptorów dla całej sesji powłoki, dzięki czemu żadnemu Dziedzicz komenda już przekierowany deskryptor pliku. To samo z exec 3>&1. Deskryptor pliku 3będzie teraz odnosił się do / point do kontrolującego terminala, a każde polecenie uruchomione w tej sesji powłoki będzie o tym wiedzieć.

Kiedy więc to result=$(dialog --inputbox test 0 0 2>&1 1>&3);nastąpi, powłoka tworzy potok do okna dialogowego do zapisu, ale 2>&1najpierw spowoduje, że deskryptor pliku polecenia 2 zostanie zduplikowany na deskryptorze pliku zapisu tej potoku (w ten sposób wyjście zostanie przesłane do końca odczytu potoku i do zmiennej) , podczas gdy deskryptor pliku 1 zostanie zduplikowany na 3. Spowoduje to, że deskryptor pliku 1 nadal będzie odnosił się do terminala sterującego, a okno dialogowe TUI pojawi się na ekranie.

Teraz faktycznie jest krótka ręka dla bieżącego kontrolującego terminalu procesu, czyli /dev/tty. W ten sposób rozwiązanie można uprościć bez użycia deskryptorów plików, po prostu w:

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

Najważniejsze rzeczy do zapamiętania:

  • deskryptory plików są dziedziczone z powłoki przez każde polecenie
  • zastępowanie poleceń jest realizowane jako potok
  • zduplikowane deskryptory plików będą odnosić się do tego samego miejsca, co oryginalne, ale możemy manipulować każdym deskryptorem pliku osobno

Zobacz też

Sergiy Kolodyazhnyy
źródło
Strona podręcznika mówi również, że --stdoutopcja może być niebezpieczna i łatwo zawiedzie w niektórych systemach, i myślę, że --output-fd 1robi to samo: --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.- Jednak pomysł nazwanej rury jest fajny!
Bajt Dowódca
@ByteCommander „Może się nie powieść” nie jest zbyt przekonujący, ponieważ nie zawiera przykładów. Ponadto nie wspominają o niczym, o --output-fdczym ja tu nie skorzystałem --stdout. Po drugie, okno dialogowe jest rysowane na pierwszym etapie, zwracane dane wyjściowe są na drugim miejscu. Nie robimy tych dwóch rzeczy jednocześnie. Jednak --output-fd nie wymaga specjalnie użycia fd 1 (STDOUT). Można go łatwo przekierować do innego deskryptora pliku
Sergiy Kolodyazhnyy
Nie jestem pewien, może działa wszędzie, może działa tylko na większości systemów. Działa na mojej, a strona mówi, że należy zachować ostrożność przy użyciu podobnej opcji, to wszystko, co wiem na pewno. Ale, jak już powiedziałem, +1 i tak zasługują na nazwane potoki.
Bajt Dowódca
Powinienem tu skomentować, aby zachować równowagę. Dla mnie może to być jedyna bezpośrednia odpowiedź kanoniczna (1) używa tylko tego samego narzędzia i implementuje opcje bez żadnego zewnętrznego narzędzia (2) Działa w Ubuntu i to wszystko, o co chodzi w AU. : / niestety OP wydaje się porzucać to pytanie.
user.dz
Jaka jest zaleta używania nazwanego potoku zamiast zwykłego pliku tutaj? Nie chcesz usunąć potoku po użyciu?
jarno
7

: DI nie mogę tego wyjaśnić !!! Jeśli rozumiesz, co mówią w odnośniku: Advanced Bash-Scripting Guide: Chapter 20. I / O Redirection , napisz nową odpowiedź, a dam ci 50rep

Otrzymano nagrodę , wyjaśnienia znajdują się w odpowiedzi ByteCommander . :) To część historii.

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

Źródło: Okno dialogowe w bash nie przechwytuje poprawnie zmiennych
Odwołanie: Zaawansowany przewodnik bash-scripting: Rozdział 20. Przekierowanie I / O

user.dz
źródło
czy ta oferta jest nadal aktualna? Myślę, że mógłbym wyjaśnić, co tam znalazłeś półtora roku temu ... :-)
Byte Commander
@ByteCommander, ale jeśli możesz to zapewnić, dam ci to, będę na moje słowa: D.
user.dz
@ByteCommander, proszę, pinguj mnie po opublikowaniu.
user.dz
1
Skończone! askubuntu.com/a/704616/367990 Mam nadzieję, że rozumiesz wszystko i cieszysz się z „Eureka!” za chwilę. :-D Pozostaw komentarz, jeśli coś pozostało niejasne.
Bajt Dowódca
4

To działa dla mnie:

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

Strona podręcznika dialogmówi o --stdout:

Wyjście bezpośrednie do wyjścia standardowego. Ta opcja ma na celu zapewnienie zgodności z Xdialog, jednak nie jest zalecane używanie jej w przenośnych skryptach, ponieważ curses zwykle zapisuje aktualizacje ekranu na standardowe wyjście. Jeśli użyjesz tej opcji, okno dialogowe spróbuje ponownie otworzyć terminal, aby mógł zapisać na wyświetlaczu. W zależności od platformy i środowiska może się to nie powieść.

Czy ktoś może powiedzieć, na jakiej platformie lub środowisku to nie działa? Czy przekierowanie dialogwyjścia do 2>&1 >/dev/ttyzamiast tego działa lepiej?

jarno
źródło
4

W przypadku, gdy ktoś też wylądował tutaj z Google i chociaż to pytanie dotyczy konkretnie bash, oto inna alternatywa:

Możesz użyć zenity . Zenity to graficzne narzędzie, którego można używać w skryptach bash. Ale oczywiście wymagałoby to serwera X, jak słusznie zauważył użytkownik 877329.

sudo apt-get install zenity

Następnie w swoim skrypcie:

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

Przydatny link .

Wtower
źródło
3
Chyba że nie ma serwera X
877329
1
OP chce wiedzieć dialog. To tak, jakbym przyszedł i zapytał: „Jak napisać to i to w pythonie?”, Ale dajesz mi wstrząs - bardzo się cieszę, że można to zrobić inaczej, ale nie o to pytam
Sergiy Kolodyazhnyy
@Erg Twój komentarz jest nieważny, moja odpowiedź nie brzmi: narzędzie stanowi doskonale poprawną i prostą alternatywę dla rozwiązania wymaganego przez OP.
Wtower,
3

Odpowiedź udzielona przez Sneetshera jest nieco bardziej elegancka, ale mogę wyjaśnić, co jest nie tak: wartość $$ różna (ponieważ uruchamia nową powłokę i $$jest PID bieżącej powłoki). Będziesz chciał umieścić nazwę pliku w zmiennej, a następnie odwołać się do tej zmiennej w całym tekście.

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

W takim przypadku lepszym rozwiązaniem byłoby uniknięcie pliku tymczasowego, ale w wielu sytuacjach nie można uniknąć pliku tymczasowego.

potrójny
źródło