479 lines
26 KiB
Markdown
479 lines
26 KiB
Markdown
[https://habr.com/ru/company/ruvds/blog/326328/](https://habr.com/ru/company/ruvds/blog/326328/)
|
||
|
||
---
|
||
|
||
Освоив предыдущие части этой серии материалов, вы узнали о том, что такое bash-скрипты, как их писать, как управлять потоком выполнения программы, как работать с файлами. Сегодня мы поговорим о том, как добавить скриптам интерактивности, оснастив их возможностями по получению данных от пользователя и по обработке этих данных.
|
||
|
||
Наиболее распространённый способ передачи данных сценариям заключается в использовании параметров командной строки. Вызвав сценарий с параметрами, мы передаём ему некую информацию, с которой он может работать. Выглядит это так:
|
||
|
||
```
|
||
$ ./myscript 10 20
|
||
```
|
||
|
||
В данном примере сценарию передано два параметра — «10» и «20». Всё это хорошо, но как прочесть данные в скрипте?
|
||
|
||
## Чтение параметров командной строки
|
||
|
||
Оболочка bash назначает специальным переменным, называемым позиционными параметрами, введённые при вызове скрипта параметры командной строки:
|
||
|
||
- `$0 —` имя скрипта.
|
||
- `$1 —` первый параметр.
|
||
- `$2 —` второй параметр — и так далее, вплоть до переменной `$9`, в которую попадает девятый параметр.
|
||
|
||
Вот как можно использовать параметры командной строки в скрипте с помощью этих переменных:
|
||
|
||
```
|
||
#!/bin/bash
|
||
echo $0
|
||
echo $1
|
||
echo $2
|
||
echo $3
|
||
```
|
||
|
||
Запустим сценарий с параметрами:
|
||
|
||
```
|
||
./myscript 5 10 15
|
||
```
|
||
|
||
Вот что он выведет в консоль.
|
||
|
||
![[fb5f9fb80361a4083e31d7225fa3cdbf.png]]
|
||
|
||
_Вывод параметров, с которыми запущен скрипт_
|
||
|
||
Обратите внимание на то, что параметры командной строки разделяются пробелами.
|
||
|
||
Взглянем на ещё один пример использования параметров. Тут мы найдём сумму чисел, переданных сценарию:
|
||
|
||
```
|
||
#!/bin/bash
|
||
total=$[ $1 + $2 ]
|
||
echo The first parameter is $1.
|
||
echo The second parameter is $2.
|
||
echo The sum is $total.
|
||
```
|
||
|
||
Запустим скрипт и проверим результат вычислений.
|
||
|
||
![[646db142c4fe6ebfc41f66b2a65534e6.png]]
|
||
|
||
_Сценарий, который находит сумму переданных ему чисел_
|
||
|
||
Параметры командной строки не обязательно должны быть числами. Сценариям можно передавать и строки. Например, вот скрипт, работающий со строкой:
|
||
|
||
```
|
||
#!/bin/bash
|
||
echo Hello $1, how do you do
|
||
```
|
||
|
||
Запустим его:
|
||
|
||
```
|
||
./myscript Adam
|
||
```
|
||
|
||
Он выведет то, что мы от него ожидаем.
|
||
|
||
![[f450d1b0337e0533990d1a25f2890e43.png]]
|
||
|
||
_Сценарий, работающий со строковым параметром_
|
||
|
||
Что если параметр содержит пробелы, а нам надо обрабатывать его как самостоятельный фрагмент данных? Полагаем, если вы освоили предыдущие части этого руководства, ответ вы уже знаете. Заключается он в использовании кавычек.
|
||
|
||
Если скрипту надо больше девяти параметров, при обращении к ним номер в имени переменной надо заключать в фигурные скобки, например так:
|
||
|
||
```
|
||
${10}
|
||
```
|
||
|
||
## Проверка параметров
|
||
|
||
Если скрипт вызван без параметров, но для нормальной работы кода предполагается их наличие, возникнет ошибка. Поэтому рекомендуется всегда проверять наличие параметров, переданных сценарию при вызове. Например, это можно организовать так:
|
||
|
||
```
|
||
#!/bin/bash
|
||
if [ -n "$1" ]
|
||
then
|
||
echo Hello $1.
|
||
else
|
||
echo "No parameters found. "
|
||
fi
|
||
```
|
||
|
||
Вызовем скрипт сначала с параметром, а потом без параметров.
|
||
|
||
![[b8c9ce15f6793d007f1d5460a7a20078.png]]
|
||
|
||
_Вызов скрипта, проверяющего наличие параметров командной строки_
|
||
|
||
## Подсчёт параметров
|
||
|
||
В скрипте можно подсчитать количество переданных ему параметров. Оболочка bash предоставляет для этого специальную переменную. А именно, переменная `$#` содержит количество параметров, переданных сценарию при вызове.
|
||
|
||
Опробуем её:
|
||
|
||
```
|
||
#!/bin/bash
|
||
echo There were $# parameters passed.
|
||
```
|
||
|
||
Вызовем сценарий.
|
||
|
||
```
|
||
./myscript 1 2 3 4 5
|
||
```
|
||
|
||
В результате скрипт сообщит о том, что ему передано 5 параметров.
|
||
|
||
![[b4919fe68032df326b43cc39a88e718d.png]]
|
||
|
||
_Подсчёт количества параметров в скрипте_
|
||
|
||
Эта переменная даёт необычный способ получения последнего из переданных скрипту параметров, не требующий знания их количества. Вот как это выглядит:
|
||
|
||
```
|
||
#!/bin/bash
|
||
echo The last parameter was ${!#}
|
||
```
|
||
|
||
Вызовем скрипт и посмотрим, что он выведет.
|
||
|
||
![[e7f7ed0e751be4c5156ae49b7e8ea86d.png]]
|
||
|
||
_Обращение к последнему параметру_
|
||
|
||
## Захват всех параметров командной строки
|
||
|
||
В некоторых случаях нужно захватить все параметры, переданные скрипту. Для этого можно воспользоваться переменными `$*` и `$@`. Обе они содержат все параметры командной строки, что делает возможным доступ к тому, что передано сценарию, без использования позиционных параметров.
|
||
|
||
Переменная `$*` содержит все параметры, введённые в командной строке, в виде единого «слова».
|
||
|
||
В переменной `$@` параметры разбиты на отдельные «слова». Эти параметры можно перебирать в циклах.
|
||
|
||
Рассмотрим разницу между этими переменными на примерах. Сначала взглянем на их содержимое:
|
||
|
||
```
|
||
#!/bin/bash
|
||
echo "Using the \$* method: $*"
|
||
echo "-----------"
|
||
echo "Using the \$@ method: $@"
|
||
```
|
||
|
||
Вот вывод скрипта.
|
||
|
||
![[ee1579f3d1c96830d6c2484f6124c89e.png]]
|
||
|
||
_Переменные $* и $@_
|
||
|
||
Как видно, при выводе обеих переменных получается одно и то же. Теперь попробуем пройтись по содержимому этих переменных в циклах для того, чтобы увидеть разницу между ними:
|
||
|
||
```
|
||
#!/bin/bash
|
||
count=1
|
||
for param in "$*"
|
||
do
|
||
echo "\$* Parameter #$count = $param"
|
||
count=$(( $count + 1 ))
|
||
done
|
||
count=1
|
||
for param in "$@"
|
||
do
|
||
echo "\$@ Parameter #$count = $param"
|
||
count=$(( $count + 1 ))
|
||
done
|
||
```
|
||
|
||
Взгляните на то, что скрипт вывел в консоль. Разница между переменными вполне очевидна.
|
||
|
||
![[4bd369c4cecd29ae7b221dcdbf669fbb.png]]
|
||
|
||
_Разбор переменных $* и $@ в цикле_
|
||
|
||
Переменная `$*` содержит все переданные скрипту параметры как единый фрагмент данных, в то время как в переменной `$@` они представлены самостоятельными значениями. Какой именно переменной воспользоваться — зависит от того, что именно нужно в конкретном сценарии.
|
||
|
||
## Команда shift
|
||
|
||
Использовать команду `shift` в bash-скриптах следует с осторожностью, так как она, в прямом смысле слова, сдвигает значения позиционных параметров.
|
||
|
||
Когда вы используете эту команду, она, по умолчанию, сдвигает значения позиционных параметров влево. Например, значение переменной `$3` становится значением переменной `$2`, значение `$2` переходит в `$1`, а то, что было до этого в `$1,` теряется. Обратите внимание на то, что при этом значение переменной `$0`, содержащей имя скрипта, не меняется.
|
||
|
||
Воспользовавшись командой `shift`, рассмотрим ещё один способ перебора переданных скрипту параметров:
|
||
|
||
```
|
||
#!/bin/bash
|
||
count=1
|
||
while [ -n "$1" ]
|
||
do
|
||
echo "Parameter #$count = $1"
|
||
count=$(( $count + 1 ))
|
||
shift
|
||
done
|
||
```
|
||
|
||
Скрипт задействует цикл `while`, проверяя длину значения первого параметра. Когда длина станет равна нулю, происходит выход из цикла. После проверки первого параметра и вывода его на экран, вызывается команда `shift`, которая сдвигает значения параметров на одну позицию.
|
||
|
||
![[db12d91f92f7e8bc7d62a30471638ed3.png]]
|
||
|
||
_Использование команды shift для перебора параметров_
|
||
|
||
Используя команду `shift`, помните о том, что при каждом её вызове значение переменной `$1` безвозвратно теряется.
|
||
|
||
## Ключи командной строки
|
||
|
||
Ключи командной строки обычно выглядят как буквы, перед которыми ставится тире. Они служат для управления сценариями. Рассмотрим такой пример:
|
||
|
||
```
|
||
#!/bin/bash
|
||
echo
|
||
while [ -n "$1" ]
|
||
do
|
||
case "$1" in
|
||
-a) echo "Found the -a option" ;;
|
||
-b) echo "Found the -b option" ;;
|
||
-c) echo "Found the -c option" ;;
|
||
*) echo "$1 is not an option" ;;
|
||
esac
|
||
shift
|
||
done
|
||
```
|
||
|
||
Запустим скрипт:
|
||
|
||
```
|
||
$ ./myscript –a –b –c –d
|
||
```
|
||
|
||
И проанализируем то, что он выведет в терминал.
|
||
|
||
![[2efa185daf65a2171852f56b40c182fb.png]]
|
||
|
||
_Обработка ключей в скрипте_
|
||
|
||
В этом коде использована конструкция `case`, которая сверяет переданный ей ключ со списком обрабатываемых скриптом ключей. Если переданное значение нашлось в этом списке, выполняется соответствующая ветвь кода. Если при вызове скрипта будет использован любой ключ, обработка которого не предусмотрена, будет исполнена ветвь «*».
|
||
|
||
## Как различать ключи и параметры
|
||
|
||
Часто при написании bash-скриптов возникает ситуация, когда надо использовать и параметры командной строки, и ключи. Стандартный способ это сделать заключается в применении специальной последовательности символов, которая сообщает скрипту о том, когда заканчиваются ключи и начинаются обычные параметры.
|
||
|
||
Эта последовательность — двойное тире (--). Оболочка использует её для указания позиции, на которой заканчивается список ключей. После того, как скрипт обнаружит признак окончания ключей, то, что осталось, можно, не опасаясь ошибок, обрабатывать как параметры, а не как ключи. Рассмотрим пример:
|
||
|
||
```
|
||
#!/bin/bash
|
||
while [ -n "$1" ]
|
||
do
|
||
case "$1" in
|
||
-a) echo "Found the -a option" ;;
|
||
-b) echo "Found the -b option";;
|
||
-c) echo "Found the -c option" ;;
|
||
--) shift
|
||
break ;;
|
||
*) echo "$1 is not an option";;
|
||
esac
|
||
shift
|
||
done
|
||
count=1
|
||
for param in $@
|
||
do
|
||
echo "Parameter #$count: $param"
|
||
count=$(( $count + 1 ))
|
||
done
|
||
```
|
||
|
||
Этот сценарий использует команду `break` для прерывания цикла `while` при обнаружении в строке двойного тире.
|
||
|
||
Вот что получится после его вызова.
|
||
|
||
![[e06806fe1b4bb8c750896fdaa3767f4a.png]]
|
||
|
||
_Обработка ключей и параметров командной строки_
|
||
|
||
Как видно, когда скрипт, разбирая переданные ему данные, находит двойное тире, он завершает обработку ключей и считает всё, что ещё не обработано, параметрами.
|
||
|
||
## Обработка ключей со значениями
|
||
|
||
По мере усложнения ваших скриптов, вы столкнётесь с ситуациями, когда обычных ключей уже недостаточно, а значит, нужно будет использовать ключи с некими значениями. Например, вызов сценария в котором используется подобная возможность, выглядит так:
|
||
|
||
```
|
||
./myscript -a test1 -b -c test2
|
||
```
|
||
|
||
Скрипт должен уметь определять, когда вместе с ключами командной строки используются дополнительные параметры:
|
||
|
||
```
|
||
#!/bin/bash
|
||
while [ -n "$1" ]
|
||
do
|
||
case "$1" in
|
||
-a) echo "Found the -a option";;
|
||
-b) param="$2"
|
||
echo "Found the -b option, with parameter value $param"
|
||
shift ;;
|
||
-c) echo "Found the -c option";;
|
||
--) shift
|
||
break ;;
|
||
*) echo "$1 is not an option";;
|
||
esac
|
||
shift
|
||
done
|
||
count=1
|
||
for param in "$@"
|
||
do
|
||
echo "Parameter #$count: $param"
|
||
count=$(( $count + 1 ))
|
||
done
|
||
```
|
||
|
||
Вызовем этот скрипт в таком виде:
|
||
|
||
```
|
||
./myscript -a -b test1 -d
|
||
```
|
||
|
||
Посмотрим на результаты его работы.
|
||
|
||
![[8a8867258b53425258e90111f9763ddb.png]]
|
||
|
||
_Обработка параметров ключей_
|
||
|
||
В данном примере в конструкции `case` обрабатываются три ключа. Ключ `-b` требует наличия дополнительного параметра. Так как обрабатываемый ключ находится в переменной `$1`, соответствующий ему параметр будет находиться в `$2` (тут используется команда `shift`, поэтому, по мере обработки, всё, что передано сценарию, сдвигается влево). Когда с этим мы разобрались, осталось лишь извлечь значение переменной `$2` и у нас будет параметр нужного ключа. Конечно, тут понадобится ещё одна команда `shift` для того, чтобы следующий ключ попал в `$1`.
|
||
|
||
## Использование стандартных ключей
|
||
|
||
При написании bash-скриптов вы можете выбирать любые буквы для ключей командной строки и произвольно задавать реакцию скрипта на эти ключи. Однако, в мире Linux значения некоторых ключей стали чем-то вроде стандарта, которого полезно придерживаться. Вот список этих ключей:
|
||
|
||
```
|
||
-a Вывести все объекты.
|
||
-c Произвести подсчёт.
|
||
-d Указать директорию.
|
||
-e Развернуть объект.
|
||
-f Указать файл, из которого нужно прочитать данные.
|
||
-h Вывести справку по команде.
|
||
-i Игнорировать регистр символов.
|
||
-l Выполнить полноформатный вывод данных.
|
||
-n Использовать неинтерактивный (пакетный) режим.
|
||
-o Позволяет указать файл, в который нужно перенаправить вывод.
|
||
-q Выполнить скрипт в quiet-режиме.
|
||
-r Обрабатывать папки и файлы рекурсивно.
|
||
-s Выполнить скрипт в silent-режиме.
|
||
-v Выполнить многословный вывод.
|
||
-x Исключить объект.
|
||
-y Ответить «yes» на все вопросы.
|
||
```
|
||
|
||
Если вы работаете в Linux, вам, скорее всего, знакомы многие из этих ключей. Использовав их в общепринятом значении в своих скриптах, вы поможете пользователям взаимодействовать с ними, не беспокоясь о чтении документации.
|
||
|
||
## Получение данных от пользователя
|
||
|
||
Ключи и параметры командной строки — это отличный способ получить данные от того, кто пользуется скриптом, однако в некоторых случаях нужно больше интерактивности.
|
||
|
||
Иногда сценарии нуждаются в данных, которые пользователь должен ввести во время выполнения программы. Именно для этой цели в оболочке bash имеется команда `read`.
|
||
|
||
Эта команда позволяет принимать введённые данные либо со стандартного ввода (с клавиатуры), либо используя другие дескрипторы файлов. После получения данных, эта команда помещает их в переменную:
|
||
|
||
```
|
||
#!/bin/bash
|
||
echo -n "Enter your name: "
|
||
read name
|
||
echo "Hello $name, welcome to my program."
|
||
```
|
||
|
||
Обратите внимание на то, что команда `echo`, которая выводит приглашение, вызывается с ключом `-n`. Это приводит к тому, что в конце приглашения не выводится знак перевода строки, что позволяет пользователю скрипта вводить данные там же, где расположено приглашение, а не на следующей строке.
|
||
|
||
![[53d1ca2de0cdddb76571125d44c79697.png]]
|
||
|
||
_Обработка пользовательского ввода_
|
||
|
||
При вызове `read` можно указывать и несколько переменных:
|
||
|
||
```
|
||
#!/bin/bash
|
||
read -p "Enter your name: " first last
|
||
echo "Your data for $last, $first…"
|
||
```
|
||
|
||
Вот что выведет скрипт после запуска.
|
||
|
||
![[db7d564840726149b098fad0545da7e6.png]]
|
||
|
||
_Несколько переменных в команде read_
|
||
|
||
Если, вызвав `read`, не указывать переменную, данные, введённые пользователем, будут помещены в специальную переменную среды `REPLY`:
|
||
|
||
```
|
||
#!/bin/bash
|
||
read -p "Enter your name: "
|
||
echo Hello $REPLY, welcome to my program.
|
||
```
|
||
|
||
![[e7afae7525869d3a0d64ec8d99637fda.png]]
|
||
|
||
_Использование переменной среды REPLY_
|
||
|
||
Если скрипт должен продолжать выполнение независимо от того, введёт пользователь какие-то данные или нет, вызывая команду `read` можно воспользоваться ключом `-t`. А именно, параметр ключа задаёт время ожидания ввода в секундах:
|
||
|
||
```
|
||
#!/bin/bash
|
||
if read -t 5 -p "Enter your name: " name
|
||
then
|
||
echo "Hello $name, welcome to my script"
|
||
else
|
||
echo "Sorry, too slow! "
|
||
fi
|
||
```
|
||
|
||
Если данные не будут введены в течение 5 секунд, скрипт выполнит ветвь условного оператора `else`, выведя извинения.
|
||
|
||
![[65c68e9078b67feec2bbddd94c3729b9.png]]
|
||
|
||
_Ограничение времени на ввод данных_
|
||
|
||
## Ввод паролей
|
||
|
||
Иногда то, что вводит пользователь в ответ на вопрос скрипта, лучше на экране не показывать. Например, так обычно делают, запрашивая пароли. Ключ `-s` команды `read` предотвращает отображение на экране данных, вводимых с клавиатуры. На самом деле, данные выводятся, но команда `read` делает цвет текста таким же, как цвет фона.
|
||
|
||
```
|
||
#!/bin/bash
|
||
read -s -p "Enter your password: " pass
|
||
echo "Is your password really $pass? "
|
||
```
|
||
|
||
Вот как отработает этот скрипт.
|
||
|
||
![[3c3fd43bd63034b4d7dc7159ad06da75.png]]
|
||
|
||
_Ввод конфиденциальных данных_
|
||
|
||
## Чтение данных из файла
|
||
|
||
Команда `read` может, при каждом вызове, читать одну строку текста из файла. Когда в файле больше не останется непрочитанных строк, она просто остановится. Если нужно получить в скрипте всё содержимое файла, можно, с помощью конвейера, передать результаты вызова команды `cat` для файла, конструкции `while`, которая содержит команду `read` (конечно, использование команды `cat` выглядит примитивно, но наша цель — показать всё максимально просто, ориентируясь на новичков; опытные пользователи, уверены, это поймут).
|
||
|
||
Напишем скрипт, в котором используется только что описанный подход к чтению файлов.
|
||
|
||
```
|
||
#!/bin/bash
|
||
count=1
|
||
cat myfile | while read line
|
||
do
|
||
echo "Line $count: $line"
|
||
count=$(( $count + 1 ))
|
||
done
|
||
echo "Finished"
|
||
```
|
||
|
||
Посмотрим на него в деле.
|
||
|
||
![[89324607c6c7c11435ad782d88950d6f.png]]
|
||
|
||
_Чтение данных из файла_
|
||
|
||
Тут мы передали в цикл `while` содержимое файла и перебрали все строки этого файла, выводя номер и содержимое каждой из них.
|
||
|
||
## Итоги
|
||
|
||
Сегодня мы разобрали работу с ключами и параметрами командной строки. Без этих средств диапазон использования скриптов оказывается чрезвычайно узким. Даже если скрипт написан, что называется, «для себя». Тут же мы рассмотрели подходы к получению данных от пользователя во время выполнения программы — это делает сценарии интерактивными.
|
||
|
||
В следующий раз поговорим об операциях ввода и вывода.
|
||
|
||
Уважаемые читатели! Спасибо вам за то, что делитесь опытом в комментариях к предыдущим частям этого цикла материалов. Если вам есть что сказать об обработке всего того, что можно передать в скрипт при запуске или во время его работы, уверены, многим будет интересно об этом почитать.
|