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

Я учил себя скриптовому 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?

6 ответов

:D Я не могу это объяснить!!! Если вы можете понять, что они говорят, в Руководстве по расширенному написанию сценариев: Глава 20. Перенаправление ввода / вывода, напишите новый ответ, и я дам вам 50rep:

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

Ссылка: диалог в bash неправильно захватывает переменные

^ ответ от user.dz (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 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 и поэтому просто будут отображаться в терминале. Какое чудо!;-)

Резюме:

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

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

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

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

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

Использование собственных инструментов диалога: --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() syscall позволяет нам дублировать файловые дескрипторы, заставляя их эффективно ссылаться на одно и то же место, но мы можем управлять ими по отдельности. Файловые дескрипторы процессов, к которым подключен управляющий терминал, фактически указывают на конкретное терминальное устройство. Это очевидно, если вы делаете

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

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

  • файловые дескрипторы наследуются от оболочки каждой командой
  • Подстановка команд реализована в виде канала
  • duplicated file descriptors will refer to same place as original one, but we can manipulate each file descriptor separately

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

: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. Перенаправление ввода / вывода

Это работает для меня:

#!/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 вместо этого работать лучше тогда?

В случае, если кто-то еще тоже попал сюда из Google, и хотя этот вопрос задается специально для bash, здесь есть другая альтернатива:

Вы можете использовать zenity. Zenity - графическая утилита, которую можно использовать внутри скриптов bash. Но, конечно, для этого потребуется X-сервер, как справедливо указал user877329.

sudo apt-get install zenity

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

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

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

Ответ, предоставленный Снишером, несколько более элегантен, но я могу объяснить, что не так: ценность $$ отличается внутри обратных тики (потому что он запускает новую оболочку, и $$ это 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

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

Другие вопросы по тегам