Лабораторная работа "Сценарии Bash" - efanov/mephi GitHub Wiki
В предыдущей работе вы познакомились с возможностями командной строки и основными стандартными командами, которых достаточно для повседневного использования в задачах администрирования.
По мере усложнения этих задач, возникает необходимость постоянно вводить множество однотипных команд, возможно, с различными параметрами. Для выполнения сложных задач однотипные команды объединяют в специальный файл - сценарий, который потом можно выполнять как обычную команду. Bash является идеальным языком для автоматизации задач администрирования: выполнение команд, работа с файлами и потоками.
Цель данной работы - научиться разрабатывать собственные сценарии на языке Bash.
Знание языка Bash является залогом успешного решения задач администрирования системы, даже если вы не предполагаете заниматься написанием своих сценариев. Многие системные службы и утилиты используют сценарии Bash, поэтому важно понимать, как они работают и уметь исправлять их, когда это необходимо.
Данная работа является подготовительной для выполнения основного задания курса - разработка собственных интерактивных сценариев командной строки для выполнения задач администрирования средств защиты Linux.
Для редактирования сценариев можно использовать один из следующих Open Source редакторов с подсветкой синтаксиса:
- Vim
- Gedit
- Mousepad
- Geany
- MCEdit
- Visual Studio Code
При выполнении данной работы рекомендуется использовать редактор vim, которым можно пользоваться даже в случаях, когда работа в графической среде невозможна - например, при входе по ssh.
- https://learnxinyminutes.com/docs/ru-ru/bash-ru/
- http://www.opennet.ru/docs/RUS/bash_scripting_guide/
- http://www.commandlinefu.com/commands/browse
- https://gist.github.com/LeCoupa/122b12050f5fb267e75f
- http://rus-linux.net/MyLDP/BOOKS/Vim/prosto-o-vim.pdf
В предыдущей работе для того чтобы вывести текст на экран, использовалась команда echo. В данном примере мы напишем для вывода фразы "Hello world!" специальный сценарий.
- Создайте файл hello.sh.
- Откройте данный файл для редактирования в текстовом редакторе.
- Запишите первую строку файла: #!/bin/bash. Это так называемый "шебанг" - специальная инструкция, сообщающая операционной системе, что данный файл нужно воспринимать именно как Bash-сценарий и использовать для его выполнения командую оболочку Bash.
- Запишите вторую строку в файл: echo "Hello world!". Данная команда выводит на экран фразу "Hello world!".
- Выполните полученный сценарий: bash hello.sh
- Для того чтобы сценарий можно было выполнить как обычную программу, нужно сделать файл сценария исполняемым: chmod +x hello.sh
- Попробуйте запустить сценарий как обычную программу: ./hello.sh.
- Попробуйте запустить сценарий без указания пути: hello.sh. Данная команда не работает, так как она отсутствует в перечне путей в переменной$PATH.$PATHсодержит список каталогов, разделённых:, в которых командная оболочка последовательно ищет исполняемый файл.
- Создайте в домашнем каталоге каталог binи переместите туда файлhello.sh.
- Попробуйте запустить сценарий без указания пути: hello.sh. Так как сценарий теперь расположен по пути, который есть в$PATH, команда должна сработать и вывести на экран "Hello world!".
Примечание. Данный сценарий будет доступен только текущему пользователю. Для того чтобы сценарий работал у всех пользователей системы, его нужно скопировать в каталог /usr/local/bin (для этого потребуются права root).
Начиная со следующего раздела вам будет необходимо самостоятельно разработать несколько сценариев на языке Bash. Создайте рабочий каталог, в котором будут содержаться ваши сценарии:
$ mkdir ~/scripts
$ cd ~/scripts
Установите программу git:
$ su
<ввести пароль root>
# yum install -y git
# exit
Инициализируйте в данном каталоге git-репозиторий:
$ git init
Создайте файл README, в котором напишите:
- ФИО студента
- Номер группы
И сделайте первый коммит с данным файлом. Не забудьте предварительно настроить git.
$ git config --global user.name "ФИО"
$ git config --global user.email "Адрес электронной почты"
$ git add README
$ git commit -m 'Added README' README
Дальнейшие правила выполнения задания:
- 
Все сценарии должны содержаться в каталоге scripts.
- Каждое задание - отдельный файл со сценарием. Название сценария придумать самостоятельно.
- После написания и тестирования каждого сценария его нужно добавить в git аналогично файлу README и выполнить git commitс комментарием: "<порядковый номер задания> - <краткое описание сценария>".
- Исправление ошибок должно также сопровождаться коммитом с описанием изменений.
- Задания (примеры) без номера заводить в git не нужно.
Пример. Завершён сценарий № 1.1 раздела "1. Объединение команд", его название - make_shared.sh.
$ git add make_shared.sh
$ git commit -m '1.1 - создание общего каталога' make_shared.sh
В данном разделе рассмотрим простейшие примеры использования сценариев Bash - объединение нескольких однотипных команд. Это простейшие сценарии, которые не принимают аргументов, не содержат сложных условий, циклов и т.п.
Например, для создания каталога, доступного всем пользователям на чтение и запись, необходимо выполнить два действия:
- создать каталог;
- изменить права доступа для каталога.
1.1. Разработать сценарий, который создаёт каталог /tmp/shared и устанавливает на него права доступа rwxrwxrwx.
Пример решения данной задачи на языке сценариев Bash:
#!/bin/bash
DIR=/tmp/shared
mkdir -p "$DIR"
chmod 777 "$DIR"
1.2. Вывести количество файлов в домашнем каталоге, которые заканчиваются на .txt. Создайте несколько таких файлов для тестирования.
1.3. Вывести текущие переменные окружения в отсортированном по алфавиту порядке.
1.4. Разработать программу "Good morning", которая:
- Пожелает пользователю доброго утра.
- Выведет текущее время и календарь на текущий месяц.
- Выведет список дел из файла TODO домашнего каталога пользователя.
1.5. Найти и вывести пути до файлов из каталога /usr (включая подкаталоги), размер которых больше 20 Мб. Подсказка: man find.
1.6. Подсчитать количество файлов, количество скрытых файлов в домашнем каталоге текущего пользователя и вывести результат в формате:
Домашний каталог пользователя
<User>
содержит обычных файлов:
XX
скрытых файлов:
YY
1.7. Вывести на экран дату, время, список активных пользователей в системе на данный момент, время работы системы с момента последней перезагрузки.
1.8. Вывести количество процессов, запущенных от имени текущего и от имени пользователя root в формате:
Процессов пользователя:
<User>
XX
Процессов пользователя root:
YY
1.9. Найти и вывести 5 процессов, потребляющих больше всего памяти в системе. Подсказка: man ps
1.10. Вывести файлы и каталоги из домашнего каталога пользователя, упорядочив их по размеру. Подсказка: использовать команды du и sort.
1.11. Разработать сценарий, который выводит файлы из текущего каталога в следующем порядке:
- Каталоги.
- Обычные файлы.
- Символьные ссылки.
- Символьные устройства.
- Блочные устройства.
Формат вывода:
Каталоги:
drwxr-xr-x  2 root root           560 сен 13 01:34 block/
drwxr-xr-x  2 root root           120 сен 13 01:34 bsg/
drwxr-xr-x  3 root root            60 июн 19 06:41 bus/
drwxr-xr-x  2 root root          3680 сен 13 01:34 char
...
Обычные файлы:
...
Символьные ссылки:
...
Символьные устройства:
...
Блочные устройства:
...
Когда сценарий будет готов, скопируйте его в каталог ~/bin для тестирования. Далее протестируйте сценарий для каталогов /, /dev, /tmp.
Подсказка: ls -l /dev | grep ^b
Полезная шпаргалка: http://www.catonmat.net/download/bash-redirections-cheat-sheet.pdf
Краткий справочник:
- 
>- записать стандартного вывод (stdout) в файл (содержимое файла будет безвозвратно утеряно)
- 
1>- полностью аналогично предыдущему
- 
2>- аналогично, но для вывода ошибок (stderr)
- 
&>- аналогично, но для двух стандартных выводов сразу - stderr и stdout
- 
>>- дописать стандартный вывод (stdout) в файл (содержимое файла будет сохранено)
- 
2>>- аналогично, но для stderr
- 
&>>- аналогично, но для двух стандартных выводов сразу - stderr и stdout
- 
< file- перенаправить файл в стандартный ввод (stdin)
- 
> &2- всё, что программа выводит в stdout, будет перенаправлено в stderr
- 
ls | wc- перенаправляет stdout ls в stdin wc
- 
ls |& wc- перенаправляет stderr и stdout ls в stdin wc
- 
/dev/null- универсальный файл-"чёрная дыра", в неё можно отправить любой ненужный вывод
- 
:- универсальная команда, которая не выводит ничего и всегда возвращает истинный код возврата, в неё можно отправить любой ненужный вывод
Обычно перенаправление вывода размещают после команды - наиболее логичным способом:
echo error >&2
ls > /tmp/list
Но возможен вариант размещения перед командой, что удобно в случае, когда нужно сосредоточиться на аргументах команды:
>&2 echo error
> /tmp/list ls -l
< /tmp/list grep 1
Основная сложность проявляется в том, что и stderr и stdout по умолчанию выводятся на экран и никак не отличаются друг от друга. Проверить stderr это или stdout можно перенаправлением в /dev/null.
Например:
ls /non-existent
ls: невозможно получить доступ к /non-existent: Нет такого файла или каталога
# попробуем спрятать ошибку. Так не работает, так как ошибка выводится в stderr
ls /non-existent > /dev/null
# теперь пробуем перенаправить поток ошибок:
ls /non-existent 2> /dev/null
# получилось! Теперь на экран ничего не выводится
# можно заблокировать любой вывод программы
# это нужно, когда мы просто хотим узнать код возврата
ls /non-existent &> /dev/null
# данный способ годится только для Bash
# в более старых системах приходилось делать так:
ls /non-existent 2> /dev/null >&2Частая ошибка - попытка найти что-то в выводе программы с помощью grep, но вывод осуществляется "мимо" stdout, в итоге мы получим весь вывод просто на экран.
# вы можете использовать grep, чтобы найти нужный параметр в справке
ls --help | grep ссыл
                             следовать по символьным ссылкам в командной
                             следовать по всем символьным ссылкам в командной
  -L, --dereference          показывая информацию для символьной ссылки,
                             показывать информацию о файле, на который ссылка
                             ссылается
# но с ошибками не так просто! Эта команда никак не поможет с поиском:
ls /{1..100} | grep 99
# чтобы найти ошибку в гуще других, нужно чуть изменить команду
ls /{1..100} |& grep 99Удобство перенаправления потоков в Bash (по сравнению с такими языками как C) компенсируется тем, что символы >, <, |, которые нередко встречаются в повседневной работе, нужно экранировать.
Некоторые команды меняют свой вывод в зависимости от того, перенаправлен ли их вывод или нет:
ls /sbin
NetworkManager         fsck.ext4                    lvmsadc                                  setenforce
accessdb               fsck.minix                   lvmsar                                   setfiles
addgnupghome           fsck.xfs                     lvreduce                                 setsebool
addpart                fsfreeze                     lvremove                                 sfdisk
...
ls /sbin | head
NetworkManager
accessdb
addgnupghome
addpart
adduser
agetty
alternatives
anacron
applygnupgdefaults
arpdЭто происходит по причине, что ls определяет, что stdout в данный момент не связан с терминалом и меняет формат вывода по умолчанию. Некоторые команды меняют вывод для удобства пользователя - список файлов превращается в колонки и раскрашивается цветами, а когда вы перенаправляете вывод команды, она понимает, что вывод будет обработан и выводит необработанный список.
Вы можете создавать сценарии, которые также принимают на стандартный ввод (stdin) некий ввод и обрабатывает его с помощью стандартных команд. Для этого нужно просто не указывать файл для обрабатывающей команды. Например, нужно разработать сценарий (mygrep.sh), который принимает на стандартный ввод текст и выводит строки, совпадающие с заранее запрограммированным шаблоном.
#!/bin/bash
grep -i bashДанный сценарий будет фактически упрощённым аналогом команды grep, где все необходимые аргументы уже заданы. Его можно использовать следующим образом:
cat /etc/passwd | ./mygrep.sh
< /etc/passwd ./mygrep.shДля тестирования следующих заданий выполните подготовительные действия:
- создайте в домашнем каталоге файл numbers.txt, в который запишите 10 000 натуральных чисел (см. командуseq);
- создайте в домашнем каталоге файл users.txt, в который запишите имена всех пользователей системы (используйтеcutи/etc/passwd);
- создайте в домашнем каталоге файл bash.txt, в который запишите содержимое двоичного файла/bin/bashв текстовом виде (используйтеod);
- создайте в домашнем каталоге файл services.txt, который будет идентичным файлу/etc/services(скопировать файл с новым именем).
2.1. Разработать сценарий, который ведёт в файле /tmp/run.log последовательный журнал запусков:
- в конец журнала добавляет строку с датой и временем запуска сценария (используйте команду dateдля фиксации даты и времени запуска сценария);
- в стандартный вывод (stdout) - выводит фразу "Hello, World!"
- в стандартный вывод ошибок (stderr) - выводит количество предыдущих запусков программы (для этого достаточно подсчитать количество строк в журнале).
Убедиться в правильности работы программы и выводе различных сообщений в различные потоки вывода:
2.1.sh > /dev/null # должен вывести счётчик запусков, счётчик должен увеличиваться
2.1.sh 2> /dev/null # должен вывести Hello, World!2.2. Разработать сценарий, который запускает сценарий, разработанный в предыдущем задании, а также открывает журнал запусков предыдущего сценария в программе less.
2.3. Разработать сценарий, который для bash.txt (созданного ранее):
- сохранит строки, которые содержат сочетание символов 000000в файл/tmp/zeros;
- сохранит строки, которые не содержат сочетания символов 000000- в файл/tmp/nozeros;
- выведет 10 первых и 10 последних строк от каждого из файлов /tmp/zerosи/tmp/nozeros.
2.4. Разработать сценарий, который считывает построчно стандартный ввод и выводит только те строки, которые содержат слово bin целиком в стандартный вывод ошибок. Для проверки сценария используйте конвейер с командой 'ls /.
2.5. Разработать сценарий, который для всех файлов с расширением txt в домашнем каталоге пользователя:
- выведет список таких файлов;
- выведет суммарный размер в байтах и строках для файлов с расширением txt.
Подсказка. Для решении этой задачи создайте временный файл в каталоге /tmp, по окончании работы сценария удалите его.
2.6. Разработать сценарий поиска дубликатов файлов (файлы, с одинаковым содержимым), который выводит сначала количество дубликатов, затем имя файла. Файлы без дубликатов выводиться не должны.
Для подготовки выполните команды:
mkdir dups
cd dups
touch {1..3}.txt
echo 4 > 4.txt
echo 4 > 5.txt
echo 6 > 6.txtПодсказка. Наиболее эффективным методом поиска дубликатов является вычисление их хэш-суммы с последующей их обработкой. У файлов-дубликатов хэш-суммы будут одинаковыми. Используйте команды sort, uniq, grep, tr, cut. Изучите как команды sort и uniq работают с колонками.
md5sum *.txt | ???? -k? | ???? -? -? ?? | ... | ... | ...Должен получиться вывод:
2	4.txt
3	1.txtВ данном разделе необходимо разработать сценарии, которые не только автоматизируют какие-то действия, но и принимают некоторые параметры от пользователя. Таким образом, созданные сценарии можно повторно использовать для различных входных данных.
Аргументы командной строки в сценарий Bash передаются также как и в обычную программу вроде grep или wc. Внутри сценария используются специальные переменные $1, $2 и т.п. - по порядку аргументов.
$ ./myscript.sh foo bar 1 2 3
Первые два аргумента: foo bar
Сумма третьего, четвёртого и пятого аргументов: 6
$ cat myscript.sh
#!/bin/bash
echo "Первые два аргумента: $1 $2"
echo "Сумма третьего, четвёртого и пятого аргументов: $(($3+$4+$5))"Исследуйте, как ведут себя специальные переменные Bash самостоятельно:
$1, $2 - первый аргумент, второй аргумент и т.п.;
$# - количество аргументов командной строки, переданные сценарию;
$* - все аргументы, переданные сценарию, объединённые в один;
$@ - все аргументы, переданные сценарию, по отдельности.
При обработке аргументов всегда используйте экранирующие кавычки. Это обезопасит код от странных ошибок, связанные с тем, что во входных данных могут содержаться пробелы и специальные символы:
# НЕПРАВИЛЬНО!
echo $1 $2 $3
grep foo $1
echo $*
du $@ 
# Правильно:
echo "$1 $2 $3"
grep foo -- "$1"
echo "$*"
du -- "$@"
# исключение составляет переменная $#, это всегда число:
grep -m$# /etc/passwd -- "$1"
# при передаче аргумента (напр., имени файла) в другую команду, всегда отделяйте
# пользовательские данные от аргументов команды символами --. Это гарантирует,
# что ваша команда будет работать даже для файлов, которые начинаются с символа -.
grep "$1" /etc/passwd # неправильно, будет ошибка, если $1 == --help
grep /etc/passwd -- "$1" # правильноПеременные в Bash аналогичны аргументам. Однако в отличие от аргументов, переменную, прежде чем использовать, необходимо объявить:
NAME=Вася
# если в данных содержится пробел, их надо экранировать
FIO="Иванов Иван Иванович"
# в переменную удобно записывать вывод другой команды:
USERS=$(grep /bin/bash /etc/passwd | cut -d: -f1)
# при копировании одной переменной в другую экранирование не требуется:
FIO_COPY=$FIO # экранирование не нужно
# аналогично, не нужно экранирование при записи в переменную 
# результата работы какой-либо команды
FILES=$(ls) # экранирование не нужно
# однако при объединении нескольких команд без экранирования не обойтись:
FIO_AND_ID="$FIO $(id)" # требуется экранировать, так как объединяем результатНекоторые полезные переменные:
$USER # имя текущего пользователя
$HOST # имя узла
$HOME # путь до домашнего каталога текущего пользователя
$RANDOM # случайное число
$LANG # текущие настройки локализации (язык и кодировка)
$$ # PID текущего процесса (сценария)
$? # код возврата предыдущей команды3.1. Разработать сценарий, который выводит на экран количество переданных ему аргументов. Скопировать его в $HOME/bin для дальнейшего использования его в других сценариях.
3.2. Разработать сценарий, который вызывает предыдущий сценарий дважды: первый раз с объединённым полным списком аргументов, второй раз - со списком всех переданных ему аргументов по отдельности.
3.3. Разработать сценарий-тестировщик, который будет тестировать ваши сценарии на различные входные данные. Первым аргументом данный сценарий должен принимать имя файла сценария, который необходимо протестировать. Запрограммируйте следующие сценарии тестирования:
- с аргументами "1" "2" "3";
- с пятью случайными числами (см. переменную $RANDOM);
- с аргументами "foo" "bar" "foobar" "foo bar";
- с аргументами "foo" "--foo" "--help" "-l".
Протестируйте с помощью разработанного сценария-тестировщика два предыдущих сценария.
3.4. Перепишите задание 1.11. так, чтобы исследуемый каталог передавался через аргумент командной строки.
3.5. Разработать сценарий, который вызывает команду grep и принимает следующие аргументы:
- текст, который нужно найти;
- файл, в котором нужно найти этот текст;
- максимальное количество строк, которое нужно вывести на экран.
Вывод команды grep отсортировать и пронумеровать.
3.6. Разработать сценарий, который выводит в одну строку имя пользователя, его домашний каталог, а также количество символов в этих двух переменных. Например: root /root 9. Подсказка: изучите аргументы команды echo, wc, математические вычисления в Bash - $(()).