В первой части статьи мы рассмотрели командные оболочки, профили, синонимы и первые команды. Под спойлером я также рассказал, как развернуть тестовую виртуальную машину.
В этой части речь пойдет о файлах скриптов, их параметрах и правах доступа. Также я расскажу про операторы условного выполнения, выбора и циклы.
Скрипты
Для выполнения нескольких команд одним вызовом удобно использовать скрипты. Скрипт это текстовый файл, содержащий команды для shell. Это могут быть как внутренние команды shell, так и вызовы внешних исполняемых файлов.
Как правило, имя файла скрипта имеет окончание .sh, но это не является обязательным требованием и используется лишь для того, чтобы пользователю было удобнее ориентироваться по имени файла. Для интерпретатора более важным является содержимое файла, а также права доступа к нему.
Перейдем в домашнюю директорию командой cd ~
и
создадим в ней с помощью редактора nano (nano
script.sh
)файл, содержащий 2 строки:
#!/bin/bashecho Hello!
Чтобы выйти из редактора nano после набора текста скрипта, нужно нажать Ctrl+X, далее на вопрос "Save modified buffer?" нажать Y, далее на запрос "File Name to Write:" нажать Enter. При желании можно использовать любой другой текстовый редактор.
Скрипт запускается командой ./<имя_файла>
,
т.е. ./
перед именем файла указывает на то, что нужно
выполнить скрипт или исполняемый файл, находящийся в текущей
директории. Если выполнить команду script.sh
, то будет
выдана ошибка, т.к. оболочка будет искать файл в директориях,
указанных в переменной среды PATH, а также среди встроенных команд
(таких, как, например, pwd):
test@osboxes:~$ script.shscript.sh: command not found
Ошибки не будет, если выполнять скрипт с указанием абсолютного
пути, но данный подход является менее универсальным:
/home/user/script.sh
. Однако на данном этапе при
попытке выполнить созданный файл будет выдана ошибка:
test@osboxes:~$ ./script.sh-bash: ./script.sh: Permission denied
Проверим права доступа к файлу:
test@osboxes:~$ ls -l script.sh-rw-rw-r-- 1 test test 22 Nov 9 05:27 script.sh
Из вывода команды ls
видно, что отсутствуют права
на выполнение. Рассмотрим подробнее на картинке:
Права доступа задаются тремя наборами: для пользователя, которому принадлежит файл; для группы, в которую входит пользователь; и для всех остальных. Здесь r, w и x означают соответственно доступ на чтение, запись и выполнение.
В нашем примере пользователь (test) имеет доступ на чтение и
запись, группа также имеет доступ на чтение и запись, все остальные
только на чтение. Эти права выданы в соответствии с правами,
заданными по умолчанию, которые можно проверить командой
umask -S
. Изменить права по умолчанию можно, добавив
вызов команды umask с нужными параметрами в файл профиля
пользователя (файл ~/.profile), либо для всех пользователей в
общесистемный профиль (файл /etc/profile).
Для того, чтобы установить права, используется команда
chmod <параметры> <имя_файла>
. Например,
чтобы выдать права на выполнение файла всем пользователям, нужно
выполнить команду:
test@osboxes:~$ chmod a+x script.sh
Чтобы выдать права на чтение и выполнение пользователю и группе:
test@osboxes:~$ chmod ug+rx script.sh
Чтобы запретить доступ на запись (изменение содержимого) файла всем:
test@osboxes:~$ chmod a-w script.sh
Также для указания прав можно использовать маску. Например, чтобы разрешить права на чтение, запись, выполнение пользователю, чтение и выполнение группе, и чтение для остальных, нужно выполнить:
test@osboxes:~$ chmod 754 script.sh
Будут выданы права -rwxr-xr--
:
test@osboxes:~$ ls -la script.sh-rwxr-xr-- 1 test test 22 Nov 9 05:27 script.sh
Указывая 3 цифры, мы задаем соответствующие маски для каждой из трех групп. Переведя цифру в двоичную систему, можно понять, каким правам она соответствует. Иллюстрация для нашего примера:
Символ перед наборами прав доступа указывает на тип файла (
означает обычный файл, d
директория, l
ссылка, c
символьное устройство, b
блочное устройство, и т. д.). Соответствие числа, его двоичного
представления и прав доступ можно представить в виде таблицы:
Число |
Двоичный вид |
Права доступа |
0 |
000 |
Нет прав |
1 |
001 |
Только выполнение (x) |
2 |
010 |
Только запись (w) |
3 |
011 |
Запись и выполнение (wx) |
4 |
100 |
Только чтение (r) |
5 |
101 |
Чтение и выполнение (rx) |
6 |
110 |
Чтение и запись (rw) |
7 |
111 |
Чтение, запись и выполнение (rwx) |
Выдав права на выполнение, можно выполнить скрипт:
test@osboxes:~$ ./script.shHello!
Первая строка в скрипте содержит текст #!/bin/bash
.
Пара символов #!
называется Шебанг (англ. shebang) и
используется для указания интерпретатору, с помощью какой оболочки
выполнять указанный скрипт. Это гарантирует корректность исполнения
скрипта в нужной оболочке в случае, если у пользователя будет
указана другая.
Также в скриптах можно встретить строку #!/bin/sh
.
Но, как правило, /bin/sh является ссылкой на конкретный shell, и в
нашем случае /bin/sh ссылается на /bin/dash, поэтому лучше явно
указывать необходимый интерпретатор. Вторая строка содержит команду
echo Hello!
, результат работы которой мы видим в
приведенном выводе.
Параметры скриптов
Для того, чтобы обеспечить некоторую универсальность, существует
возможность при вызове передавать скрипту параметры. В этом случае
вызов скрипта будет выглядеть так: <имя_скрипта>
<параметр1> <параметр2>
, например
./script1.sh Moscow Russia
.
Для того, чтобы получить значение первого параметра, необходимо
в скрипте указать $1
, второго - $2
, и
т.д. Существует также ряд других переменных, значения которых можно
использовать в скрипте:
$0
имя скрипта
$#
количество переданных параметров
$$
PID(идентификатор) процесса, выполняющего
скрипт
$?
код завершения предыдущей команды
Создадим файл script1.sh следующего содержания:
#!/bin/bashecho Hello, $USER!printf "Specified City is: %s, Country is: %s\n" $1 $2
Выдадим права на выполнение и выполним скрипт с параметрами:
test@osboxes:~$ chmod u+x script1.shtest@osboxes:~$ ./script1.sh Moscow RussiaHello, test!Specified City is: Moscow, Country is: Russia
Мы передали 2 параметра, указывающие город и страну, и использовали их в скрипте, чтобы сформировать строку, выводимую командой printf. Также для вывода в строке Hello использовали имя пользователя из переменной USER.
Для того, чтобы передать значения параметров, состоящие из нескольких слов (содержащие пробелы), нужно заключить их в кавычки:
test@osboxes:~$ ./script1.sh "San Francisco" "United States"Hello, test!Specified City is: San Francisco, Country is: United States
При этом нужно доработать скрипт, чтобы в команду printf параметры также передавались в кавычках:
printf "Specified City is: %s, Country is: %s\n" "$1" "$2"
Из приведенных примеров видно, что при обращении к переменной для получения её значения используется символ $. Для того, чтобы сохранить значение переменной просто указывается её имя:
COUNTRY=RUSSIAecho $COUNTRY
Операторы условного выполнения, выбора и циклы
Так же, как и в языках программирования, в bash существуют операторы условного выполнения выполнение определенных действий при определенных условиях. Кроме того, существует возможность повторного выполнения определенного блока команд пока выполняется заданное условие операторы цикла. Рассмотрим каждый из них подробнее.
Оператор условного выполнения представляет собой конструкцию вида:
if [ <условие> ]then <команда1>else <команда2>fi
Создадим скрипт, проверяющий длину введенной строки (например, для проверки длины пароля), которая должна быть не меньше (т.е. больше) 8 символов:
#!/bin/bashecho Hello, $USER!echo -n "Enter string: "read strif [ ${#str} -lt 8 ]then echo String is too shortelse echo String is okfi
Выполним 2 теста, с длиной строки 5 и 8 символов:
test@osboxes:~$ ./script2.shHello, test!Enter string: abcdeString is too shorttest@osboxes:~$ ./script2.shHello, test!Enter string: abcdefghString is ok
Командой read str
мы получаем значение, введенное
пользователем и сохраняем его в переменную str. С помощью выражения
${#str}
мы получаем длину строки в переменной str и
сравниваем её с 8. Если длина строки меньше, чем 8 (-lt
8
), то выдаем сообщение String is too short, иначе String is
ok.
Условия можно комбинировать, например, чтобы указать, чтоб длина
должна быть не меньше восьми 8 и не больше 16 символов, для условия
некорректных строк нужно использовать выражение [ ${#str} -lt
8 ] || [ ${#str} -gt 16 ]
. Здесь ||
означает
логическое "ИЛИ", а для логического "И" в bash используется
&&
.
Условия также могут быть вложенными:
#!/bin/bashecho Hello, $USER!echo -n "Enter string: "read strif [ ${#str} -lt 8 ]then echo String is too shortelse if [ ${#str} -gt 16 ] then echo String is too long else echo String is ok fifi
Здесь мы сначала проверяем, что строка меньше 8 символов, отсекая минимальные значения, и выводим "String is too short", если условие выполняется. Если условие не выполняется(строка не меньше 8 символов) - идем дальше(первый else) и проверяем, что строка больше 16 символов. Если условие выполняется - выводим "String is too long", если не выполняется(второй else) - выводим "String is ok".
Результат выполнения тестов:
test@osboxes:~$ ./script2.shHello, test!Enter string: abcdefString is too shorttest@osboxes:~$ ./script2.shHello, test!Enter string: abcdefghijklmnopqrstuvString is too longtest@osboxes:~$ ./script2.shHello, test!Enter string: abcdefghijklString is ok
Оператор выбора выглядит следующим образом:
case "$переменная" in "$значение1" ) <команда1>;; "$значение2" ) <команда2>;;esac
Создадим новый скрипт, который будет выводить количество спутников для указанной планеты:
#!/bin/bashecho -n "Enter the name of planet: "read PLANETecho -n "The $PLANET has "case $PLANET in Mercury | Venus ) echo -n "no";; Earth ) echo -n "one";; Mars ) echo -n "two";; Jupiter ) echo -n "79";; *) echo -n "an unknown number of";;esacecho " satellite(s)."
Тест:
test@osboxes:~$ ./script3.shEnter the name of planet: MercuryThe Mercury has no satellite(s).test@osboxes:~$ ./script3.shEnter the name of planet: VenusThe Venus has no satellite(s).test@osboxes:~$ ./script3.shEnter the name of planet: EarthThe Earth has one satellite(s).test@osboxes:~$ ./script3.shEnter the name of planet: MarsThe Mars has two satellite(s).test@osboxes:~$ ./script3.shEnter the name of planet: JupiterThe Jupiter has 79 satellite(s).test@osboxes:~$ ./script3.shEnter the name of planet: Alpha555The Alpha555 has an unknown number of satellite(s).
Здесь в зависимости от введенного названия планеты скрипт
выводит количество её спутников.
В case мы использовали выражение Mercury | Venus
, где
|
означает логическое "ИЛИ" (в отличие от if, где
используется ||
), чтобы выводить "no" для Меркурия и
Венеры, не имеющих спутников. В case также можно указывать
диапазоны с помощью []
. Например, скрипт для проверки
принадлежности диапазону введенного символа будет выглядеть
так:
#!/bin/bashecho -n "Enter key: "read -n 1 keyechocase "$key" in [a-z] ) echo "Lowercase";; [A-Z] ) echo "Uppercase";; [0-9] ) echo "Digit";; * ) echo "Something else";;esac
Мы проверяем символ на принадлежность одному из четырех диапазонов(английские символы в нижнем регистре, английские символы в верхнем регистре, цифры, все остальные символы). Результат теста:
test@osboxes:~$ ./a.shEnter key: tLowercasetest@osboxes:~$ ./a.shEnter key: PUppercasetest@osboxes:~$ ./a.shEnter key: 5Digittest@osboxes:~$ ./a.shEnter key: @Something else
Цикл может задаваться тремя разными способами:
Выполняется в интервале указанных значений (либо указанного множества):
for [ <условие> ] do <команды> done
Выполняется, пока соблюдается условие:
while [ <условие> ] do <команды> done
Выполняется, пока не перестанет соблюдаться условие:
until [ <условие> ] do <команды> done
Добавим в скрипт с планетами цикл с условием while и будем выходить из скрипта, если вместо имени планеты будет введено EXIT
#!/bin/bashPLANET="-"while [ $PLANET != "EXIT" ]do echo -n "Enter the name of planet: " read PLANET if [ $PLANET != "EXIT" ] then. echo -n "The $PLANET has " case $PLANET in Mercury | Venus ) echo -n "no";; Earth ) echo -n "one";; Mars ) echo -n "two";; Jupiter ) echo -n "79";; *) echo -n "an unknown number of";; esac echo " satellite(s)." fidone
Здесь мы также добавили условие, при котором оператор выбора будет выполняться только в случае, если введено не EXIT. Таким образом, мы будем запрашивать имя планеты и выводить количество её спутников до тех пор, пока не будет введено EXIT:
test@osboxes:~$ ./script4.shEnter the name of planet: EarthThe Earth has one satellite(s).Enter the name of planet: JupiterThe Jupiter has 79 satellite(s).Enter the name of planet: Planet123The Planet123 has an unknown number of satellite(s).Enter the name of planet: EXIT
Нужно отметить, что условие while [ $PLANET != "EXIT"
]
можно заменить на until [ $PLANET == "EXIT"
]
. ==
означает "равно", !=
означает "не равно".
Приведем пример циклов с указанием интервалов и множеств:
#!/bin/bashrm *.datecho -n "File count: "read countfor (( i=1; i<=$count; i++ ))do head -c ${i}M </dev/urandom >myfile${i}mb.datdonels -l *.datecho -n "Delete file greater than (mb): "read maxsizefor f in *.datdo size=$(( $(stat -c %s $f) /1024/1024)) if [ $size -gt $maxsize ] then. rm $f echo Deleted file $f fidonels -l *.datread
Сначала мы запрашиваем у пользователя количество файлов, которые
необходимо сгенерировать (read count
).
В первом цикле (for (( i=1; i<=$count; i++ ))
)
мы генерируем несколько файлов, количество которых задано в
переменной count, которую введет пользователь. В команду
head
передаем количество мегабайт, считываемых из
устройства /dev/random, чтение из которого позволяет получать
случайные байты.
Символ <
указывает перенаправление входного
потока (/dev/urandom) для команды head
.
Символ >
указывает перенаправление выходного
потока (вывод команды head -c ${i}M
) в файл, имя
которого мы генерируем на основе постоянной строки с добавлением в
неё значения переменной цикла (myfile${i}mb.dat
).
Далее мы запрашиваем размер, файлы больше которого необходимо удалить.
Во втором цикле (for f in *.dat
) мы перебираем все
файлы .dat в текущей директории и сравниваем размер каждого файла
со значением, введенным пользователем. В случае, если размер файла
больше, мы удаляем этот файл.
В конце скрипта выводим список файлов .dat, чтобы отобразить
список оставшихся файлов (ls -l *.dat
). Результаты
теста:
test@osboxes:~$ ./script5.shFile count: 10-rw-rw-r-- 1 test test 10485760 Nov 9 08:48 myfile10mb.dat-rw-rw-r-- 1 test test 1048576 Nov 9 08:48 myfile1mb.dat-rw-rw-r-- 1 test test 2097152 Nov 9 08:48 myfile2mb.dat-rw-rw-r-- 1 test test 3145728 Nov 9 08:48 myfile3mb.dat-rw-rw-r-- 1 test test 4194304 Nov 9 08:48 myfile4mb.dat-rw-rw-r-- 1 test test 5242880 Nov 9 08:48 myfile5mb.dat-rw-rw-r-- 1 test test 6291456 Nov 9 08:48 myfile6mb.dat-rw-rw-r-- 1 test test 7340032 Nov 9 08:48 myfile7mb.dat-rw-rw-r-- 1 test test 8388608 Nov 9 08:48 myfile8mb.dat-rw-rw-r-- 1 test test 9437184 Nov 9 08:48 myfile9mb.datDelete file greater than (mb): 5Deleted file myfile10mb.datDeleted file myfile6mb.datDeleted file myfile7mb.datDeleted file myfile8mb.datDeleted file myfile9mb.dat-rw-rw-r-- 1 test test 1048576 Nov 9 08:48 myfile1mb.dat-rw-rw-r-- 1 test test 2097152 Nov 9 08:48 myfile2mb.dat-rw-rw-r-- 1 test test 3145728 Nov 9 08:48 myfile3mb.dat-rw-rw-r-- 1 test test 4194304 Nov 9 08:48 myfile4mb.dat-rw-rw-r-- 1 test test 5242880 Nov 9 08:48 myfile5mb.dat
Мы создали 10 файлов (myfile1mb.dat .. myfile10mb.dat) размером
от 1 до 10 мегабайт и далее удалили все файлы .dat размером больше
5 мегабайт. При этом для каждого удаляемого файла вывели сообщение
о его удалении (Deleted file myfile10mb.dat
). В конце
вывели список оставшихся файлов (myfile1mb.dat ..
myfile5mb.dat).
В следующей части мы рассмотрим функции, планировщик заданий cron, а также различные полезные команды.