Основы Bash. Введение.
BASH — Bourne-Again SHell (что может переводится как «перерожденный шел», или «Снова шел Борна(создатель sh)»), самый популярный командный интерпретатор в юниксоподобных системах, в особенности в GNU/Linux. Ниже приведу ряд встроенных команд, которые мы будем использовать для создания своих скриптов.
break
выход из цикла for, while или until
continue
выполнение следующей итерации цикла for, while или until
echo
вывод аргументов, разделенных пробелами, на стандартное устройство вывода
exit
выход из оболочки
export
отмечает аргументы как переменные для передачи в дочерние процессы в среде
hash
запоминает полные имена путей команд, указанных в качестве аргументов, чтобы не искать их при следующем обращении
kill
посылает сигнал завершения процессу
pwd
выводит текущий рабочий каталог
read
читает строку из ввода оболочки и использует ее для присвоения значений указанным переменным.\
return
заставляет функцию оболочки выйти с указанным значением
shift
перемещает позиционные параметры налево
test
вычисляет условное выражение
times
выводит имя пользователя и системное время, использованное оболочкой и ее потомками
trap
указывает команды, которые должны выполняться при получении оболочкой сигнала
unset
вызывает уничтожение переменных оболочки
wait
ждет выхода из дочернего процесса и сообщает выходное состояние.
И конечно же кроме встроенных команд мы будем использовать целую кучу внешних, отдельных команд-программ, с которыми мы познакомимся уже в процессе
Что необходимо знать с самого начала
- Любой bash-скрипт должен начинаться со строки:
#!/bin/bash
в этой строке после #! указывается путь к bash-интерпретатору, поэтому если он у вас установлен в другом месте(где, вы можете узнать набрав whereis bash
) поменяйте её на ваш путь.
- Коментарии начинаются с символа # (кроме первой строки).
- В bash переменные не имеют типа(о них речь пойдет ниже)
Переменные и параметры скрипта
Приведу как пример небольшой пример, который мы разберем:
#!/bin/bash
#указываем где у нас хранится bash-интерпретатор
#присваиваем переменной parametr1 значение первого параметра скрипта
parametr1=$1
#присваиваем переменной script_name значение имени скрипта
script_name=$0
# команда echo выводит определенную строку, обращение к переменным осуществляется через $имя_переменной.
echo "Вы запустили скрипт с именем $script_name и параметром $parametr1"
# здесь мы видим другие кавычки, разница в том, что в одинарных кавычках не происходит подстановки переменных.
echo 'Вы запустили скрипт с именем $script_name и параметром $parametr1'
#Выход с кодом 0 (удачное завершение работы скрипта)
exit 0
Результат выполнения скрипта:
ite@ite-desktop:~$ ./test.sh qwerty
Вы запустили скрипт с именем ./test.sh и параметром qwerty
Вы запустили скрипт с именем $script_name и параметром $parametr1
После того как мы познакомились как использовать переменные и передавать скрипту параметры, время познакомиться с зарезервированными переменными:
$DIRSTACK
— содержимое вершины стека каталогов
$EDITOR
— текстовый редактор по умолчанию
$EUID
— Эффективный UID. Если вы использовали программу su для выполнения команд от другого пользователя, то эта переменная содержит UID этого пользователя, в то время как…
$UID
— …содержит реальный идентификатор, который устанавливается только при логине.
$FUNCNAME
— имя текущей функции в скрипте.
$GROUPS
— массив групп к которым принадлежит текущий пользователь
$HOME
— домашний каталог пользователя
$HOSTNAME
— ваш hostname
$HOSTTYPE
— архитектура машины.
$LC_CTYPE
— внутренняя переменная, котороя определяет кодировку символов
$OLDPWD
— прежний рабочий каталог
$OSTYPE
— тип ОС
$PATH
— путь поиска программ
$PPID
— идентификатор родительского процесса
$SECONDS
— время работы скрипта(в сек.)
$#
— общее количество параметров переданных скрипту
$*
— все аргументы переданыне скрипту(выводятся в строку)
$@
— тоже самое, что и предыдущий, но параметры выводятся в столбик
$!
— PID последнего запущенного в фоне процесса
$$
— PID самого скрипта
Условия
Условные операторы, думаю, знакомы практически каждому, кто хоть раз пытался на чем-то писать программы. В bash условия пишутся след. образом (как обычно на примере):
#!/bin/bash
#в переменную source засовываем первый параметр скрипта
source=$1
#в переменную dest засовываем второй параметр скрипта
dest=$2
# в ковычках указываем имена переменных для сравнения. -eq — логическое сравнение обозначающие «равны»
if [[ "$source" -eq "$dest" ]]
# если они действительно равны, то
then
#выводим сообщение об ошибке, т.к. $source и $dest у нас равны
echo "Применик $dest и источник $source один и тот же файл!"
# выходим с ошибкой (1 — код ошибки)
exit 1
# если же они не равны
else
# то выполняем команду cp: копируем источник в приемник
cp $source $dest
echo "Удачное копирование!"
fi
#обозначаем окончание условия.
Результат выполнения скрипта:
ite@ite-desktop:~$ ./primer2.sh 1 1
Применик 1 и источник 1 один и тот же файл!
ite@ite-desktop:~$ ./primer2.sh 1 2
Удачное копирование!
Структура if-then-else
используется следующим образом:
if
<команда или набор команд возвращающих код возврата(0 или 1)>
then
<если выражение после if истино, то выполняется этот блок>
else
<если выражение после if ложно, тот этот>
В качестве команд возвращающих код возврата могут выступать структуры [[ , [ , test, (( ))
или любая другая(или несколько) linux-команда.
test
— используется для логического сравнения. после выражения, неоьбходима закрывающая скобка «]»
[
— синоним команды test
[[
— расширенная версия «[» (начиная с версии 2.02)(как в примере), внутри которой могут быть использованы || (или), & (и). Долна иметь закрывающуб скобку «]]»
(( ))
— математическое сравнение.
для построения многоярусных условий вида:
if ...
then ....
else
if ....
then....
else ....
для краткости и читаемости кода, можно использовать структуру:
if ..
then ...
elif ...
then ...
elif ...
Условия. Множественный выбор
Если необходимо сравнивать какоую-то одну переменную с большим количеством параметров, то целесообразней использовать оператор case.
#!/bin/bash
echo "Выберите редатор для запуска:"
echo "1 Запуск программы nano"
echo "2 Запуск программы vi"
echo "3 Запуск программы emacs"
echo "4 Выход"
#здесь мы читаем в переменную $doing со стандартного ввода
read doing
case $doing in
1)
/usr/bin/nano
# если $doing содержит 1, то запустить nano
;;
2)
/usr/bin/vi
# если $doing содержит 2, то запустить vi
;;
3)
/usr/bin/emacs
# если $doing содержит 3, то запустить emacs
;;
4)
exit 0
;;
*)
#если введено с клавиатуры то, что в case не описывается, выполнять следующее:
echo "Введено неправильное действие"
esac
#окончание оператора case.
Результат работы:
ite@ite-desktop:~$ ./menu2.sh
Выберите редатор для запуска:
1 Запуск программы nano
2 Запуск программы vi
3 Запуск программы emacs
4 Выход
После выбор цифры и нажатия Enter запуститься тот редактор, который вы выбрали(если конечно все пути указаны правильно, и у вас установлены эти редакторы 🙂 )
Прведу список логических операторв, которые используются для конструкции if-then-else-fi:
-z
# строка пуста
-n
# строка не пуста
=, (==)
# строки равны
!=
# строки неравны
-eq
# равно
-ne
# неравно
-lt,(< )
# меньше
-le,(<=)
# меньше или равно
-gt,(>)
#больше
-ge,(>=)
#больше или равно
!
#отрицание логического выражения
-a,(&&)
#логическое «И»
-o,(||)
# логическое «ИЛИ»
С основами языка и условиями мы разобрались. Ниже мы разберем операторы цикла и выполнение математических операций.
Циклы. Цикл for-in.
Оператор for-in
предназначен для поочередного обращения к значениям перечисленным в списке. Каждое значение поочередно в списке присваивается переменной.
Синтаксис следующий:
for переменная in список_значений
do
команды
done
Рассмотрим небольшой пример:
#!/bin/bash
for i in 0 1 2 3 4 #переменной $i будем поочередно присваивать значения от 0 до 4 включительно
do
echo "Console number is $i" >> /dev/pts/$i #Пишем в файл /dev/pts/$i(файл виртуального терминала) строку "Console number is $i"
done #цикл окончен
exit 0
После выполнения примера в первых 5 виртуальных консолях(терминалах) появится строка с её номером. В переменную $i
поочередно подставляются значения из списка и в цикле идет работа со значением этой переменной
Циклы. Цикл while.
Цикл while
сложнее цикла for-in
и используется для повторения команд, пока какое-то выражение истинно( код возврата = 0).
Синтаксис оператора следующий:
while выражение или команда возвращающая код возврата
do
команды
done
Пример работы цикла рассмотрим на следующем примере:
#!/bin/bash
again=yes #присваиваем значение "yes" переменной again
while [ "$again" = "yes" ] #Будем выполнять цикл, пока $again будет равно "yes"
do
echo "Please enter a name:"
read name
echo "The name you entered is $name"
echo "Do you wish to continue?"
read again
done
echo "Bye-Bye"
А теперь результат работы скрипта: Основы Bash
ite@ite-desktop:~$ ./bash2_primer1.sh
Please enter a name:
ite
The name you entered is ite
Do you wish to continue?
yes
Please enter a name:
mihail
The name you entered is mihail
Do you wish to continue?
no
Bye-Bye
Как видим цикл выполняется до тех пор, пока мы не введем что-то отличное от «yes». Между do
и done
можно описывать любые структуры, операторы и т.п., все они будут выполнятся в цикле.Но следует быть осторожным с этим циклом, если вы запустите на выполнение в нём какую-либо команду, без изменения переменной выражения, вы можете попасть в бесконечный цикл.
Теперь об условии истинности. После while
, как и в условном операторе if-then-else
можно вставлять любое выражение или команду, которая возвращает код возврата, и цикл будет исполнятся до тех пор, пока код возврата = 0! Оператор [
аналог команды test
, которая проверяет истинность условия, которое ей передали.
Рассмотрим еще один пример, я взял его из книги Advanced Bash Scripting. Уж очень он мне понравился :), но я его немного упростил. В этом примере мы познакомимся с еще одним типом циклов UNTIL-DO. Эта практически полный аналог цикла WHILE-DO, только выполняется пока какое-то выражение ложно.
Вот пример:
#!/bin/bash
echo "Введите числитель: "
read dividend
echo "Введите знаменатель: "
read divisor
dnd=$dividend #мы будем изменять переменные dividend и divisor,
#сохраним их знания в других переменных, т.к. они нам
#понадобятся
dvs=$divisor
remainder=1
until [ "$remainder" -eq 0 ]
do
let "remainder = dividend % divisor"
dividend=$divisor
divisor=$remainder
done
echo "НОД чисел $dnd и $dvs = $dividend"
Результат выполнения скрипта:
ite@ite-desktop:~$ ./bash2_primer3.sh
Введите числитель:
100
Введите знаменатель:
90
НОД чисел 100 и 90 = 10
Математические операции. Основы Bash.
Команда let
.
Команда let
производит арифметические операции над числами и переменными.
Рассмотрим небольшой пример, в котором мы производим некоторые вычисления над введенными числами:
#!/bin/bash
echo "Введите a: "
read a
echo "Введите b: "
read b
let "c = a + b" #сложение
echo "a+b= $c"
let "c = a / b" #деление
echo "a/b= $c"
let "c <<= 2" #сдвигает c на 2 разряда влево
echo "c после сдвига на 2 разряда: $c"
let "c = a % b" # находит остаток от деления a на b
echo "$a / $b. остаток: $c "
Результат выполнения:
ite@ite-desktop:~$ ./bash2_primer2.sh
Введите a:
123
Введите b:
12
a+b= 135
a/b= 10
c после сдвига на 2 разряда: 40
123 / 12. остаток: 3
Ну вот, как видите ничего сложного, список математических операций стандартный:
+
— сложение
—
— вычитание
*
— умножение
/
— деление
**
— возведение в степень
%
— модуль(деление по модулю), остаток от деления
let
позволяет использовать сокращения арифметических команд, тем самым сокращая кол-во используемых переменных. Например: a = a+b
эквивалентно a +=b
и т.д
Работа с внешними программами при написании shell-скриптов. Основы Bash.
Для начала немного полезной теории.
Перенаправление потоков.
В bash (как и многих других оболочках) есть встроенные файловые дескрипторы: 0 (stdin)
, 1 (stdout)
, 2 (stderr)
.
stdout
— Стандартный вывод. Сюда попадает все что выводят программы
stdin
— Стандартный ввод. Это все что набирает юзер в консоли
stderr
— Стандартный вывод ошибок.
Для операций с этими дескрипторами, существуют специальные символы: >
(перенаправление вывода), <
(перенаправление ввода). Оперировать ими не сложно. Например:
cat /dev/random > /dev/null #перенаправить вывод команды cat /dev/random в /dev/null (абсолютно бесполезная операция :)) )
или
ls -la > listing #записать в файл listing содержание текущего каталога (уже полезней)
Если есть необходимость дописывать в файл(при использовании «>
» он заменятеся), необходимо вместо «>
» использовать «>>
»
sudo < my_password
после просьбы sudo ввести пароль, он возьмется из файла my_password, как будто вы его ввели с клавиатуры.
Если необходимо записать в файл только ошибки, которые могли возникнуть при работе программы, то можно использовать:
./program_with_error 2> error_file
цифра 2 перед «>
» означает что нужно перенаправлять все что попадет в дескриптор 2(stderr).
Если необходимо заставить stderr
писать в stdout
, то это можно след. образом:
./program_with_error 2>&1
символ «&
» означает указатель на дескриптор 1(stdout)
(Поумолчанию stderr
пишет на ту консоль, в котрой работает пользователь(вренее пишет на дисплей)).
2. Основы Bash. Конвееры.
Конвеер — очень мощный инструмент для работы с консолью Bash. Синтаксис простой:
команда1 | команда 2
— означает, что вывод команды 1 передастся на ввод команде 2
Конвееры можно группировать в цепочки и выводить с помощью перенаправления в файл, например:
ls -la | grep «hash» |sort > sortilg_list
вывод команды ls -la
передается команде grep
, которая отбирает все строки, в которых встретится слово hash, и передает команде сортировке sort
, которая пишет результат в файл sorting_list. Все довольно понятно и просто.
Чаще всего скрипты на Bash используются в качестве автоматизации каких-то рутинных операций в консоли, отсюда иногда возникает необходимость в обработке stdout
одной команды и передача на stdin
другой команде, при этом результат выполнения одной команды должен быть неким образом обработан. В этом разделе я постораюсь объяснить основные принципы работы с внешними командами внутри скрипта. Думаю что примеров я привел достаточно и можно теперь писать только основные моменты.
1. Передача вывода в переменную. Основы Bash.
Для того чтобы записать в переменную вывод какой-либо команды, достаточно заключить команду в ``
ковычки, например
a = ` echo «qwerty» `
echo $a
Результат работы: qwerty
Однако если вы захотите записать в переменную список директорий, то необходимо, должным образом обработать результат для помещения данных в переменную. Рассмотрим небольшой, пример:
LIST=`find /svn/ -type d 2>/dev/null| awk '{FS="/"} {print $4}'| sort|uniq | tr '\n' ' '`
for ONE_OF_LIST in $LIST
do
svnadmin hotcopy /svn/$ONE_OF_LIST /svn/temp4backup/$ONE_OF_LIST
done
Здесь мы используем цикл for-do-done
для архивирование всех директорий в папке /svn/ с помощью команды svnadmin hotcopy
(что в нашем случае не имеет никого значения, просто как пример). Наибольшй интерес вызывает строка: LIST=find /svn/ -type d 2>/dev/null| awk '{FS="/"} {print $4}'| sort|uniq | tr '\n' ' '
В ней переменной LIST присваивается выполнение команды find, обработанной командами awk, sort, uniq,tr(все эти команды мы рассматривать не будем, ибо это отдельная статья). В переменной LIST будут имена всех каталогов в папке /svn/ пгомещенных в одну строку(для того чтобы её стравить циклу.
Возможно вам будет интересно — Подборка скриптов на Bash для автоматизации процессов.