Простой интерактив в bash-скриптах, часть 2. Списки (меню).
В 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=$12. Список у меня в виде строк, разделенных стандартным переносом
\n, так что заодно добавлю пункт Cancel, чтобы пользователь мог отказаться от выбора.LIST_BUF="$LIST_BUF""Cancel"3. Оператор
select работает со списком значений, разделенных пробелами, так что надо символы \n заменить на пробелы:LIST_BUF=`echo -e "$LIST_BUF"|sed 's/\n/ /'`4. Устанавливаем подсказку ввода для пользователя, присвоив второй параметр функции специальной переменной
PS3.PS3=$25. Теперь используем оператор
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"
echo2. Как я уже говорил, функция
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"|nl5. Добавим, как и в предыдущем примере, пункт Cancel (Отмена) и пустую строку.
echo -e " C Cancel"
echo6. Далее организуем бесконечный цикл, как и в примере Да/нет из первой части:
while [ 1 -eq 1 ];do
#тут будет код
done7. В цикле первым делом используем оператор
read, с параметрами -n1 (читать 1 символ и завершать свою работу, передавая управление следующей команде) и -s (не отображать введенные символы):read -n1 -s8. Далее проверяем, не нажал ли пользователь клавишу
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=$REPLY10.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
Взаимодействие bash-скриптов с пользователем. Часть 2
Это репост с сайта http://tolik-punkoff.com
Оригинал: http://tolik-punkoff.com/2019/05/13/pros