Скрипты в Linux
Скрипт или как его еще называют сценарий, это последовательность команд которые по очереди считывает и выполняет программа-интерпретатор, в нашем случае это программа командной строки bash.
Скрипт — это текстовый файл в котором перечислены обычные команды, которые мы привыкли вводить вручную, а также указанна программа которая будет их выполнять. Загрузчик, который будет выполнять скрипт не умеет работать с переменными окружения, поэтому ему нужно передать точный путь к программе которую нужно запустить. А дальше он уже передаст ваш скрипт этой программе и начнется выполнение.
Простейший пример скрипта для командной оболочки Bash:
#!/bin/bash
echo "Hello world"
Утилита echo выводит строку, переданную ей в параметре на экран. Первая строка особая, она задает программу которая будет выполнять команды. Мы можем создать скрипт на любом другом языке программирования и указать нужный интерпретатор, например на python:
#!/usr/bin/env python
print("Hello world")
Или на PHP:
#!/usr/bin/env php
echo "Hello world";
В первом случае мы прямо указали на программу, которая будет выполнять команды, в двух следующих мы не знаем точный адрес программы, поэтому просим утилиту env найти ее по имени и запустить. Такой подход используется во многих скриптах. В системе Linux, чтобы система могла выполнить скрипт, нужно установить на файл с ним флаг исполняемый.
Флаг ничего не меняет в самом файле, только говорит системе что это не просто текстовый файл, а программа и ее нужно выполнять:
- Открыть файл
- Узнать интерпретатор
- Выполнить
Сначала давайте создадим файл сценария bash в редакторе nano:
nano test.sh
Затем напишем текст и сохранили файл, как показано ниже:
#!/bin/bash
echo "Тест пройден"
Сделаем файл исполняемым:
chmod ugo+x test.sh
Проверим является ли файл исполняемым или нет:
ls -l test.sh
Выполним файл:
./test.sh
Все работает. Вы уже знаете как написать маленький скрипт, скажем для обновления. Как видите, скрипты содержат те же команды, что и выполняются в терминале, их писать очень просто. Но теперь мы немного усложним задачу. Поскольку скрипт, это программа, ему нужно самому принимать некоторые решения, хранить результаты выполнения команд и выполнять циклы. Все это позволяет делать оболочка Bash.
Ряд встроенных команд, которые используются для создания своих скриптов:
breakвыход из цикла for, while или untilcontinueвыполнение следующей итерации цикла for, while или untilechoвывод аргументов, разделенных пробелами, на стандартное устройство выводаexitвыход из оболочкиexportотмечает аргументы как переменные для передачи в дочерние процессы в средеhashзапоминает полные имена путей команд, указанных в качестве аргументов, чтобы не искать их при следующем обращенииkillпосылает сигнал завершения процессуpwdвыводит текущий рабочий каталогreadчитает строку из ввода оболочки и использует ее для присвоения значений указанным переменнымreturnзаставляет функцию оболочки выйти с указанным значениемshiftперемещает позиционные параметры налевоtestвычисляет условное выражениеtimesвыводит имя пользователя и системное время, использованное оболочкой и ее потомкамиtrapуказывает команды, которые должны выполняться при получении оболочкой сигналаunsetвызывает уничтожение переменных оболочкиwaitждет выхода из дочернего процесса и сообщает выходное состояние
Ряд зарезервированными переменными, которые используются для создания своих скриптов:
$DIRSTACKсодержимое вершины стека каталогов$EDITORтекстовый редактор по умолчанию$EUIDесли вы использовали программу su для выполнения команд от другого пользователя, то эта переменная содержитUIDэтого пользователя$UIDсодержит реальный идентификатор, который устанавливается только при логине$FUNCNAMEимя текущей функции в скрипте$GROUPSмассив групп к которым принадлежит текущий пользователь$HOMEдомашний каталог пользователя$HOSTNAMEвашhostname$HOSTTYPEархитектура машины$LC_CTYPEвнутренняя переменная, котороя определяет кодировку символов$OLDPWDпрежний рабочий каталог$OSTYPEтип ОС$PATHпуть поиска программ$PPIDидентификатор родительского процесса$SECONDSвремя работы скрипта в сек.$#общее количество параметров переданных скрипту$*все аргументы переданыне скрипту (выводятся в строку)$@тоже самое, что и предыдущий, но параметры выводятся в столбик$!PIDпоследнего запущенного в фоне процесса$$PIDсамого скрипта
Переменные в скриптах
Написание скриптов на Bash редко обходится без сохранения временных данных таких как строки и числа, а значит создания переменных. Без переменных не обходится ни один язык программирования и наш примитивный язык командной оболочки тоже. Переменные могут быть определены явно, путем присвоения значения, или неявно, путем автоматического присвоения значения при выполнении определенных операций
Объявим переменную string, для создания переменной в Bash необходимо присвоить ей значение, используя знак равенства =:
string="Hello world"
Пробел это специальный символ разделитель, поэтому если не использовать кавычки слово world уже будет считаться отдельной командой, по той же причине мы не ставим пробелов перед и после знака равно.
Чтобы вывести значение переменной используется символ $:
echo $string
Модифицируем наш скрипт:
#!/bin/bash
string1="hello "
string2=world
string3=$string1$string2
echo $string3
И проверяем:
./test.sh
В переменные можно записать данные и результат выполнения утилит. Для этого используется такой синтаксис:
$(команда)
С помощью этой конструкции вывод команды будет перенаправлен прямо туда, откуда она была вызвана, а не на экран. Утилита date возвращает текущую дату, эти команды эквивалентны:
date echo $(date)
Напишем скрипт, где будет выводиться hello world и дата:
#!/bin/bash
string1="hello world "
string2=$(date)
string=$string1$string2
echo $string
Также возможно присвоить значение переменной через ввод с клавиатуры с помощью команды read. Например, следующий скрипт запрашивает у пользователя имя и сохраняет его в переменной:
#!/bin/bash
echo "What is your name?"
read name
echo "Hello, $name!"
Переменные также могут использоваться для передачи значений между различными командами и скриптами. Например, для передачи значения переменной из одного скрипта в другой, необходимо использовать команду export:
export название_переменной
Параметры скрипта
Не всегда можно создать bash скрипт который не зависит от ввода пользователя. В большинстве случаев нужно спросить у пользователя какое действие предпринять или какой файл использовать. При вызове скрипта мы можем передавать ему параметры, параметры доступны в виде переменных с именами в виде номеров.
Переменная с именем $1 содержит значение первого параметра, переменная с именем $2 содержит значение второго и так далее. Этот bash скрипт выведет значение первого параметра:
#!/bin/bash
echo $1
Внутри скрипта, возможно использовать специальные параметры командной строки:
$0имя скрипта (то есть, имя файла, который был запущен)$#количество переданных параметров$*или$@список всех переданных параметров (в виде одной строки или массива соответственно)$?код возврата последней выполненной команды
Конструкции if else
Создание bash скрипта было бы не настолько полезным без возможности анализировать определенные факторы и выполнять в ответ на них нужные действия.
Для проверки условий есть команда синтаксис ее такой:
if [ команда_условие ] then команда_которую_выполняем else команда fi
Команда проверяет код завершения команды условия, если 0 (успех) выполняет команду или несколько команд после слова then, если код завершения 1 выполняется блок else, параметр fi означает завершение блока команд.
Поскольку чаще всего нас интересует не код возврата команды, а сравнение строк и чисел, была введена команда [[, которая позволяет выполнять различные сравнения и выдавать код возврата зависящий от результата сравнения:
[[ параметр1 оператор параметр2 ]]
Для сравнения используются уже привычные нам операторы. Если выражение верно, команда вернет 0, если нет 1.
#!/bin/bash
if [[ $1 > 2 ]]
then
echo $1" больше 2"
else
echo $1" меньше 2 или 2"
fi
С помощью оператора elif можно проверять дополнительные условия и определять команды, котоорые выполняются при истинности этих условий. Этот оператор во многом аналогичен if:
#!/bin/bash
if [ "$a" = "$b" ]
then
echo 'true'
elif [[ "$a" =~ $b ]]
then
echo 'true'
else
echo 'false'
fi
Написать if можно в одну строку:
if [ $x -ne 0 ]; then echo 1; fi
if [ $x -ne 0 -a $y -eq 1 ]; then echo 1; else echo 2; fi
Можно обойтись без if:
[[ $? -ne 0 ]] && echo 1 || echo 2
Список логических операторв
-zстрока пуста-nстрока не пуста=, (==)строки равны!=строки неравны-eqравно-neнеравно-lt, (< )меньше-le, (<=)меньше или равно-gt, (>)больше-ge, (>=)больше или равно!отрицание логического выражения-a, (&&)логическое И-o, (||)логическое ИЛИ
Конструкция Case
Конструкция case позволяет упростить написание условных операторов для сравнения переменных с несколькими возможными значениями.
Синтаксис конструкции case выглядит следующим образом:
#!/bin/bash
case variable in
pattern1)
command1
;;
pattern2)
command2
;;
pattern3)
command3
;;
*)
default command
;;
esac
variableпеременная, которую нужно проверитьpattern1, pattern2, pattern3возможные значения, которые нужно проверитьcommand1, command2, command3команды, которые нужно выполнить в зависимости от значения переменной- Символ
*в конце списка значений используется как обработчик по умолчанию, если ни одно из значений не соответствует переменной
Для примера, давайте рассмотрим скрипт, который проверяет день недели и выполняет соответствующее действие:
#!/bin/bash
day=$(date +%u)
case $day in
1)
echo "Сегодня понедельник"
;;
2)
echo "Сегодня вторник"
;;
3)
echo "Сегодня среда"
;;
4)
echo "Сегодня четверг"
;;
5)
echo "Сегодня пятница"
;;
6)
echo "Сегодня суббота"
;;
7)
echo "Сегодня воскресенье"
;;
*)
echo "Некорректное значение дня недели"
;;
esac
В этом примере мы используем переменную day, которую мы определяем с помощью команды date +%u. В данном случае, %u используется для получения числового значения дня недели от 1 (понедельник) до 7 (воскресенье). Затем мы сравниваем эту переменную с днями недели, используя конструкцию case. Если ее значение соответствует определенному значению дня недели, то мы выводим соответствующее сообщение. Если значение не соответствует перечисленным дням недели, мы выводим сообщение об ошибке.
Циклы
Преимущество программ в том, что мы можем в несколько строчек указать какие действия нужно выполнить несколько раз. Возможно написание скриптов на bash, которые состоят всего из нескольких строчек, а выполняются часами анализируя параметры и выполняя нужные действия.
Первым рассмотрим цикл for, используется для выполнения команд для каждого элемента из списка:
for переменная in список do команда done
Перебирает весь список и присваивает по очереди переменной значение из списка, после каждого присваивания выполняет команды расположенные между do и done.
Переберем пять цифр:
#!/bin/bash
for index in 1 2 3 4 5
do
echo $index
doneВторым рассмотрим цикл while, он выполняется пока команда условия возвращает код 0 (успех):
while [ условие ] do команда done
Здесь в квадратных скобках указывается условие, которое проверяется перед каждой итерацией цикла. Команды, указанные между do и done, будут выполнены до тех пор, пока условие остается истинным.
#!/bin/bash
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
count=$((count+1))
done
В этом примере count увеличивается на 1 после каждой итерации цикла. Когда значение count достигает 4, цикл завершается.
Функции
Функции используются для группировки команд в логически связанные блоки. Функции могут быть вызваны из скрипта с помощью имени функции.
Синтаксис функции выглядит следующим образом:
название_функции () {
команды_и_выражения
}
Название функции должно начинаться с буквы или символа подчеркивания и может содержать только буквы, цифры и символы подчеркивания. За названием функции следует список аргументов в скобках. Команды и выражения, которые должны быть выполнены при вызове функции, должны быть внутри фигурных скобок.
Пример функции, которая выводит текущее время и дату:
#!/bin/bash
print_date () {
echo "Сегодняшняя дата: $(date)"
}
#вызов функции
print_date
Функции также могут иметь аргументы, которые передаются в качестве параметров внутри скобок при их вызове. Вот пример функции, которая принимает два аргумента и выводит их сумму:
#!/bin/bash
sum_numbers () {
result=$(( $1 + $2 ))
echo "Сумма чисел $1 и $2 равна $result"
}
#вызов функции
sum_numbers 10 20
В этом примере $1 и $2 переменные, которые содержат значения первого и второго аргументов соответственно. sum_numbers 10 20 вызовет функцию sum_numbers.
Функции также могут возвращать значения при помощи ключевого слова return. Перепишем прошлый пример, используя новые знания:
#!/bin/bash
sum_numbers () {
result=$(( $1 + $2 ))
return $result
}
#вызов функции
sum_numbers 12 24
#вывод
echo "Сумма чисел равна $?"
Здесь результат сохраняется в переменную result и возвращается из функции с помощью команды return.
Переменная $? содержит код возврата функции, то есть в данном случае — результат вычисления суммы.
Есть и другой способ взаимодействия с результатом вызова функции, помимо return. Немного перепишем код прошлого скрипта:
#!/bin/bash
sum_numbers () {
result=$(( $1 + $2 ))
echo $result
}
sum=$(sum_numbers 9 11)
#вывод
echo "Сумма чисел равна $sum"
Здесь, вместо использования $? и return, мы сохраняем результат вызова функции в переменную sum и затем выводим ее значение на экран.
Математические операции
Команда 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 "
Список математических операций:
+сложение-вычитание*умножение⁄деление**возведение в степень%модуль (деление по модулю), остаток от деленияletпозволяет использовать сокращения арифметических команд, тем самым сокращая кол-во используемых переменных. Напримерa = a+bэквивалентноa +=b
Перенаправление потоков
В bash, как и многих других оболочках, есть встроенные файловые дескрипторы:
0 stdinстандартный ввод, это все что набирает юзер в консоли1 stdoutстандартный вывод, сюда попадает все что выводят программы2 stderrстандартный вывод ошибок
Для операций с этими дескрипторами, существуют специальные символы:
>перенаправление вывода<перенаправление вывода>>дописывать в файл информацию
Автоматический ввод пароля для sudo, сделаем исполняемый файл:
#!/bin/bash
0000000
После просьбы sudo ввести пароль, он возьмется из файла my_password.sh, как будто вы его ввели с клавиатуры:
sudo ls < my_password.sh
Перенаправить вывод команды cat /dev/random в /dev/null. /dev/null специальный файл в операционных системах класса UNIX, представляющий собой пустое устройство:
cat /dev/random > /dev/null
Записать в файл listing содержание текущего каталога:
ls -la > listing
Если необходимо записать в файл только ошибки, которые могли возникнуть при работе программы. Цифра 2 перед > означает что нужно перенаправлять все что попадет в дескриптор 2 (stderr):
./program_with_error 2> error_file
Если необходимо заставить stderr писать в stdout, то это можно сделать. Символ & означает указатель на дескриптор 1 (stdout):
./program_with_error 2>&1
Конвееры
Конвеер, очень мощный инструмент для работы с консолью Bash, синтаксис простой:
команда1 | команда2
Вывод команды 1 передастся на ввод команде 2, конвееры можно группировать в цепочки и выводить с помощью перенаправления в файл. Вывод команды ls -la передается команде grep, которая отбирает все строки, в которых встретится слово hash, и передает команде сортировке sort, которая пишет результат в файл sorting_list:
ls -la | grep "hash" | sort > sortilg_list
Чаще всего скрипты на Bash используются в качестве автоматизации каких-то рутинных операций в консоли, отсюда иногда возникает необходимость в обработке stdout одной команды и передача на stdin другой команде, при этом результат выполнения одной команды должен быть неким образом обработан. В этом разделе я постораюсь объяснить основные принципы работы с внешними командами внутри скрипта. Думаю что примеров я привел достаточно и можно теперь писать только основные моменты.
Передача вывода в переменную
Для того чтобы записать в переменную вывод какой-либо команды, достаточно заключить команду в `` ковычки:
a = `echo "qwerty"`
echo $a
Однако если вы захотите записать в переменную список директорий, то необходимо, должным образом обработать результат для помещения данных в переменную:
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/ пгомещенных в одну строку, для того чтобы её стравить циклу.



