В первой части статьи мы рассмотрели командные оболочки, профили, синонимы и первые команды. Под спойлером я также рассказал, как развернуть тестовую виртуальную машину.
В этой части речь пойдет о файлах скриптов, их параметрах и правах доступа. Также я расскажу про операторы условного выполнения, выбора и циклы.
Скрипты
Для выполнения нескольких команд одним вызовом удобно использовать скрипты. Скрипт – это текстовый файл, содержащий команды для shell. Это могут быть как внутренние команды shell, так и вызовы внешних исполняемых файлов.
Как правило, имя файла скрипта имеет окончание .sh, но это не является обязательным требованием и используется лишь для того, чтобы пользователю было удобнее ориентироваться по имени файла. Для интерпретатора более важным является содержимое файла, а также права доступа к нему.
Перейдем в домашнюю директорию командой cd ~
и создадим в ней с помощью редактора nano (nano script.sh
)файл, содержащий 2 строки:
#!/bin/bash
echo Hello!
Чтобы выйти из редактора nano после набора текста скрипта, нужно нажать Ctrl+X, далее на вопрос "Save modified buffer?" нажать Y, далее на запрос "File Name to Write:" нажать Enter. При желании можно использовать любой другой текстовый редактор.
Скрипт запускается командой ./<имя_файла>
, т.е. ./
перед именем файла указывает на то, что нужно выполнить скрипт или исполняемый файл, находящийся в текущей директории. Если выполнить команду script.sh
, то будет выдана ошибка, т.к. оболочка будет искать файл в директориях, указанных в переменной среды PATH, а также среди встроенных команд (таких, как, например, pwd):
test@osboxes:~$ script.sh
script.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.sh
Hello!
Первая строка в скрипте содержит текст #!/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/bash
echo Hello, $USER!
printf "Specified City is: %s, Country is: %s\n" $1 $2
Выдадим права на выполнение и выполним скрипт с параметрами:
test@osboxes:~$ chmod u+x script1.sh
test@osboxes:~$ ./script1.sh Moscow Russia
Hello, 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=RUSSIA
echo $COUNTRY
Операторы условного выполнения, выбора и циклы
Так же, как и в языках программирования, в bash существуют операторы условного выполнения – выполнение определенных действий при определенных условиях. Кроме того, существует возможность повторного выполнения определенного блока команд пока выполняется заданное условие – операторы цикла. Рассмотрим каждый из них подробнее.
Оператор условного выполнения представляет собой конструкцию вида:
if [ <условие> ]
then
<команда1>
else
<команда2>
fi
Создадим скрипт, проверяющий длину введенной строки (например, для проверки длины пароля), которая должна быть не меньше (т.е. больше) 8 символов:
#!/bin/bash
echo Hello, $USER!
echo -n "Enter string: "
read str
if [ ${#str} -lt 8 ]
then
echo String is too short
else
echo String is ok
fi
Выполним 2 теста, с длиной строки 5 и 8 символов:
test@osboxes:~$ ./script2.sh
Hello, test!
Enter string: abcde
String is too short
test@osboxes:~$ ./script2.sh
Hello, test!
Enter string: abcdefgh
String 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/bash
echo Hello, $USER!
echo -n "Enter string: "
read str
if [ ${#str} -lt 8 ]
then
echo String is too short
else
if [ ${#str} -gt 16 ]
then
echo String is too long
else
echo String is ok
fi
fi
Здесь мы сначала проверяем, что строка меньше 8 символов, отсекая минимальные значения, и выводим "String is too short", если условие выполняется. Если условие не выполняется(строка не меньше 8 символов) - идем дальше(первый else) и проверяем, что строка больше 16 символов. Если условие выполняется - выводим "String is too long", если не выполняется(второй else) - выводим "String is ok".
Результат выполнения тестов:
test@osboxes:~$ ./script2.sh
Hello, test!
Enter string: abcdef
String is too short
test@osboxes:~$ ./script2.sh
Hello, test!
Enter string: abcdefghijklmnopqrstuv
String is too long
test@osboxes:~$ ./script2.sh
Hello, test!
Enter string: abcdefghijkl
String is ok
Оператор выбора выглядит следующим образом:
case "$переменная" in
"$значение1" )
<команда1>;;
"$значение2" )
<команда2>;;
esac
Создадим новый скрипт, который будет выводить количество спутников для указанной планеты:
#!/bin/bash
echo -n "Enter the name of planet: "
read PLANET
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)."
Тест:
test@osboxes:~$ ./script3.sh
Enter the name of planet: Mercury
The Mercury has no satellite(s).
test@osboxes:~$ ./script3.sh
Enter the name of planet: Venus
The Venus has no satellite(s).
test@osboxes:~$ ./script3.sh
Enter the name of planet: Earth
The Earth has one satellite(s).
test@osboxes:~$ ./script3.sh
Enter the name of planet: Mars
The Mars has two satellite(s).
test@osboxes:~$ ./script3.sh
Enter the name of planet: Jupiter
The Jupiter has 79 satellite(s).
test@osboxes:~$ ./script3.sh
Enter the name of planet: Alpha555
The Alpha555 has an unknown number of satellite(s).
Здесь в зависимости от введенного названия планеты скрипт выводит количество её спутников.
В case мы использовали выражение Mercury | Venus
, где |
означает логическое "ИЛИ" (в отличие от if, где используется ||
), чтобы выводить "no" для Меркурия и Венеры, не имеющих спутников. В case также можно указывать диапазоны с помощью []
. Например, скрипт для проверки принадлежности диапазону введенного символа будет выглядеть так:
#!/bin/bash
echo -n "Enter key: "
read -n 1 key
echo
case "$key" in
[a-z] ) echo "Lowercase";;
[A-Z] ) echo "Uppercase";;
[0-9] ) echo "Digit";;
* ) echo "Something else";;
esac
Мы проверяем символ на принадлежность одному из четырех диапазонов(английские символы в нижнем регистре, английские символы в верхнем регистре, цифры, все остальные символы). Результат теста:
test@osboxes:~$ ./a.sh
Enter key: t
Lowercase
test@osboxes:~$ ./a.sh
Enter key: P
Uppercase
test@osboxes:~$ ./a.sh
Enter key: 5
Digit
test@osboxes:~$ ./a.sh
Enter key: @
Something else
Цикл может задаваться тремя разными способами:
Выполняется в интервале указанных значений (либо указанного множества):
for [ <условие> ] do <команды> done
Выполняется, пока соблюдается условие:
while [ <условие> ] do <команды> done
Выполняется, пока не начнёт соблюдаться условие:
until [ <условие> ] do <команды> done
Добавим в скрипт с планетами цикл с условием while и будем выходить из скрипта, если вместо имени планеты будет введено EXIT
#!/bin/bash
PLANET="-"
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)."
fi
done
Здесь мы также добавили условие, при котором оператор выбора будет выполняться только в случае, если введено не EXIT. Таким образом, мы будем запрашивать имя планеты и выводить количество её спутников до тех пор, пока не будет введено EXIT:
test@osboxes:~$ ./script4.sh
Enter the name of planet: Earth
The Earth has one satellite(s).
Enter the name of planet: Jupiter
The Jupiter has 79 satellite(s).
Enter the name of planet: Planet123
The Planet123 has an unknown number of satellite(s).
Enter the name of planet: EXIT
Нужно отметить, что условие while [ $PLANET != "EXIT" ]
можно заменить на until [ $PLANET == "EXIT" ]
. ==
означает "равно", !=
означает "не равно".
Приведем пример циклов с указанием интервалов и множеств:
#!/bin/bash
rm *.dat
echo -n "File count: "
read count
for (( i=1; i<=$count; i++ ))
do
head -c ${i}M </dev/urandom >myfile${i}mb.dat
done
ls -l *.dat
echo -n "Delete file greater than (mb): "
read maxsize
for f in *.dat
do
size=$(( $(stat -c %s $f) /1024/1024))
if [ $size -gt $maxsize ]
then
rm $f
echo Deleted file $f
fi
done
ls -l *.dat
read
Сначала мы запрашиваем у пользователя количество файлов, которые необходимо сгенерировать (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.sh
File 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.dat
Delete file greater than (mb): 5
Deleted file myfile10mb.dat
Deleted file myfile6mb.dat
Deleted file myfile7mb.dat
Deleted file myfile8mb.dat
Deleted 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, а также различные полезные команды.