Взаимодействие bash-скриптов с пользователем

    Любой приказ, который может быть неправильно понят, понимается неправильно (Армейская аксиома)

    Редкий скрипт лишен необходимости общения с пользователем. Мы ожидаем, что программа (утилита) будет выполнять то, что нам от нее хочется. Следовательно, нужны инструменты влияния на них, да и программа сама должна объяснить, как продвигается ее работа.
    Данным топиком я хочу рассмотреть несколько способов взаимодействия bash-скриптов с пользователем. Статья рассчитана на новичков в скриптинге, но, надеюсь, люди опытные тоже найдут что-нибудь интересное для себя.

    Топик так же снабжен примитивными примерами, не несущими смысловой нагрузки, но позволяющими посмотреть в работе некоторые интересные штуки.

    Переменные


    Самый распространенный способ хранения начальных данных — переменные. В самом начале программы объявляются несколько таких переменных, в которые пользователь записывает некоторые исходные данные.
    #!/bin/bash
    
    # Вписать сюда адрес электронной почты
    EMAIL=example@gmail.com
    
    echo "Адрес электронной почты: $EMAIL"
    

    Такой способ хорош, если данных не много и скрипт рассчитан на автоматическое выполнение без участия пользователя. Необходимо ясно известить пользователя о том, что и где ему необходимо вписать. Желательно собрать все это в одном месте — файле конфигурации. Подключить его можно командой source. Например, если конфигурационный файл будет лежать в той же директории, что и скрипт, мы получим:
    #!/bin/bash
    
    source ./config.cfg
    
    echo "Адрес электронной почты: $EMAIL"
    

    В файл config.cfg не забудем поместить строчку EMAIL=example@gmail.com

    Параметры командной строки


    Еще один способ сообщить данные программе — указать при запуске в командной строке. Содержатся эти параметры в переменных с номерами. Например: $0 — имя скрипта, $1 — первый параметр, $2 — второй параметр и т. д. Также существуют две вспомогательные переменные: $# содержит количество переданных аргументов; $@ содержит все аргументы, переданные скрипту, разделенные пробелами.

    #!/bin/bash
    
    # Цикл выдаст все переданные аргументы
    for n in $@
    do
      echo "$n"
    done
    



    Вопросы и подтверждения



    Думаю многим знаком вопрос со скриншота выше. Такой диалог можно использовать… ну вы и сами догадались, где его можно использовать.
    #!/bin/bash
    
    echo -n "Продолжить? (y/n) "
    
    read item
    case "$item" in
        y|Y) echo "Ввели «y», продолжаем..."
            ;;
        n|N) echo "Ввели «n», завершаем..."
            exit 0
            ;;
        *) echo "Ничего не ввели. Выполняем действие по умолчанию..."
            ;;
    esac
    

    Обратите внимание, что на скриншоте буква «Д» — большая. Это означает действие по умолчанию, то есть если пользователь ничего не введет, то это будет равнозначно вводу «Д».

    OK / FAIL


    Еще одним способом общения программы с пользователем являются статусы выполнения. Скорее всего они вам знакомы.

    Реализация тоже довольно проста.
    #!/bin/bash
    
    SETCOLOR_SUCCESS="echo -en \\033[1;32m"
    SETCOLOR_FAILURE="echo -en \\033[1;31m"
    SETCOLOR_NORMAL="echo -en \\033[0;39m"
    
    echo -e "Удаляется файл..."
    
    # Команда, которую нужно отследить
    rm test_file
    
    if [ $? -eq 0 ]; then
        $SETCOLOR_SUCCESS
        echo -n "$(tput hpa $(tput cols))$(tput cub 6)[OK]"
        $SETCOLOR_NORMAL
        echo
    else
        $SETCOLOR_FAILURE
        echo -n "$(tput hpa $(tput cols))$(tput cub 6)[fail]"
        $SETCOLOR_NORMAL
        echo
    fi
    

    Вот так выглядит работа скрипта:

    Хорошие люди написали расширенную версию скрипта с логированием и прогресом выполнения. С радостью поделюсь ссылкой.

    Исходя из вышеприведенной ссылки код можно упростить.
    #!/bin/bash
    
    red=$(tput setf 4)
    green=$(tput setf 2)
    reset=$(tput sgr0)
    toend=$(tput hpa $(tput cols))$(tput cub 6)
    
    echo -e "Удаляется файл..."
    
    # Команда, которую нужно отследить
    rm test_file
    
    if [ $? -eq 0 ]; then
        echo -n "${green}${toend}[OK]"
    else
        echo -n "${red}${toend}[fail]"
    fi
    echo -n "${reset}"
    echo
    


    Псевдографика


    Для любителей графического представления существуют удобный инструмент: dialog. По умолчанию его в системе нет, так что исправим положение.
    sudo apt-get install dialog
    

    Опробовать его можно простой командой:
    dialog --title " Уведомление " --msgbox "\n Свершилось что-то страшное!" 6 50
    

    Вот пример диалога прогресса:
    #!/bin/sh
    
    (
    c=10
    while [ $c -ne 110 ]
        do
            echo $c
            ((c+=10))
            sleep 1
    done
    ) |
    dialog --title " Тест диалога прогресса " --gauge "Please wait ...." 10 60 0
    
    clear
    

    Не забываем вставлять clear для очистки экрана, чтобы не оставлять синий фон. Эта утилита поддерживает еще очень много типов диалоговых окон. Главным недостатком является то, что по умолчанию ее нет в системе.

    Альтернативой dialog может служить whiptail, который даже присутствует в некоторых системах по умолчанию.

    Подробнее можно ознакомиться по ссылкам:
    http://unstableme.blogspot.com/2009/12/linux-dialog-utility-short-tutorial.html
    http://www.cc-c.de/german/linux/linux-dialog.php

    GUI


    Хоть есть ярые противники GUI, но он явно имеет право на существование. Такие диалоги можно получить с помощью команды kdialog (если графической оболочкой выступает KDE), либо gdialog и zenity (для Gnome).

    Например, форма для ввода пароля:
    kdialog --password "Пожалуйста, введите свой пароль:"
    

    либо
    gdialog --password "Пожалуйста, введите свой пароль:"
    

    Еще пример один для KDE:
    kdialog --question "Вы хотите продолжить?"
    rc=$?
    if [ "${rc}" == "0" ]; then
        echo "Нажали yes"
    else
        echo "Нажали no"
    fi
    

    И для Gnome:
    #!/bin/bash
    
    name=$(gdialog --title "Ввод данных" --inputbox "Введите ваше имя:" 50 60 2>&1)
    echo "Ваше имя: $name"
    

    Как видите, явным недостатком этого метода является привязанность к конкретной среде рабочего стола. Да и вообще к графической среде, которая может и отсутствовать. Но, тем не менее, может и пригодиться когда-нибудь.

    Подробнее по ссылкам:
    http://pwet.fr/man/linux/commandes/kdialog
    http://linux.about.com/library/cmd/blcmdl1_gdialog.htm
    http://www.techrepublic.com/blog/opensource/gui-scripting-in-bash/1667

    P.S. Продолжение следует…

    UPD: Добавил упрощенный код в раздел «OK / FAIL».
    UPD2: Добавил пример подключения конфигурационного файла в раздел «Переменные».

    Опубликована вторая часть.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 48
    • +9
      echo -n "$(tput hpa $(tput cols))$(tput cub 6)[OK]"

      Можете объяснить что это за уличная магия?
      • +16
        tput hpa N — сдвиг курсора на N позиций
        tput cols получает ширину окна терминала
        tput cub 6 — сдвигает курсор на 6 позиций влево

        Таким образом [OK] прижимается к краю.
        • +4
          Спасибо
          • +3
            Если ты используешь tput для указания положения текста, то почему бы им же не выставлять цвет текста, вместо эскейп-кодов?
            tput setaf 0..7 — цвет текста
            tput setab 0..7 — цвет фона
            tput srg0 — сброс на дефолтный
            И другие опции
            • 0
              Простите ошибочка
              tput sgr0 — сброс на дефолтный
              • 0
                хреново читаю. уже есть: )
            • +7
              "Изучаем tput"
              tput cols — получение числа колонок на устройстве вывода
              tput hpa _ — передвинуть курсор в позицию __
              $(tput hpa $(tput cols) — передвинуть курсор в конец строки
              tput cub 6 — сдвинуть курсор влево на 6 знаков.

              Вне зависимости от размера экрана, курсор позиционируется на 6 символов от правого края. Потом печатается нужный текст.
            • +1
              А ещё хотелось бы знать, во всех ли системах есть команда tput, насколько она стабильна и т.п. А то по прошествии года университетские лабораторки почему-то не запустились — обновление что-то сломало…
              • +2
                Авторитетным источником выступить не смогу, но из строчки "tput is a standard Unix operating system command which is used to set terminal features" можно сделать вывод, что эта команда присутствует и во всех *nix системах и стабильна.
                • +2
                  Может ещё зависеть от (настроек) терминала, видимо. Живой пример — tput cols, cub и cup работает, а tput hpa — нет.
              • +6
                Ежели взаимодействие на bash — вы просто обязаны рассказать о select.

                select action in "ololo" "upya4ka" "exit"
                do
                echo $action
                test $action = "exit" && break
                done
                • +3
                  Спасибо. В следующей части расскажу. А еще про нотиферы и звуковое оповещение. Есть еще пожелания?
                • +2
                  >rm test_file || ok=0

                  есть же $? — статус выполнения последней команды.

                  кроме того, скрипт из примера съедает ошибку и в своем коде возврата всегда возвращает 0, типа ничего и не было.
                  • +1
                    Действительно, исправил. Спасибо.
                    Просто на практике мне приходилось анализировать сразу несколько команд, и там предыдущий вариант казался логичным. Про $? я и не подумал даже.
                  • +2
                    whiptail забыли. там же такие красивые менюшечки ((:
                    whiptail --title Habr --checklist "Simple checkbox menu" 12 35 3 $(echo {hello,habra,habr}" '' 0" )
                    • +3
                      Можно сказать, что не забыл. Помните, в статье я рассказывал про dialog. А теперь фокус… Заменяем whiptail на dialog:
                      dialog --title Habr --checklist «Simple checkbox menu» 12 35 3 $(echo {hello,habra,habr}" '' 0" )
                      И смотрим, что получилось :)
                      • +2
                        Ну все же один на ncurses основан, а другой на newt и slang.
                        Хотя разница, с точки зрения пользователя, не так уж и велика.
                        • +2
                          Хорошо, спасибо. Напишу в статье, что существует альтернатива.
                          • +1
                            Тут еще можно поспорить, кто кому альтернативой является: зачастую whiptail установлен в дефолтной системе, а вот dialog еще ставить надо.
                    • +3
                      Есть еще getopts. Когда надо передать различные параметры и ключи, удобнее чем через ${1-9}
                      • +1
                        О, вот про что я забыл! Спасибо.
                      • +1
                        Ни слова про функции, думаю это фундаментально.
                        • +1
                          А также ничего про то как включить файл конфигурации, хотя про него сказано. Для быстрого справочника который Вы пытаетесь сделать, думаю информация была бы уместной.
                          • +1
                            Про функции я хотел упомянуть, когда буду рассказывать про getopts. Про включение файла конфигурации вставлю. Хотя я больше интерфейсы хотел рассмотреть. Их применение программистом может быть очень разное.
                        • +1
                          Спасибо большое!
                          • +2
                            Материал интересный, но продолжение нужно обязательно, т.к. есть еще много нераскрытых моментов, как например (заметили уже) — функции.

                            Нашел свой скрипт, на котором немного изучил bash. Он помогает обновлять систему на машине без интернета, с помощью обычной флешки: сначала создаётся список пакетов, на другой машине (с интернетом) они закачиваются, потом на первой машине устанавливаются.
                            Там есть примеры взаимодействия с пользователем, функции, всяческие условия-циклы, использование других утилит (gpg в частности), комменты и сообщения на русском, короче, вдруг пригодится кому:

                            dl.dropbox.com/u/22991016/updates.sh
                            • +1
                              Спасибо за примеры!!!
                              • +1
                                ncurses я не увидел тут
                                • +1
                                  Как видите, явным недостатком этого метода является привязанность к конкретной среде рабочего стола. Да и вообще к графической среде, которая может и отсутствовать. Но, тем не менее, может и пригодиться когда-нибудь.

                                  Можно в скрипте анализировать переменную окружения DESKTOP_SESSION. Различать GNOME, KDE, какую-то другую (переменная определена, но не равна ни GNOME, ни KDE) и текстовую консоль (переменная не определена) для того, чтобы выбрать gdialog/zenity (думаю стоит указать, что zenity это обновленный gdialog, доступный в GNOME уже давно), kdialog, dialog.

                                  Пытался в свое время найти какой-то стандартный способ определения в скриптах не только среды исполнения, но и пользовательских предпочтений — не смог. Может кто знает?
                                  • +1
                                    Я не сталкивался с необходимостью использования GUI в скриптах. Да и неправильно это как-то. Консольный вывод привычнее.
                                    • +1
                                      Кому привычней, а кому нет. Да и задача может стоять как создание GUI приложения, являющегося по сути враппером к CLI инструментам. Неужели разрабатывать полноценное приложение на Сях, питоне или, прости господи, PHP?
                                  • +2
                                    > source ./config.cfg
                                    . config.cfg

                                    :)
                                    • +2
                                      А если скрипт запускается из левой директории можно делать так:
                                      MyDir=`cd -P "$(dirname "$0")" && pwd`

                                      . "$MyDir"/config.cfg

                                      • 0
                                        Да, . config.cfg проще и удобнее, но source ./config.cfg нагляднее (:

                                        Но вот не пойму зачем:
                                        MyDir=`cd -P "$(dirname "$0")" && pwd`
                                        . "$MyDir"/config.cfg


                                        Разве . config.cfg не всегда сработает?
                                        • +1
                                          Не сработает, если скрипт запущен из другой рабочей директории.
                                          • 0
                                            Либо я чего-то не догоняю, либо одно из двух. Команда cd -P "$(dirname "$0")" && pwd указывает в ту директорию, их которой запущен скрипт. А значит дает тот же результат, что и . config.cfg. Так ведь?
                                            • +1
                                              Ох блин…
                                              Ок, на примере :)
                                              Скрипт у нас лежит в /root, называется допустим script.sh.
                                              Находясь в директории /root вызываем /root/script.sh — все ок.
                                              Находясь в любой другой директории вызываем /root/script.sh — получаем ошибку о том что не найден config.cfg.
                                              cd -P "$(dirname "$0")" && pwd меняет директорию на ту, где расположен скрипт, а не откуда он запущен, таким образом позволяя подгрузить конфиг. Пишу ее в переменную MyDir я для того, чтобы если что иметь возможность переключаться между директориями. Развивая тему:

                                              # определяем рабочую директорию
                                              WorkDir=`pwd`

                                              # определяем директорию где расположен скрипт с конфигом и возвращаемся обратно
                                              MyDir=`cd -P "$(dirname "$0")" && pwd && cd "$WorkDir"`

                                              # читаем конфиг
                                              . "$MyDir"/config.cfg

                                              # далее выполняем всю работу как обычно, в текущей рабочей папке
                                    • 0
                                      Гуястам:
                                      xmessage -buttons Yes:0,No:1 -default Yes 'Are you sure?'

                                      xmessage, правда, дистроспецифичен. С недавнего времени наличие иксов в арчике не подразумевает автоматически его наличия.

                                      Ну а если делать красиво, то, видимо, анализировать $DESKTOP_SESSION и в зависимости от его значения вызывать kdialog/gdialog
                                      • 0
                                        да причём здесь вообще гуй?
                                        в топике ясно написано — баш-скрипты.
                                        В основном работа идёт на удалённых серверах, на которых и быть не может иксов.
                                        • 0
                                          А если пишем скрипт установки какой-нибудь игрушки? Юникс не только для серверов!
                                          • 0
                                            Паковать в deb/rpm/etc
                                            Не надо вот этих вот скриптов установки.
                                            • 0
                                              А если политика партии требует устанавливать только для текущего пользователя, в $HOME? Причём дело может быть не только в копирастии, но и в желании не требовать для неё прав супер пользователя.
                                              • 0
                                                Тогда зачем вам вообще инсталлятор?
                                                Пакуйте игру в архив, пользователь распакует где захочет и запустит там же.
                                            • 0
                                              юникс, иксы…
                                              minix, macos x — тоже вроде как юниксы, но вот как-то эти kde,gnome-советы тут не годятся.

                                              Вообщем я за то чтобы перетянуть это в блог оболочки.
                                              • 0
                                                Это не совсем оболочки. Я хотел публиковать в интерфейсы, но тогда кармы не хватило. Хотя сейчас думаю, что *nix все-таки более подходящий блог. Можно считать два последних заголовка приложением.
                                            • 0
                                              В любом случае, полезно иметь на счету такие инструменты, как GUI. Есть пользователи, которые панически боятся консоль. Или захочется получить эстетическое удовольствие.
                                              А вообще, GUI будет полезен, если скрипт автостартует с графической оболочкой.

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.