Как получить ввод диалогового окна, направленный на переменную?

18

Я учил себя скриптовому Bash и столкнулся с проблемой. Я написал скрипт для получения ввода от пользователя с помощью команды 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?

emerikanbloke
источник
По моему опыту скрипт отлично работает, если убрать пустую строку после 2-й строки. Кроме того, вы можете использовать mktempкоманду для создания временного файла.
Ярно

Ответы:

16

: D Я не могу это объяснить !!! Если вы можете понять, что они говорят, в Руководстве по расширенному написанию сценариев: Глава 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. Все это описано как-то в самом простонародье man bash.

    • dialog --inputbox TEXT HEIGHT WIDTH
      Эта команда показывает окно TUI с заданным ТЕКСТОМ, полем ввода текста и двумя кнопками ОК и ОТМЕНА. Если выбрано ОК, команда завершается со статусом 0 и печатает введенный текст в STDERR, если выбирается ОТМЕНА, она завершается с кодом 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 перенаправляется на «поток -», что означает, что он должен быть закрыт. Данные, отправленные в поток 3, больше нигде не будут перенаправлены.

  • echo $result $exitcode

    Эта простая echoкоманда (больше информации man echo) просто печатает содержимое двух переменных Bash resultи exitcodeв STDOUT. Поскольку у нас больше нет явных или неявных перенаправлений потоков, они действительно будут появляться в STDOUT и поэтому просто будут отображаться в терминале. Какое чудо! ;-)

Резюме:

Сначала мы устанавливаем оболочку, чтобы перенаправить все, что мы посылаем, в пользовательский поток 3 обратно в STDOUT, чтобы он отображался в нашем терминале.

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

Нам также нужен dialogкод завершения команды, который показывает нам, был ли нажат OK или CANCEL. Это значение представлено в зарезервированной переменной Bash, $?и мы просто копируем его в нашу собственную переменную $exitcode.

После этого мы снова закрываем поток 3, так как он нам больше не нужен, чтобы остановить дальнейшие перенаправления.

Наконец, мы обычно выводим содержимое обеих переменных $result(пользовательский ввод диалогового окна) и $exitcode(0 для ОК, 1 для ОТМЕНА) на терминал.

Byte Commander
источник
Я думаю, что использование execизлишне сложно. Почему бы просто не использовать --stdoutопцию dialogили не перенаправить вывод 2>&1 >/dev/tty?
Ярно
Пожалуйста, посмотрите мой ответ .
Ярно
3
Отличный ответ! Тем не менее, я считаю, что у вас есть одна нота, которая является неправильной - вы говорите, что «они будут интерпретироваться справа налево», но я считаю, что это не так. В руководстве по bash gnu.org/software/bash/manual/html_node/Redirections.html это указывает, что перенаправления происходят, когда они встречаются (то есть слева направо)
17
14

Использование собственных инструментов диалога: --output-fd flag

Если вы читаете man-страницу для диалога, есть опция --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теперь будет ссылаться на / point на управляющий терминал, и любая команда, которая выполняется в этом сеансе оболочки, будет знать об этом.

Таким образом, когда это 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"

Ключевые вещи, которые нужно помнить:

  • файловые дескрипторы наследуются от оболочки каждой командой
  • Подстановка команд реализована в виде канала
  • Дублированные файловые дескрипторы будут ссылаться на то же место, что и исходный, но мы можем манипулировать каждым файловым дескриптором отдельно

Смотрите также

Сергей Колодяжный
источник
На man-странице также сказано, что --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.- Однако идея с именованным каналом - это круто!
Byte Commander
@ByteCommander «Может не получится» не очень убедительно, так как здесь нет примеров. Кроме того, они ничего не упоминают о том --output-fd, какой вариант я здесь использовал, а не --stdout. Во-вторых, диалог выводится на стандартный вывод первым, возвращаемый результат - вторым. Мы не делаем эти две вещи одновременно. Тем --output-fd не менее, специально не требуется использовать fd 1 (STDOUT). Его можно легко перенаправить на другой файловый дескриптор
Сергей Колодяжный
Я не уверен, может быть, это работает везде, может быть, это работает только на большинстве систем. Это работает на моем, и на странице руководства написано, что использовать подобную опцию следует с осторожностью - это все, что я знаю точно. Но, как я уже сказал, +1 в любом случае заслуживает именованных каналов.
Byte Commander
Я должен прокомментировать здесь, чтобы сохранить равновесие. Для меня это может быть единственным прямым каноническим ответом (1) он использует только один и тот же инструмент и реализовал опции без какого-либо внешнего инструмента (2) он работает в Ubuntu и это все, что есть в AU. К сожалению, ОП, похоже, отказывается от этого вопроса.
user.dz
В чем преимущество использования именованного канала вместо обычного файла? Вы не хотите удалить трубу после использования?
Ярно
7

: D Я не могу это объяснить !!! Если вы понимаете, о чем они говорят в справочнике: Расширенное руководство по написанию сценариев: Глава 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. Перенаправление ввода / вывода

user.dz
источник
это предложение остается в силе? Я думаю, я мог бы объяснить, что вы нашли там полтора года назад ... :-)
Byte Commander
@ByteCommander, но, если вы можете предоставить это, я дам вам это, я буду по моим словам: D.
user.dz
@ByteCommander, пожалуйста, пингуй меня после того, как ты отправишь это сообщение.
user.dz
1
Законченный! askubuntu.com/a/704616/367990 Я надеюсь, что вы все понимаете и наслаждаетесь "Эврика!" момент. :-D Оставьте комментарий, если что-то осталось неясным.
Byte Commander
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 . Zenity - графическая утилита, которую можно использовать внутри скриптов bash. Но, конечно, для этого потребуется X-сервер, как справедливо указал user877329.

sudo apt-get install zenity

Тогда в вашем сценарии:

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

Полезная ссылка .

Wtower
источник
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

В этом случае было бы лучше избежать временного файла, но будет много ситуаций, когда вы не сможете избежать временного файла.

tripleee
источник