В bash-скрипте можно организовать простое меню, в виде списка, и предоставить пользователю выбор из нескольких вариантов.
Список вариантов можно сформировать вручную, просто перечислив нужные в скрипте. Другой способ — сформировать список динамически, так, как это, например, описано здесь копия. Покажу второй способ.
Сначала возьмем из скрипта по ссылке выше функцию create_list()
и необходимые переменные:
FOUNDLST="" DIR1="/home/smallwolfie/test/archives" TARGZ="" create_list() #$1 - dir, $2 - file mask { FOUNDLST="" for FLE in $(find $1 -maxdepth 1 -iname $2); do if [ -n "$FLE" ]; then FOUNDLST="$FOUNDLST"`basename $FLE`"\n" fi done } #тут будет функция вывода списка #... #создаем список create_list $DIR1 "*.tar.gz" if [ -z "$FOUNDLST" ]; then echo "$DIR1 *.tar.gz not found" exit else TARGZ="$FOUNDLST" fi
Организуем это дело в виде отдельной функции ask_list()
, куда первым параметром передается список, а вторым — запрос для пользователя.
1. Скопируем содержимое первого параметра в переменную $LIST_BUF
, со списком придется сделать несколько преобразований:
LIST_BUF=$1
2. Список у меня в виде строк, разделенных стандартным переносом \n
, так что заодно добавлю пункт Cancel, чтобы пользователь мог отказаться от выбора.
LIST_BUF="$LIST_BUF""Cancel"
3. Оператор select
работает со списком значений, разделенных пробелами, так что надо символы \n
заменить на пробелы:
LIST_BUF=`echo -e "$LIST_BUF"|sed 's/\n/ /'`
4. Устанавливаем подсказку ввода для пользователя, присвоив второй параметр функции специальной переменной PS3
.
PS3=$2
5. Теперь используем оператор select
:
echo select LIST_RET in $LIST_BUF; do if [ -n "$LIST_RET" ];then break fi done
На экран будет выведен список, а пользователю будет предложено ввести номер из списка:
smallwolfie@wolfschanze: ~/test/$ ./asklist2
1) slinstall.tar.gz 3) teSt.Tar.gZ 5) Cancel
2) slmini.tar.gz 4) TEST.TAR.GZ
Select file:
Если внутри конструкции select..done
не поставить break
, то цикл выбора окажется бесконечным.
Значение (строка после номера) будет записано в переменную, указанную после ключевого слова select
.
Если пользователь введет номер не из списка, или вообще что-то левое, то переменная $LIST_RET
окажется пустой.
Здесь внутри конструкции select..done
добавлена конструкция для проверки этого. Если переменная окажется пустой — пользователю будет предложено повторить ввод:
smallwolfie@wolfschanze: ~/test/$ ./asklist-select
1) slinstall.tar.gz 3) teSt.Tar.gZ 5) Cancel
2) slmini.tar.gz 4) TEST.TAR.GZ
Select file: blablabla
Select file:
Функция целиком:
ask_list() #$1 - list #$2 - header { LIST_BUF=$1 LIST_BUF="$LIST_BUF""Cancel" LIST_BUF=`echo -e "$LIST_BUF"|sed 's/\n/ /'` PS3=$2 echo select LIST_RET in $LIST_BUF; do if [ -n "$LIST_RET" ];then break fi done }
Пример вызова функции:
ask_list "$TARGZ" "Select file: " if [[ "$LIST_RET" == "Cancel" ]]; then echo "Cancelled by user!" exit fi echo "User select: $LIST_RET"
Результат:
smallwolfie@wolfschanze: ~/test/$ ./asklist-select
1) slinstall.tar.gz 3) teSt.Tar.gZ 5) Cancel
2) slmini.tar.gz 4) TEST.TAR.GZ
Select file: 5
Cancelled by user!
smallwolfie@wolfschanze: ~/test/$ ./asklist-select
1) slinstall.tar.gz 3) teSt.Tar.gZ 5) Cancel
2) slmini.tar.gz 4) TEST.TAR.GZ
Select file: 3
User select: teSt.Tar.gZ
Скрипт на GitHub
Скрипт на PasteBin
Организуем это дело также в виде функции
ask_list()
с аналогичным набором параметров, только оператором select
пользоваться не будем, а сделаем, как в примере с вопросом Да/Нет из предыдущей части копия. Начало и конец скрипта останутся теми же, рассмотрим только видоизмененную функцию ask_list()
.Вместо оператора
select
будем выводить список сами, а для ввода данных воспользуемся оператором read
.
Минус способа: данный вариант подходит для списков с небольшим количеством вариантов, от 1 до 9. Ну и да, в таком виде это будет корректно работать только в оболочке bash, ибо есть башизм.
Плюс способа: на экране ничего не меняется, если пользователь ввел неверное значение (т.е. такое меню выглядит симпатичнее, и с экрана никуда не уползет). И заголовок меню будет сверху.
Еще один плюс — можно использовать элементы списка с пробелами.
1. Присваиваем первый параметр функции переменной LIST_BUF
, затем выводим второй параметр — заголовок меню, и выводим пустую строку, отделяющую заголовок от меню:
LIST_BUF=$1
echo "$2"
echo
2. Как я уже говорил, функция create_list()
формирует список в виде строк с символом переноса строки (\n
) на конце, поэтому последний символ \n
надо отрезать, если он есть, чтобы в дальнейшем пункт Cancel
оказался на нужном месте на экране, а не был отделен пустой строкой, и чтобы правильно было посчитано количество строк (элементов) в списке. Для этого и применяется такой вот жутковатый башизм:
START_CHR=$((${#LIST_BUF}-2)) CHR_BUF=${LIST_BUF:$START_CHR:2} if [[ "$CHR_BUF" == "\n" ]];then LIST_BUF=${LIST_BUF::-2} fi
Сначала получаем номер начального символа, учитывая то, что перенос строки хранится в переменной в виде эскейп-последовательности (т.е. не как символ с кодом 0x0A
, а в виде последовательности символов \
и n
), т.е надо взять 2 символа с конца строки. Потом вырезаем эти 2 символа, сравниваем с шаблоном, и, наконец, отрезаем его, если он таки есть.
3. Получаем количество строк, хранящихся в переменной $LIST_BUF
, выводим ее с помощью echo
с параметром -e
(т.е. преобразовать эскейп-последовательности в реальные символы) и передать их команде wc -l
, подсчитывающей количество строк:
LIST_CTR=`echo -e "$LIST_BUF"|wc -l`
4. Выведем список строк на экран. Опять же, передадим содержимое списка в $LIST_BUF
команде echo -e
, а потом этот вывод поступит команде nl
, которая пронумерует каждую строку (последовательность символов, заканчивающуюся переносом строки).
echo -e "$LIST_BUF"|nl
5. Добавим, как и в предыдущем примере, пункт Cancel (Отмена) и пустую строку.
echo -e " C Cancel"
echo
6. Далее организуем бесконечный цикл, как и в примере Да/нет из первой части:
while [ 1 -eq 1 ];do #тут будет код done
7. В цикле первым делом используем оператор read
, с параметрами -n1
(читать 1 символ и завершать свою работу, передавая управление следующей команде) и -s
(не отображать введенные символы):
read -n1 -s
8. Далее проверяем, не нажал ли пользователь клавишу C
(или c
). Да, дополнительный символ x
перед переменными и строками нужен, чтоб правильно сработало логическое условие ИЛИ
(||
), ну вот такой кривой bash (и shell вообще), что я-то сделаю:
if [[ "x$REPLY" == "xC" || "x$REPLY" == "xc" ]]; then LIST_RET="C" return fi
Если нажал, записываем в переменную LIST_RET
значение C
. Значит, пользователь отменил ввод.
9. Далее, следует такая вот конструкция:
if (echo "$REPLY" | grep -E -q "^?[0-9]+$"); then if [ "$REPLY" -gt 0 -a "$REPLY" -le "$LIST_CTR" ]; then #тут код, который рассмотрим ниже fi fi
В первом if
‘е определяем, оказалось ли в специальной переменной $REPLY
число копия заметки про определение числа в bash, если нет, то опять возвращаемся в цикл ввода.
Во втором условии проверяем, чтобы введенное значение было больше (-gt
0)
, и (-a
) меньше или равно последнему элементу списка (-le "$LIST_CTR"
)
Если так, то выполнится код внутри условия, если нет — экран пользователя не изменится, и ему придется ввести корректное значение из списка.
10. Код в условии:
10.1 В переменную LIST_RET
запишем то, что нажал пользователь:
LIST_RET=$REPLY
10.2. Далее получим значение (текстовую строку) элемента:
Передадим содержимое переменной $LIST_BUF
, команде awk
, чья задача найти нужный номер строки:
LIST_ITEM=`echo -e "$LIST_BUF"|awk -v lnum="${LIST_RET}" '(NR == lnum)'`
С помощью конструкции awk -v lnum="${LIST_RET}
, программе awk
передается номер строки, который нужно найти, далее, значение переменной LIST_RET
предается в переменную скрипта awk
, и awk
вытаскивает строку с нужным номером из переменной, содержащей весь список.
Вся функция:
ask_list() #$1 - list #$2 - header { LIST_BUF=$1 echo "$2" echo START_CHR=$((${#LIST_BUF}-2)) CHR_BUF=${LIST_BUF:$START_CHR:2} if [[ "$CHR_BUF" == "\n" ]];then LIST_BUF=${LIST_BUF::-2} fi LIST_CTR=`echo -e "$LIST_BUF"|wc -l` echo -e "$LIST_BUF"|nl echo -e " C Cancel" echo while [ 1 -eq 1 ];do read -n1 -s if [[ "x$REPLY" == "xC" || "x$REPLY" == "xc" ]]; then LIST_RET="C" return fi if (echo "$REPLY" | grep -E -q "^?[0-9]+$"); then if [ "$REPLY" -gt 0 -a "$REPLY" -le "$LIST_CTR" ]; then LIST_RET=$REPLY LIST_ITEM=`echo -e "$LIST_BUF"|awk -v lnum="${LIST_RET}" '(NR == lnum)'` return fi fi done }
Пример вызова функции:
ask_list $TARGZ "Select file" if [[ "$LIST_RET" == "C" ]]; then echo "Cancelled by user" else echo "User select item: $LIST_RET" echo "Item value: $LIST_ITEM" fi
Вывод в консоль:
smallwolfie@wolfschanze: ~/test/$ ./asklist Select file 1 slinstall.tar.gz 2 slmini.tar.gz 3 teSt.Tar.gZ 4 TEST.TAR.GZ C Cancel
Отмена:
smallwolfie@wolfschanze: ~/test/$ ./asklist Select file 1 slinstall.tar.gz 2 slmini.tar.gz 3 teSt.Tar.gZ 4 TEST.TAR.GZ C Cancel Cancelled by user
Выбор из списка:
smallwolfie@wolfschanze: ~/test/$ ./asklist Select file 1 slinstall.tar.gz 2 slmini.tar.gz 3 teSt.Tar.gZ 4 TEST.TAR.GZ C Cancel User select item: 2 Item value: slmini.tar.gz
Скрипт на GitHub
Скрипт на PasteBin
Pingback: Интерактивный скрипт для переключения VPN’ок | Персональный блог Толика Панкова