Як отримати вхід діалогового вікна до змінної?


18

Я вчу себе писати сценарії, і я натрапив на проблему. Я написав сценарій, щоб взяти дані від користувача, використовуючи команду 'read', і зробити це введення змінною, щоб використовувати його пізніше в сценарії. Сценарій працює, але….

Я хотів би мати можливість налаштувати його за допомогою "діалогу". Я це з’ясував

'dialog --inputbox' направить результат на 'stderr', і щоб отримати цей вхід як змінну, вам потрібно направити його у файл, а потім отримати його. Код, який я знайшов, щоб пояснити це:

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

Я бачу, що він надсилає sdterr в /tmp/inputbox.tmp.$$ з 2>, але вихідний файл виглядає як "inputbox.tmp.21661". Коли я намагаюся знайти файл, він видає мені помилку. Тому я все ще не можу отримати вхід користувача з --inputbox як змінної.

Приклад сценарію:

echo "  What app would you like to remove? "

read dead_app

sudo apt-get remove --purge $dead_app

Отже, як бачите, це базовий сценарій. Чи можливо навіть отримати змінну як слово dialog --inputbox?


На мій досвід, сценарій працює добре, якщо ви видалите порожній рядок після 2-го рядка. Крім того, ви можете використовувати mktempкоманду для створення тимчасового файлу.
jarno

Відповіді:


16

: DI не можу це пояснити !!! Якщо ви можете зрозуміти, що вони говорять у Посібнику з розширеного сценарію Bash: Глава 20. Перенаправлення вводу-виводу , напишіть нову відповідь, і я дам вам 50rep :

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

Довідка: Діалог у bash неправильно схоплює змінні

^ відповідь від @Sneetsher (4 липня 2014 р.)

Як вимагається, я спробую пояснити, що робить цей фрагмент по черзі.

Зауважте, що я спрощу його, опустивши всі ;крапки з комою на кінцях рядка, оскільки вони не потрібні, якщо ми пишемо одну команду на рядок.

I / O - Потоки:

Спочатку потрібно зрозуміти комунікаційні потоки. Є 10 потоків, пронумерованих від 0 до 9:

  • Потік 0 ("STDIN"):
    "Стандартний ввід", стандартний потік введення для зчитування даних з клавіатури.

  • Потік 1 ("STDOUT"):
    "Стандартний вихід", вихідний потік за замовчуванням, який використовується для показу нормального тексту в терміналі.

  • Потік 2 ("STDERR"): "Стандартна помилка", вихідний потік за замовчуванням, який використовується для відображення помилок або іншого тексту для спеціальних цілей у терміналі.

  • Потоки 3-9:
    Додаткові, вільно доступні потоки. Вони не використовуються за замовчуванням і не існують, поки щось не спробує їх використовувати.

Зауважте, що всі "потоки" внутрішньо представлені дескрипторами файлів у /dev/fd(це символічне посилання, на /proc/self/fdяке міститься ще одне символічне посилання для кожного потоку ... це трохи складно і не важливо для їх поведінки, тому я зупиняюся тут.). Стандартні потоки також /dev/stdin, /dev/stdoutі /dev/stderr(що символічні посилання знову, і т.д ...).

Сценарій:

  • exec 3>&1

    Вбудований Bash execможна використовувати для застосування перенаправлення потоку до оболонки, це означає, що він впливає на всі наступні команди. Для отримання додаткової інформації запустіть help execу своєму терміналі.

    У цьому спеціальному випадку потік 3 переспрямовується на потік 1 (STDOUT), це означає, що все, що ми надсилаємо до потоку 3 пізніше, з’явиться в нашому терміналі так, як ніби воно було нормально надруковано на STDOUT.

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

    Цей рядок складається з багатьох частин та синтаксичних структур:

    • result=$(...)
      Ця структура виконує команду в дужках і призначає вихід (STDOUT) змінній bash result. Це читається наскрізь $result. Все це якось описано у veeeery looong man bash.

    • dialog --inputbox TEXT HEIGHT WIDTH
      Ця команда показує поле TUI із заданим TEXT, полем для введення тексту та двома кнопками OK та CANCEL. Якщо буде вибрано OK, команда виходить зі статусом 0 і друкує введений текст у STDERR, якщо CANCEL буде вибрано, він вийде з кодом 1 і нічого не надрукує. Для отримання додаткової інформації читайте man dialog.

    • 2>&1 1>&3
      Це дві команди перенаправлення. Вони будуть інтерпретовані справа наліво:

      1>&3 перенаправляє потік команди 1 (STDOUT) до користувальницького потоку 3.

      2>&1 після цього переспрямовує потік команди 2 (STDERR) на потік 1 (STDOUT).

      Це означає, що все, що команда друкує до STDOUT, тепер з’являється у потоці 3, тоді як усе, що було призначено для відображення на STDERR, тепер перенаправляється на STDOUT.

    Таким чином, весь рядок відображає текстовий рядок (на STDOUT, який перенаправлений на потік 3, який оболонка знову перенаправляє назад до STDOUT в кінці кінця - див. exec 3>&1Команду) і призначає введені дані (повертається через STDERR, потім перенаправляється на STDOUT) до змінної Bash result.

  • exitcode=$?

    Цей код отримує раніше виконаний команду код виходу (тут з dialog) через зарезервовану змінну Bash $?(завжди містить останній код виходу) і просто зберігає її у нашій власній змінній Bash exitcode. Його можна прочитати $exitcodeще раз. Ви можете шукати додаткову інформацію про це в man bash, але це може зайняти деякий час ...

  • exec 3>&-

    Вбудований Bash execможна використовувати для застосування перенаправлення потоку до оболонки, це означає, що він впливає на всі наступні команди. Для отримання додаткової інформації запустіть help execу своєму терміналі.

    У цьому спеціальному випадку потік 3 переспрямовується на "stream -", що означає, що його слід закрити. Дані, надіслані до потоку 3, відтепер більше не будуть перенаправлені нікуди.

  • echo $result $exitcode

    Ця проста echoкоманда (більше інформації про man echo) просто друкує вміст двох змінних Bash resultі exitcodeдо STDOUT. Оскільки у нас більше немає явних або неявних переадресацій потоку, вони дійсно з’являться на STDOUT і тому просто відображаються в терміналі. Яке диво! ;-)

Підсумок:

По-перше, ми встановлюємо оболонку, щоб перенаправляти все, що ми надсилаємо в користувацький потік 3 назад до STDOUT, щоб воно відображалося в нашому терміналі.

Потім ми запускаємо dialogкоманду, перенаправляємо її вихідний STDOUT до нашого користувальницького потоку 3, тому що він повинен відображатися в кінцевому підсумку, але нам тимчасово потрібно використовувати потік STDOUT для чогось іншого.
Ми перенаправляємо оригінальний STDERR команди, де повертається вхід користувача діалогового вікна, на STDOUT після цього.
Тепер ми можемо захопити STDOUT (який зберігає перенаправлені дані з STDERR) і зберегти їх у нашій змінній $result. Він містить потрібний ввід користувача зараз!

Ми також хочемо dialog код виходу команди, який показує, натиснули ОК або ОТМЕНИТЬ. Це значення представлено у зарезервованій змінній Bash, $?і ми просто скопіюємо його до власної змінної $exitcode.

Після цього ми знову закриваємо потік 3, оскільки він більше не потрібен, щоб зупинити подальші перенаправлення його.

Нарешті, ми зазвичай виводимо вміст обох змінних $result(введення користувачем діалогового вікна) та $exitcode(0 для OK, 1 для CANCEL) до терміналу.


Я думаю, що використання execнадмірно складне. Чому б не просто --stdoutваріант для dialogабо перенаправити його вихід 2>&1 >/dev/tty?
jarno

Будь ласка, дивіться мою відповідь .
jarno

3
Чудова відповідь! Однак я вважаю, що у вас є одна примітка, яка є невірною - ви говорите, що "Вони будуть тлумачити справа наліво", але я вважаю, що це неправда. З посібника з bash gnu.org/software/bash/manual/html_node/Redirections.html вказується, що переадресації відбуваються під час їх виникнення (тобто зліва направо)
зблизька

14

Використання власних інструментів діалогу: --outout-fd flag

Якщо ви читаєте чоловічу сторінку для діалогу, є опція --output-fd, яка дозволяє чітко встановити, куди йде вихід (STDOUT 1, STDERR 2), а не за замовчуванням переходити до STDERR.

Нижче ви бачите, що я виконує зразок dialogкоманди, чітко вказуючи, що вихід повинен перейти до дескриптора файлу 1, що дозволяє мені зберегти його в MYVAR.

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

введіть тут опис зображення

Використання названих труб

Альтернативний підхід, який має багато прихованого потенціалу, полягає у використанні чогось відомого як названа труба .

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

введіть тут опис зображення

Більш глибокий огляд відповіді user.dz з альтернативним підходом

Оригінальна відповідь user.dz та пояснення ByteCommander про те, що вони забезпечують хороше рішення та огляд того, що він робить. Однак я вважаю, що більш глибокий аналіз може бути корисним, щоб пояснити, чому він працює.

Перш за все, важливо зрозуміти дві речі: яка проблема ми намагаємося вирішити та які основні механізми роботи механізмів оболонки, з якими ми маємо справу. Завдання полягає в захопі виводу команди за допомогою підстановки команд. Під спрощеним оглядом, який всі знають, заміни команд фіксують stdoutкоманду і дозволяють її повторно використовувати чим-небудь іншим. У цьому випадку result=$(...)частина повинна зберігати вихід будь-якої команди, позначеної через ...змінну, що називається result.

Під кришкою підстановка команд реально реалізується як труба, де є дочірній процес (фактична команда, що виконується) та процес читання (що зберігає вихід до змінної). Це видно з простого сліду системних викликів. Зауважте, що дескриптор файлу 3 - це кінець запису, а 4 - кінець запису. Для дочірнього процесу echo, який записує до свого stdout- дескриптора файлу 1, цей дескриптор файлу є фактично копією дескриптора файлу 4, що є кінцем запису файлу. Зверніть увагу, що stderrтут не грає ролі, просто тому, що це тільки труба, що з'єднує stdout.

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

Повернемося до початкової відповіді на секунду. Оскільки тепер ми знаємо, що dialogзаписує поле TUI на stdoutвідповідь stderr, і в рамках заміни команди stdoutпотрапляє десь в іншому місці, у нас вже є частина рішення - нам потрібно переписати дескриптори файлів таким чином, що stderrбуде передано в процес зчитування. Це2>&1 частина відповіді. Однак що ми робимо з TUI box?

Ось тут надходить дескриптор файлів 3. Система dup2()виклику дозволяє нам дублювати дескриптори файлів, завдяки чому вони ефективно посилаються на те саме місце, але ми можемо маніпулювати ними окремо. Дескриптори файлів процесів, які мають приєднаний контрольний термінал, фактично вказують на певний термінальний пристрій. Це очевидно, якщо ви робите

$ 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/5мій поточний псевдотермінальний пристрій. Таким чином, якщо ми зможемо якось зберегти це призначення, ми все одно можемо записати поле TUI на екран терміналу. Ось що і exec 3>&1робить. command > /dev/nullНаприклад, якщо ви викликаєте команду з перенаправленням , оболонка передає дескриптор файлу stdout, а потім використовує dup2()для запису цього дескриптора файлу /dev/null. execКоманда виконує щось подібнеdup2() дескрипторів файлів для всієї сесії оболонки, в результаті чого будь-якої команди , успадковує вже перенаправлений дескриптор файлу. Те саме з exec 3>&1. Дескриптор файлу 3тепер буде звертатися до / вказувати на керуючий термінал, і будь-яка команда, що працює в цьому сеансі оболонки, буде знати про це.

Отже, коли це result=$(dialog --inputbox test 0 0 2>&1 1>&3);відбувається, оболонка створює трубу для діалогу для запису, але також2>&1 спочатку зробить дескриптор файлу команди 2 дублювати на дескриптор файлу запису цієї труби (таким чином, виведення виходить на читання кінця труби та у змінну) , в той час як дескриптор 1 файлу буде дублюватися на 3. Це зробить дескриптор 1 файлу, як і раніше, посилається на контрольний термінал, і на екрані з'явиться діалогове вікно TUI.

Тепер насправді існує короткий перелік поточного керуючого терміналу процесу, який є /dev/tty. Таким чином, рішення можна спростити без використання дескрипторів файлів, просто в:

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

Ключові речі, які слід пам’ятати:

  • Дескриптори файлів успадковуються з оболонки кожною командою
  • заміна команд реалізується як труба
  • подвійні дескриптори файлів будуть посилатися на те саме місце, що і оригінальне, але ми можемо маніпулювати кожним дескриптором файлів окремо

Дивись також


На сторінці вказується також, що --stdoutваріант може бути небезпечним і легко виходить з ладу в деяких системах, і я думаю, що --output-fd 1це робиться так само: --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.- Однак, названа ідея труби - це круто!
Байт-командир

@ByteCommander "Може не вдатися" не дуже переконливо, оскільки це не дає прикладів. Крім того, вони нічого не згадують, про --output-fdщо я тут використовував варіант, ні --stdout. По-друге, діалогове вікно складається в stdout по-перше, повертається результат - другий. Ці дві речі ми не робимо одночасно. Однак --output-fd спеціально не потрібно використовувати fd 1 (STDOUT). Його легко переспрямувати на інший дескриптор файлу
Сергій Колодяжний

Я не впевнений, можливо, він працює скрізь, можливо, він працює лише в більшості систем. Це працює на моєму, і на сторінці вказує, що використовувати аналогічний варіант обережно - це все, що я точно знаю. Але, як я вже сказав, +1 все одно заслужений для названих труб.
Байт-командир

Я повинен тут прокоментувати, щоб зберегти деяку рівновагу. Для мене це єдиний прямий канонічний відповідь (1), він використовує лише той самий інструмент і реалізовані варіанти без будь-якого зовнішнього інструменту (2) Він працює в Ubuntu і все, що стосується АС. : / на жаль, ОП, здається, відмовляється від цього питання.
user.dz

Яка перевага використання тут названої труби замість звичайного файлу? Не хочете видалити трубку після використання?
jarno

7

: DI не можу це пояснити !!! Якщо ви можете зрозуміти, що вони говорять у посиланні: Розширений посібник із сценаріїв Bash: Розділ 20. Перенаправлення вводу-виводу , напишіть нову відповідь, і я дам вам 50rep

Баунті дали, для пояснення дивіться відповідь ByteCommander . :) Це частина історії.

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

Джерело: Діалог у файлі bash неправильно захоплює змінні
Довідник: Розширений посібник із сценаріїв Bash: Розділ 20. Перенаправлення вводу / виводу


ця пропозиція все ще діє? Думаю, я міг би пояснити, що ти там знайшов півтора року тому ... :-)
Байт командир

@ByteCommander, але, якщо ви можете це надати, я вам це передам, я буду на свої слова: D.
user.dz

@ByteCommander, будь ласка, пінг мені після публікації.
user.dz

1
Готово! askubuntu.com/a/704616/367990 Я сподіваюся, що ти все зрозумієш і сподобається "Eureka!" мить. :-D Залиште коментар, якщо щось залишилося незрозумілим.
Байт-командир

4

Це працює для мене:

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

Сторінка посібника dialogрозповідає про --stdout:

Прямий вихід на стандартний вихід. Ця опція передбачена для сумісності з Xdialog, однак використання її в портативних сценаріях не рекомендується, оскільки curses зазвичай записує свої екранні оновлення до стандартного виводу. Якщо ви використовуєте цю опцію, діалогове вікно намагається знову відкрити термінал, щоб він міг записати на дисплей. Залежно від платформи та вашого оточення, це може вийти з ладу.

Хтось може сказати, в якій платформі чи середовищі вона не працює? Чи краще переадресація dialogрезультатів 2>&1 >/dev/ttyпрацює замість цього?


4

Якщо хтось із них теж приземлився з Google, і хоча це питання задається спеціально для bash, тут є ще одна альтернатива:

Можна використовувати зенітність . Zenity - це графічна утиліта, яку можна використовувати всередині скриптів bash. Але, звичайно, для цього потрібен X-сервер, як справедливо вказав user877329.

sudo apt-get install zenity

Потім у вашому сценарії:

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

Корисне посилання .


3
Якщо тільки немає X-сервера
user877329

1
ОП хоче знати про те dialog. Це так, як я приходжу і запитую у вас "Як написати це і те на пітоні?", Але ви даєте мені баш - я дуже радий, що це можна зробити по-іншому, але це не те, що я прошу
Сергій Колодяжний,

@Serg ваш коментар недійсний, моя відповідь не: Утиліта пропонує ідеально правильну та просту альтернативу рішенню, про яке вимагає ОП.
Wtower

3

Відповідь, надана Sneetsher, є дещо елегантнішою, але я можу пояснити, що не так: значення "" " $$є різним усередині задньої панелі (тому що він запускає нову оболонку і $$є PID поточної оболонки). Ви хочете поставити ім'я файлу в змінну, а потім посилатися на цю змінну напроти.

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

У цьому випадку кращим рішенням стане уникнення тимчасового файлу, але буде багато ситуацій, коли ви не можете уникнути тимчасового файлу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.