Bash script руководство

Bash (Bourne-Again SHell) — это командный интерпретатор в UNIX-подобных операционных системах, который позволяет автоматизировать задачи на уровне командной строки. Bash-скрипты — это файлы, содержащие последовательность команд, которые могут быть выполнены с помощью интерпретатора Bash. 

Bash-скрипты могут быть использованы для автоматизации повторяющихся задач. Например, если вам нужно каждый день создавать отчет и отправлять его по электронной почте, вы можете написать скрипт, который будет выполнять эти действия автоматически. Это позволит сэкономить много времени и уменьшить вероятность ошибок.

В этой статье мы рассмотрим основные концепции и инструменты для написания Bash-скриптов в Linux.

Bash-скрипты могут быть написаны в любом текстовом редакторе и должны иметь права на выполнение. Рассмотрим некоторые из наиболее популярных редакторов:

  1. Nano — это простой текстовый редактор, который поставляется вместе с большинством дистрибутивов Linux. Он имеет простой и интуитивно понятный интерфейс, а также некоторые дополнительные полезные функции, такие как подсветка синтаксиса.
  2. Vim — это один из самых популярных текстовых редакторов для Linux, хотя он может показаться сложным для начинающих. Vim имеет множество функций, которые помогают ускорить процесс написания кода, такие как подсветка синтаксиса, автодополнение и макросы.
  3. Emacs — это еще один популярный текстовый редактор для Linux. Он также имеет множество функций, которые могут значительно упростить процесс написания кода. Одна из его главных особенностей — это возможность запуска интерпретатора Bash внутри редактора, что позволяет тестировать скрипты без выхода из редактора.

В начале каждого скрипта должна быть строка, называемая шебанг (shebang), которая сообщает операционной системе, какой интерпретатор использовать для выполнения скрипта. Шебанг должен начинаться с символа решетки (#) и следующим за ним символ восклицания (!), после которых указывается путь к интерпретатору. Для использования интерпретатора Bash, шебанг будет выглядеть следующим образом:

#!/bin/bash

Также во время написания скрипта можно оставлять комментарии, которые начинаются с символа решетки и продолжаются до конца строки. Комментарии не будут выполнены интерпретатором и используются для описания функциональности скрипта. Например:

# Это комментарий

Ниже напишем свой первый скрипт. Допустим, мы хотим создать скрипт в Linux, который приветствует пользователя и выводит текущую дату и время на экран. Для этого создадим файл greeting.sh в любой директории нашего компьютера и добавим следующий код:

#!/bin/bash
echo "Hello, $USER!"
echo "Today is $(date)"

Первая строка указывает на то, что это скрипт на Bash. Следующая строка echo "Hello, $USER!" выводит приветствие с именем текущего пользователя. $USER является системной переменной, которая содержит имя текущего пользователя. А третья строка echo "Today is $(date)" выводит текущую дату и время. $(date) используется для вызова команды date, которая возвращает текущую дату и время в формате, указанном в настройках системы.

Создавая скрипт на Bash, важно убедиться, что файл можно будет исполнять. Для этого необходимо изменить права доступа к файлу. Об этом, а также о запуске написанного скрипта мы поговорим в следующей главе.

Запуск скриптов

Для запуска скрипта в Linux необходимо дать ему права на выполнение. Чтобы сделать файл исполняемым, можно воспользоваться командой chmod (от англ. change mode — изменить режим). С помощью этой команды вы можете изменять права доступа к файлам и директориям в Linux.

Команда chmod имеет следующий синтаксис:

chmod [опции] права_доступа файл

где права_доступа — это специальный код, который задает права доступа к файлу или директории, а файл — это путь к файлу или директории, права доступа которой нужно изменить.

Для того чтобы сделать файл исполняемым, нужно добавить к его правам доступа бит исполнения (x). 

Например, сделаем файл greeting.sh исполняемым, для этого используем следующую команду:

chmod +x greeting.sh

Эта команда добавит права на выполнение для текущего пользователя. Теперь мы можем запустить Bash-скрипт в Linux, вызвав его из терминала:

./greeting.sh

Результат выполнения скрипта продемонстрирован на картинке ниже.

Image5

Параметры командной строки

Параметры командной строки позволяют передавать аргументы в скрипты Linux при его запуске. Параметры командной строки могут быть доступны в скрипте как $1, $2, $3 и т.д., где $1 — это первый параметр, $2 — второй параметр и т.д.

Перепишем скрипт из предыдущей главы для приветствия пользователя:

#!/bin/bash
echo "Hello, $1!”

А затем запустим скрипт, передав ему аргумент $USER:

./greeting.sh $USER

Результат запуска представлен на картинке ниже.

Image8

Кроме того, возможно использовать специальные параметры командной строки:

  • $0 — имя скрипта (то есть, имя файла, который был запущен)
  • $# — количество переданных параметров
  • $* или $@ — список всех переданных параметров (в виде одной строки или массива соответственно)
  • $? — код возврата последней выполненной команды

Например, чтобы вывести на экран количество переданных параметров, можно использовать следующий код:

#!/bin/bash
echo "Hello, " $1 “!”
echo "Количество переданных параметров: $#"

Результат запуска скрипта представлен на картинке ниже.

Image1

Переменные

Переменные в Bash используются для хранения данных, таких как строки и числа. Они могут быть определены явно, путем присвоения значения, или неявно, путем автоматического присвоения значения при выполнении определенных операций. Для создания переменной в Bash необходимо присвоить ей значение, используя знак равенства (=). Например:

company="Timeweb"

Обратите внимание, что не должно быть пробелов между именем переменной, знаком равенства и значением.

Значение переменной можно получить, указав ее имя после команды echo и знака $. Например:

echo $company

Также возможно присвоить значение переменной через ввод с клавиатуры с помощью команды read. Например, следующий скрипт запрашивает у пользователя имя и сохраняет его в переменной:

#!/bin/bash
echo "What is your name?"
read name
echo "Hello, $name!"

Результат работы данного скрипта представлен на картинке ниже.

Image11

В Bash доступны несколько специальных переменных, которые автоматически определяются и заполняются системой. Например, переменная $HOME содержит путь к домашней директории пользователя, а $PWD содержит путь к текущей рабочей директории. 

Кроме этого, доступны переменные окружения, которые определяются системой и могут использоваться в скриптах. Например, $PATH содержит список директорий, в которых Bash ищет исполняемые файлы.

Переменные также могут использоваться для передачи значений между различными командами и скриптами. Например, для передачи значения переменной из одного скрипта в другой, необходимо использовать команду export:

export название_переменной

Условные операторы

Условные операторы — это конструкции, которые позволяют выполнить определенный набор действий в зависимости от истинности или ложности какого-то условия. В Bash-скриптах условия записываются в скобках и передаются в команду if

Синтаксис оператора if выглядит следующим образом:

if [ условие ]
then
  команды, которые нужно выполнить, если условие верно
fi

Здесь в квадратных скобках указывается условие, которое необходимо проверить. Если условие истинно, то будут выполнены команды, указанные между then и fi.

Например, напишем скрипт в Linux chetnechet.sh, который будет проверять, является ли число, введенное пользователем, четным или нечетным:

#!/bin/bash
echo "Введите число: "
read n
if (( $n % 2 == 0 ))
then
  echo "Число $n четное"
else
  echo "Число $n нечетное"
fi

В этом примере мы использовали оператор %, который вычисляет остаток от деления на 2. Если остаток равен 0, то число четное, иначе — нечетное.

Результат работы скрипта представлен на картинке ниже.

Image9

Кроме того, существует несколько операторов сравнения, которые можно использовать в условных конструкциях:

  • -eq – равно;
  • -ne – не равно;
  • -gt – больше;
  • -lt – меньше;
  • -ge – больше или равно;
  • -le – меньше или равно.

Например, чтобы проверить, является ли переменная $a больше переменной $b, можно написать следующее:

if [ $a -gt $b ]
then
  echo "$a больше $b"
fi

Важно помнить, что в условных конструкциях необходимо использовать пробелы вокруг операторов сравнения. Если пробелов нет, то Bash-скрипт будет считать это одной большой строкой, а не операцией сравнения.

Кроме if, в Bash-скриптах также используется конструкция case. Эта конструкция позволяет проверять значение переменной на соответствие нескольким вариантам. Но об этом мы поговорим в следующей главе.

Конструкция Case

Конструкция case в bash-скриптах позволяет упростить написание условных операторов для сравнения переменных с несколькими возможными значениями.

Синтаксис конструкции case выглядит следующим образом:

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. Если ее значение соответствует определенному значению дня недели, то мы выводим соответствующее сообщение. Если значение не соответствует перечисленным дням недели, мы выводим сообщение об ошибке.

Результат работы скрипта продемонстрирован на картинке ниже.

Image3

Циклы

Циклы в Bash используются для выполнения повторяющихся действий. Существуют два типа циклов: цикл for и цикл while.

Цикл for используется для выполнения команд для каждого элемента из списка.

Синтаксис цикла for выглядит следующим образом:

for переменная in список
do
  команды
done

Здесь переменная принимает значение элемента из списка, после чего для каждого из них выполняются команды, указанные между do и done.

Пример:

#!/bin/bash

for i in {1..10}; do
    echo "Number: $i"
done

В этом примере i принимает значения от 1 до 10, и для каждого из них будет выполнена команда echo "Number: $i". Результат выполнения этого цикла будет выглядеть следующим образом:

Image10

Следующий цикл while используется для выполнения команд до тех пор, пока условие не станет ложным. Синтаксис цикла while выглядит следующим образом:

while [ условие ]
do
  команды
done

Здесь в квадратных скобках указывается условие, которое проверяется перед каждой итерацией цикла. Команды, указанные между do и done, будут выполнены до тех пор, пока условие остается истинным.

Пример:

#!/bin/bash

count=1
while [ $count -le 10 ]; do
    echo "Count: $count"
    count=$((count+1))
done

В этом примере count увеличивается на 1 после каждой итерации цикла. Когда значение count достигает 10, цикл завершается. Результат выполнения этого цикла будет выглядеть следующим образом:

Image4

Функции

Функции в Bash используются для группировки команд в логически связанные блоки. Функции могут быть вызваны из скрипта с помощью имени функции. 

Синтаксис функции выглядит следующим образом:

название_функции () {
    команды_и_выражения
}

Название функции должно начинаться с буквы или символа подчеркивания и может содержать только буквы, цифры и символы подчеркивания. За названием функции следует список аргументов в скобках. Команды и выражения, которые должны быть выполнены при вызове функции, должны быть внутри фигурных скобок.

Вот пример функции, которая выводит текущее время и дату:

#!/bin/bash

print_date () {
    echo "Сегодняшняя дата: $(date)"
}

print_date #Вызов функции

Результат работы скрипта представлен на картинке ниже.

Image13

Функции также могут иметь аргументы, которые передаются в качестве параметров внутри скобок при их вызове. Вот пример функции, которая принимает два аргумента и выводит их сумму:

#!/bin/bash

sum_numbers () {
    result=$(( $1 + $2 ))
    echo "Сумма чисел $1 и $2 равна $result"
}

sum_numbers 10 20 #Вызов функции

В этом примере $1 и $2 — это переменные, которые содержат значения первого и второго аргументов соответственно. sum_numbers 10 20 вызовет функцию sum_numbers с аргументами 10 и 20 и выведет следующий результат:

Image2

Функции также могут возвращать значения при помощи ключевого слова return. Перепишем прошлый пример, используя новые знания:

#!/bin/bash

sum_numbers () {
    result=$(( $1 + $2 ))
    return $result
}

sum_numbers 12 24 #Вызов функции
echo "Сумма чисел равна $?" #Вывод

Здесь результат сохраняется в переменную result и возвращается из функции с помощью команды return.

Переменная $? содержит код возврата функции, то есть в данном случае — результат вычисления суммы.

Результат работы скрипта представлен на картинке ниже.

Image12

Есть и другой способ взаимодействия с результатом вызова функции, помимо return. Немного перепишем код прошлого скрипта:

#!/bin/bash

sum_numbers () {
    result=$(( $1 + $2 ))
    echo $result
}
sum=$(sum_numbers 9 11)
echo "Сумма чисел равна $sum" #Вывод

Здесь, вместо использования $? и return, мы сохраняем результат вызова функции в переменную sum и затем выводим ее значение на экран. Результат продемонстрирован на картинке ниже.

Image7

Работа с файлами и директориями

Bash-скрипты могут использоваться для выполнения различных операций с файлами и директориями в Linux. Например, чтобы проверить существование файла, можно использовать команду: 

test -e filename 

Если файл существует, то команда вернет значение 0, в противном случае — ненулевое значение. Для работы с директориями в Bash-скриптах можно использовать команды cd, mkdir, rmdir, ls и т.д.

Отладка скриптов

Отладка Bash-скриптов может оказаться трудной задачей, поскольку проблемы могут быть вызваны различными факторами, такими как ошибки синтаксиса, неправильное использование переменных или функций и т.д. Для отладки Bash-скриптов можно использовать такие инструменты, как set -x, set -v и set -e

Команда set -x позволяет выводить на экран команды перед их выполнением, команда set -v выводит на экран значения переменных перед их использованием, а set -e прерывает выполнение скрипта в случае ошибки.

Заключение

Bash-скрипты — это инструмент для автоматизации задач в UNIX-подобных операционных системах. В этой статье мы рассмотрели основные концепции и инструменты для написания Bash-скриптов, такие как синтаксис, переменные, условные операторы, циклы, функции и запуск скриптов. Надеемся, что эта инструкция поможет вам стать более продуктивным и опытным пользователем Linux.

Бесплатная книга-сайт на русском, полный гайд
Advanced Bash-Scripting Guide

Введение

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 ждет выхода из дочернего процесса и сообщает выходное состояние.

И конечно же кроме встроенных команд мы будем использовать целую кучу внешних, отдельных команд-программ, с которыми мы познакомимся уже в процессе

Что необходимо знать с самого начала

  1. Любой bash-скрипт должен начинаться со строки:

#!/bin/bash

в этой строке после #! указывается путь к bash-интерпретатору, поэтому если он у вас установлен в другом месте(где, вы можете узнать набрав whereis bash) поменяйте её на ваш путь.

  1. Коментарии начинаются с символа # (кроме первой строки).
  2. В 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,(||) # логическое «ИЛИ»

С основами языка и условиями мы разобрались, чтобы не перегружать статью, разобью её на несколько частей(допустим на 3). Во второй части разберем операторы цикла и выполнение математических операций.

Основы BASH. Часть 2

Циклы. Цикл 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"  

А теперь результат работы скрипта:

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  

Математические операции

Команда 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 (как и многих других оболочках) есть встроенные файловые дескрипторы: 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. Синтаксис простой:
команда1 | команда 2 — означает, что вывод команды 1 передастся на ввод команде 2
Конвееры можно группировать в цепочки и выводить с помощью перенаправления в файл, например:

ls -la | grep «hash» |sort > sortilg_list

вывод команды ls -la передается команде grep, которая отбирает все строки, в которых встретится слово hash, и передает команде сортировке sort, которая пишет результат в файл sorting_list. Все довольно понятно и просто.

Чаще всего скрипты на Bash используются в качестве автоматизации каких-то рутинных операций в консоли, отсюда иногда возникает необходимость в обработке stdout одной команды и передача на stdin другой команде, при этом результат выполнения одной команды должен быть неким образом обработан. В этом разделе я постораюсь объяснить основные принципы работы с внешними командами внутри скрипта. Думаю что примеров я привел достаточно и можно теперь писать только основные моменты.

1. Передача вывода в переменную.

Для того чтобы записать в переменную вывод какой-либо команды, достаточно заключить команду в `` ковычки, например

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 (и его аналогов sh, zsh, etc) совместим с любым POSIX-совместимым приложением в Linux, что позволяет встроить в ваш bash-скрипт любое совместимое приложение. Это дает очень большой набор возможностей в сфере автоматизации рутинных задач администрирования систем Linux, деплоя и сборки приложений, различных пакетных обработок, в том числе аудио и видео.

Командная строка — самый мощный пользовательский интерфейс из существующих на данный момент. Базовый уровень знаний получить достаточно просто. Рекомендуется изучить руководство bash. Это можно сделать, выполнив команду man bash.

Суть bash-скриптов — записать все ваши действия в один файл и выполнять их по необходимости.

В этой статье расскажем про написание bash-скриптов с нуля и получим представление, какую пользу можно из них извлечь. Рекомендуем держать под рукой bash-справочник, если вы планируете заняться этим всерьез.

Развертывание среды

Для выполнения скриптов, которые мы будем учиться писать, нужна среда. Если вы используете на своем компьютере систему Linux, вы можете делать все локально. Если Windows, — можете установить WSL/WSL2. Кроме того, вы можете создать виртуальный сервер и подключиться к нему по SSH. Так вы не навредите своему компьютеру если что-то пойдет не так.

Мы выбрали вариант создать виртуальную машину.  Залогинимся в личном кабинете https://my.selectel.ru/, нажав на вкладку «Облачная платформа». Там вы сможете создать виртуальный сервер.

Необходимо выбрать зону размещения сервера исходя из его близости к пользователям. Чем дальше сервер, тем выше пинг. 

Нажмем «Создать сервер». 

В разделе «Источник» убеждаемся, что выбран образ Ubuntu 20.04.

Конфигурацию можно настроить по своим потребностям.

В разделе «Сеть» стоит выбрать «Подсеть — Плавающий IP-адрес».

В разделе «Доступ» загрузите SSH-ключ и не забудьте сохранить root-пароль. Подробнее об этом рассказано в этой статье

Теперь можно создать сервер кнопкой «Создать» в самом низу.

Будет отображена страница статуса сервера, надо дождаться индикации ACTIVE вверху справа.

Теперь на вкладке «Порты» можно посмотреть IP-адрес, присвоенный серверу.

Не копируйте чужой код

Копирование чужого кода на свой компьютер/сервер опасно. Ранее существовал «патч Бармина», представляющий из себя команду rm -rf /*. Ее очень любили давать новичкам Linux на некоторых конференциях в качестве универсального средства от всех проблем. Суть команды — рекурсивное удаление всех каталогов внутри корневого каталога, т. е. всех системных и пользовательских файлов. Сейчас эта команда не сработает во всех актуальных версиях Linux, но раньше она служила злой шуткой и наказанием тем, кто копировал чужие скрипты на свои серверы и выполнял их. Способов навредить серверу/компьютеру все еще достаточно, но они не столь очевидны.

Выбор редактора

Вам потребуется удобный текстовый редактор. Если вы подключаетесь по SSH, то лучшим выбором будут 3 варианта:

  • * vim (если умеете из него выходить)
  • * nano (прост, удобен и надежен)
  • * mcedit (входит в пакет mc, классический двухпанельный консольный файловый менеджер)

Если вы делаете все локально, выбор полностью на вас. Обычный выбор под Linux — gedit. В этой инструкции мы пользовались nano через SSH на удаленном сервере.

Запуск “Hello, World!”

Первая программа, которую обычно пишут программисты это «Hello, World!» —  простой вывод этой строки. Мы тоже с этого начнем. За вывод строки в консоль отвечает команда echo. Прямо в консоли вы можете напечатать echo «Hello, World!» и получить соответствующий вывод:

    root@geneviev:~ # echo "Hello, World!"
Hello, World!

Сделаем это программой. Команда touch helloworld.sh создаст файл helloworld.sh. Команда nano helloworld.sh откроет этот файл для редактирования. Заполним файл нашей программой:

    #!/bin/bash
echo "Hello, World!"

Для выхода с сохранением из nano надо нажать CTRL + O для сохранения (после чего нажать enter для перезаписи текущего открытого файла), а потом CTRL + X для выхода. Можно выходить без сохранения, при этом он спросит, точно ли вы хотите выйти без сохранения. Если да, надо нажать N для выхода без сохранения. Если вы нажмете Y, он спросит куда сохранить измененный файл, можно нажать enter для перезаписи редактируемого файла.

Разберем, что мы написали.

Первой строкой идет #!/bin/bash — фактически это указание на местоположение интерпретатора. Чтобы при запуске скрипта не требовалось указывать отдельно интерпретатор. Убедиться, что ваш bash интерпретатор лежит по этому пути можно через команду which bash:

    root@geneviev:~ # which bash
/usr/bin/bash

Второй строкой идет непосредственно вся наша программа. Как она работает, мы разобрали выше, перейдем к выполнению.

Запустить ваш скрипт/команду можно двумя способами.

Способ №1: bash helloworld.sh. Вы вызываете интерпретатор и в аргументе передаете ему имя файла для исполнения.

    root@geneviev:~ # bash helloworld.sh 
Hello, World!

Способ №2: Сначала надо разрешить системе исполнять скрипт: chmod +x helloworld.sh. Эта команда сделает файл исполняемым. Теперь вы можете запустить его как любой бинарный файл в linux: ./helloworld.sh

    root@geneviev:~ # ./helloworld.sh 
Hello, World!

Первая программа готова, она просто выводит строку в консоль.

Аргументы

Это параметры, которые вы можете передать программе при ее вызове. Например, программа ping принимает в качестве обязательного аргумента IP-адрес или DNS-имя, которое требуется пропинговать: ping selectel.ru. Это простой и удобный способ общения пользователя с программой.

Давайте научим нашу программу принимать аргументы и работать с ними. Доступ к аргументам осуществляется через служебную команду $X где X это число. $0 — всегда имя исполняемого скрипта. $1 — первый аргумент, $2 — второй и так далее. Конечно, если вы планируете передавать пару десятков аргументов вашему приложению, это может быть несколько утомительно, так что вам понадобится что-то вроде этого цикла, чтобы перебрать все поступившие аргументы:

    for var in "$@"; do
    echo "$var"
done

Подробнее про циклы будет рассказано в следующих разделах.

Пример, создадим новый файл: touch hellousername.sh. Выдаем права на исполнение chmod +x hellousername.sh.

Открываем nano hellousername.sh

Код примера следующий:

    #!/bin/bash 

echo "Hello, $1!"

Сохраняем, закрываем. Смотрим, что получилось.

    root@geneviev:~ # ./hellousername.sh Vasya
Hello, Vasya!

Программа получилась маленькая, но она учит пользоваться (на самом базовом уровне) аргументами, которые мы в нее можем передать. В данном случае аргумент передается один, Vasya, мы сразу его используем, не делая никаких проверок. 

    root@geneviev:~ # ./hellousername.sh 
Hello, !

При таком сценарии в нашей программе баг: она ни с кем не поздоровается. Чтобы исправить ситуацию, есть 2 способа: проверить число аргументов или проверить содержимое аргумента.

Способ №1

    #!/bin/bash
if [ "$#" -lt 1 ]; then
    echo "Недостаточно аргументов. Пожалуйста, передайте в качестве аргумента имя. Пример: $0 Vasya"
    exit 1
fi
echo "Hello, $1!"

Более подробно конструкцию if then [else] fi мы рассмотрим далее, пока не будем на ней останавливаться. Важно понимать, что тут проверяется. $# Это число аргументов без учета имени скрипта, который всегда $0. 

Способ №2

    #!/bin/bash
if [ -z "$1" ]; then
   echo "Имя пустое или не передано. Пожалуйста, передайте в качестве аргумента имя. Пример: $0 Vasya"
   exit 1
fi

echo "Hello, $1!"

Здесь тоже используется конструкция if then [else] fi. Ключ -z в if используется для проверки переменной на пустую строку. Есть противоположный ключ -n, он проверяет что строка не пустая. Конечно, этот способ некорректно использовать для проверки входящих аргументов, но в теле самой программы он будет полезен. Например, чтобы проверить что выполненное в самой программе приложение что-то вернуло.

Управляющие конструкции

if-else

Написание программ на любом из языков длиннее нескольких строчек сложно представить без ветвления. В разных языках бывают разные варианты ветвления, но в большинстве случаев используется синтаксис if else. В bash это также присутствует.

Возьмем один из примеров выше.

    #!/bin/bash
if [ "$#" -lt 1 ]; then
    echo "Недостаточно аргументов. Пожалуйста, передайте в качестве аргумента имя. Пример: $0 Vasya"
    exit 1
fi
echo "Hello, $1!"

Происходит проверка системной переменной $# на то, что она меньше, чем (lower than, -lt) 1. Если это выражение истинно, мы переходим в блок команд, открывающийся ключевым словом then. Весь блок, начинающийся с if, должен завершаться ключевым словом fi. Более сложная структура ветвления может выглядеть примерно так:

    if TEST-COMMAND1
then
  STATEMENTS1
elif TEST-COMMAND2
then
  STATEMENTS2
else
  STATEMENTS3
fi

Реальный пример:

    #!/bin/bash
if [ "$#" -lt 1 ];
then
    echo "Недостаточно аргументов. Пожалуйста, передайте в качестве аргумента имя. Пример: $0 Vasya"
    exit 1
fi
if [ "$1" = "Vasya" ]; then
        echo "Whatsupp, Vasiliy?"
elif [ "$1" = "Masha" ];
then
        echo "Hey, Masha"
elif [ "$1" = "Michael" ];
then
        echo "Shalom, Michael"
else
        echo "Hi, $1"
fi

Вывод программы:

    root@geneviev:~ # ./hellousername.sh Vasya
Whatsupp, Vasiliy?
root@geneviev:~ # ./hellousername.sh Masha
Hey, Masha
root@geneviev:~ # ./hellousername.sh Michael
Shalom, Michael
root@geneviev:~ # ./hellousername.sh Andrew
Hi, Andrew
root@geneviev:~ # ./hellousername.sh 
Недостаточно аргументов. Пожалуйста, передайте в качестве аргумента имя. Пример: ./hellousername.sh Vasya

Выражение «$1» = «Vasya» проверяет строки на идентичность. Блок после else выполняется только если выше не найден более подходящий блок.

&& и ||

В предыдущей главе вы могли заметить, что я использовал exit 1 для завершения работы программы в случае неуспешной проверки аргумента. Это означает, что программа завершилась с ошибкой. В bash есть операторы && и ||, которые используются для создания цепочек команд. Каждая цепочка зависит от результата выполнения предыдущей программы.

Пример 1: command1 && command2. В этом случае command2 выполнится, только если command1 завершится с кодом 0 (exit 0, по умолчанию). 

Пример 2: command1 || command2. В этом случае command2 выполнится, только если command1 завершится с кодом отличным от 0. 

Пример 3: command1 && command2 || command3. Если command1 завершится с кодом 0, то будет выполнен command2, иначе command3.

Переменные

Как гласит один из основных принципов программирования — Do Not Repeat Yourself (DRY). Вот и мы не будем повторять себя и перепишем предыдущий пример с использованием переменных, чтобы не вызывать echo каждый раз.

Переменные в bash создаются присваиванием: x=»foo bar» или z=$1. Переменной x мы присвоили строку @foo bar«, а переменной z мы присвоили значение первого аргумента. Использовать именованные переменные гораздо удобнее, чем использовать $1, особенно когда надо использовать его значение во многих местах.

К тому же, аргументы могут идти в разном порядке. Осмысленные названия переменных очень важны, при разрастании программы это снизит неизбежную путаницу. Избегайте имен переменных (и функций) вроде «a», «b», «zzzz», etc.

Чтобы не вызывать echo в каждом варианте с разными строками, разобьем строку на части. Первая часть будет приветствием. Вторая — именем. Третья — завершающим знаком препинания. Его можно не выносить в переменную.

    #!/bin/bash

greetString="Hello"
nameString="stranger"

if [ "$#" -lt 1 ];
then
    echo "Недостаточно аргументов. Пожалуйста, передайте в качестве аргумента имя. Пример: $0 Vasya"
    exit 1
fi

if [ "$1" = "Vasya" ]; 
then
    nameString="Vasiliy"
	greetString="Whatsup"
elif [ "$1" = "Masha" ];
then
	nameString="Maria"
elif [ "$1" = "Michael" ];
then
	greetString="Shalom"
	nameString="Michael"
fi

echo "$greetString, $nameString!"

В этом примере мы создаем переменные greetString и nameString, которым присваиваем значения по умолчанию. В конце программа выводит значения этих двух переменных с помощью echo и форматированной строки (в двойных кавычках переменные раскрываются). Между этими действиями программа определяет, надо ли присваивать переменным другие значения.

Switch case

Использование if-else конструкции в нашем примере не является оптимальным вариантом. Мы всего лишь сверяем значение переменной с определенным набором значений. В такой ситуации лучшим выбором будет switch-case-конструкция.

    case "$variable" in

 "$condition1" )
 command...
 ;;

 "$condition2" )
 command...
 ;;

esac

Перепишем нашу программу приветствий с использованием switch-case:

    #!/bin/bash

name=$1

case "$name" in
  "Vasya" )
    nameString="Vasiliy"
	greetString="Whatsup"
  ;;
  "Masha" )
	greetString="Hey"
	nameString="Maria"
  ;;
  * )
	greetString="Hello"
	nameString="stranger"
  ;;
esac

echo "$greetString, $nameString!"

Циклы

Как и любой полноценный язык программирования, bash поддерживает циклы. Цикл for и цикл while. Циклы нужны, чтобы выполнять какой-то код заданное число раз. Например, при парсинге CSV перебирать построчно и каждую строку рассматривать отдельно.

Цикл for

Вот пример структуры цикла for:

    for var in list
do
команды
done

Простой реальный пример:

    #!/bin/bash

for name in Maria Vasya Michael stranger
do
	echo "Hello, $name!"
done

Вывод:

    root@geneviev:~ # ./cycle.sh 
Hello, Maria!
Hello, Vasya!
Hello, Michael!
Hello, stranger!

Программа просто перебирает все имена, разделенные пробелом, и выводит их с помощью echo.

Попробуем немного усложнить пример:

    #!/bin/bash
file=$1
for name in $(cat $file)
do 
	echo "Hello, $name!"
done

Создадим файл с именами touch names и запишем в него список имен для приветствия:

    Maria
Vasiliy
Ivan
Nikolai
Innokentiy

Вывод:

    root@geneviev:~ # ./cycle.sh 
^C
root@geneviev:~ # ./cycle.sh names
Hello, Maria!
Hello, Vasiliy!
Hello, Ivan!
Hello, Nikolai!
Hello, Innokentiy!

Обратите внимание на ^C. Это символ прерывания выполнения программы. В нашем случае мы вызвали программу без аргумента, и она вошла в вечный цикл. Можно сказать, зависла. Пришлось завершить ее принудительно. Не забывайте делать проверки входных данных в реальных программах.  Как это делать, можете посмотреть в главах if-else и switch case, например.  

В нашей программе есть небольшой баг. Модифицируем файл имен:

    Erich Maria Remarque
Vasiliy
Ivan
Nikolai
Innokentiy

Запустим программу, получим вывод:

    root@geneviev:~ # ./cycle.sh names
Hello, Erich!
Hello, Maria!
Hello, Remarque!
Hello, Vasiliy!
Hello, Ivan!
Hello, Nikolai!
Hello, Innokentiy!

Как говорится, «Кто все эти люди?». Так получается, потому что у нас не задана переменная окружения IFS (Internal Field Separator), указывающая на разделители полей. Наш цикл использует пробелы и переносы строки как разделители. В начале скрипта (после #!/bin/bash) укажите использовать перенос строки как разделитель полей: IFS=$’\n’

    root@geneviev:~ # ./cycle.sh names 
Hello, Erich Maria Remarque!
Hello, Vasiliy!
Hello, Ivan!
Hello, Nikolai!
Hello, Innokentiy!

В итоге мы получим возможность работать со строками целиком. Это пригодится для парсинга CSV.

Обычно цикл for используется со счетчиком. В C-like стиле. Что-то вроде for (i=0;i<10;i++){}. В bash тоже так можно. 

    #!/bin/bash
for (( i=1; i <= 10; i++ ))
do
echo "number is $i"
done
 
Вывод:
 
root@geneviev:~ # ./cycle.sh 
number is 1
number is 2
number is 3
number is 4
number is 5
number is 6
number is 7
number is 8
number is 9
number is 10

Цикл while

Схема организации цикла while:

    while команда проверки условия
do
другие команды
done

Простой способ сделать бесконечную петлю (бесконечный цикл):

    while true
	do
	echo "this is infinity loop"
done

Это может пригодится, например, когда вам нужно вызывать что-то чаще, чем позволяет cron (например, раз в минуту). Или когда вам просто надо проверять какое-то значение постоянно. Областей применения у бесконечных циклов много.

Здесь используются те же самые выражения, что и в if:

    #!/bin/bash
count=0
while [ $count -lt 10 ]
do
	(( count++ ))
	echo "count: $count"
done

Вывод:

    root@geneviev:~ # ./cycle.sh 
count: 1
count: 2
count: 3
count: 4
count: 5
count: 6
count: 7
count: 8
count: 9
count: 10

Из цикла можно выйти с помощью команды break (работает также и для for):

    #!/bin/bash
count=0
while [ $count -lt 10 ]
do
	(( count++ ))
	echo "count: $count"
	if [ "$count" -gt 5 ]
	then
		break
	fi
done

Вывод:

    root@geneviev:~ # ./cycle.sh 
count: 1
count: 2
count: 3
count: 4
count: 5
count: 6

Заключение

Несмотря на огромную конкуренцию в сфере автоматизации рутины со стороны python, ruby, perl bash не сдает позиции. Он прост в освоении и использовании, гибок и так или иначе присутствует в абсолютном большинстве дистрибутивов Linux.

В этой статье были приведены только основы написания программ на bash. Надеемся, вы нашли их полезными для себя.

Advanced Bash-Scripting Guide

��������� ���������������� �� ����� ���������
��������� ��������

�������: ������ �������

��������: Russian Linux Gazette

����� ����������� � html-������� (~380��)

����� ����������� � sgml-������� (~380��)


������ ����������� �� ������������ ������� � ��������
�������� � ������� ���������������� �� ����� ���������,
������, ������ ���������� ���� ���������� . . . ����������, ��� �� ����� ���������
�������� � ������� UNIX
. ��� ����������� �����
��������������� ��� �������, ��������������� ���
���������������� �������� ��� ��� ���������� ��
���������������� �� shell. ����������� �������� ������
������ ������������������� ��������, ��������� ������ ���� � �������� �����
��������� — ��� ��������� ���������
.

��������� ������ ���������, � ���� .bz2 ������, ����������
�������� ������ � ������� SGML � HTML, �� ������� �� �������� ��������� ������. ��� �� ��
������� � change log.


Bash Scripting Tutorial – Linux Shell Script and Command Line for Beginners

In Linux, process automation relies heavily on shell scripting. This involves creating a file containing a series of commands that can be executed together.

In this article, we’ll start with the basics of bash scripting which includes variables, commands, inputs/ outputs, and debugging. We’ll also see examples of each along the way.

Let’s get started. 🚀

Table of Contents

  1. Pre-requisites
  2. Introduction
  • Definition of Bash scripting
  • Advantages of Bash scripting
  • Overview of Bash shell and command line interface
  1. How to Get Started with Bash Scripting
  • How to Run Bash Commands from the Command Line
  • How to Create and Execute Bash Scripts
  1. Bash Scripting Basics
  • Comments in bash scripting
  • Variables and data types in Bash
  • Input and output in Bash scripts
  • Basic Bash commands (echo, read, etc.)
  • Conditional statements (if/else)
  1. Looping and Branching in Bash
  • While loop
  • For loop
  • Case statements
  1. How to Schedule Scripts using cron
  2. How to Debug and Troubleshoot Bash Scripts
  3. Conclusion
  • Resources for learning more about Bash scripting

Pre-requisites

To follow along with this tutorial, you should have the following accesses:

  • A running version of Linux with access to the command line.

If you do not have Linux installed or you are just starting out, you can easily access the Linux command line through Replit. Replit is a browser-based IDE where you can access the bash shell in a few minutes.

You can also install Linux on top of your Windows system using WSL (Windows Subsystem for Linux). Here is a tutorial for that.

Introduction

Definition of Bash scripting

A bash script is a file containing a sequence of commands that are executed by the bash program line by line. It allows you to perform a series of actions, such as navigating to a specific directory, creating a folder, and launching a process using the command line.

By saving these commands in a script, you can repeat the same sequence of steps multiple times and execute them by running the script.

Advantages of Bash scripting

Bash scripting is a powerful and versatile tool for automating system administration tasks, managing system resources, and performing other routine tasks in Unix/Linux systems. Some advantages of shell scripting are:

  • Automation: Shell scripts allow you to automate repetitive tasks and processes, saving time and reducing the risk of errors that can occur with manual execution.
  • Portability: Shell scripts can be run on various platforms and operating systems, including Unix, Linux, macOS, and even Windows through the use of emulators or virtual machines.
  • Flexibility: Shell scripts are highly customizable and can be easily modified to suit specific requirements. They can also be combined with other programming languages or utilities to create more powerful scripts.
  • Accessibility: Shell scripts are easy to write and don’t require any special tools or software. They can be edited using any text editor, and most operating systems have a built-in shell interpreter.
  • Integration: Shell scripts can be integrated with other tools and applications, such as databases, web servers, and cloud services, allowing for more complex automation and system management tasks.
  • Debugging: Shell scripts are easy to debug, and most shells have built-in debugging and error-reporting tools that can help identify and fix issues quickly.

Overview of Bash shell and command line interface

The terms «shell» and «bash» are used interchangeably. But there is a subtle difference between the two.

The term «shell» refers to a program that provides a command-line interface for interacting with an operating system. Bash (Bourne-Again SHell) is one of the most commonly used Unix/Linux shells and is the default shell in many Linux distributions.

A shell or command-line interface looks like this:

image-135

The shell accepts commands from the user and displays the output

In the above output, zaira@Zaira is the shell prompt. When a shell is used interactively, it displays a $ when it is waiting for a command from the user.

If the shell is running as root (a user with administrative rights), the prompt is changed to #. The superuser shell prompt looks like this:

[root@host ~]#

Although Bash is a type of shell, there are other shells available as well, such as Korn shell (ksh), C shell (csh), and Z shell (zsh). Each shell has its own syntax and set of features, but they all share the common purpose of providing a command-line interface for interacting with the operating system.

You can determine your shell type using the ps command:

ps

Here is the output for me:

image-134

Checking the shell type. I’m using bash shell

In summary, while «shell» is a broad term that refers to any program that provides a command-line interface, «Bash» is a specific type of shell that is widely used in Unix/Linux systems.

Note: In this tutorial, we will be using the «bash» shell.

How to Get Started with Bash Scripting

Running Bash commands from the command line

As mentioned earlier, the shell prompt looks something like this:

[username@host ~]$

You can enter any command after the $ sign and see the output on the terminal.

Generally, commands follow this syntax:

command [OPTIONS] arguments

Let’s discuss a few basic bash commands and see their outputs. Make sure to follow along :)

  • date: Displays the current date
zaira@Zaira:~/shell-tutorial$ date
Tue Mar 14 13:08:57 PKT 2023
  • pwd: Displays the present working directory.
zaira@Zaira:~/shell-tutorial$ pwd
/home/zaira/shell-tutorial
  • ls: Lists the contents of the current directory.
zaira@Zaira:~/shell-tutorial$ ls
check_plaindrome.sh  count_odd.sh  env  log  temp
  • echo: Prints a string of text, or value of a variable to the terminal.
zaira@Zaira:~/shell-tutorial$ echo "Hello bash"
Hello bash

You can always refer to a commands manual with the man command.

For example, the manual for ls looks something like this:

image-138

You can see options for a command in detail using man

How to Create and Execute Bash scripts

Script naming conventions

By naming convention, bash scripts end with .sh. However, bash scripts can run perfectly fine without the sh extension.

Adding the Shebang

Bash scripts start with a shebang. Shebang is a combination of bash # and bang ! followed by the bash shell path. This is the first line of the script. Shebang tells the shell to execute it via bash shell. Shebang is simply an absolute path to the bash interpreter.

Below is an example of the shebang statement.

#!/bin/bash

You can find your bash shell path (which may vary from the above) using the command:

which bash

Creating our first bash script

Our first script prompts the user to enter a path. In return, its contents will be listed.

Create a file named run_all.sh using the vi command. You can use any editor of your choice.

vi run_all.sh

Add the following commands in your file and save it:

#!/bin/bash
echo "Today is " `date`

echo -e "\nenter the path to directory"
read the_path

echo -e "\n you path has the following files and folders: "
ls $the_path
Script to print contents of a user supplied directory

Let’s take a deeper look at the script line by line. I am displaying the same script again, but this time with line numbers.  

  1 #!/bin/bash
  2 echo "Today is " `date`
  3
  4 echo -e "\nenter the path to directory"
  5 read the_path
  6
  7 echo -e "\n you path has the following files and folders: "
  8 ls $the_path
  • Line #1: The shebang (#!/bin/bash) points toward the bash shell path.
  • Line #2: The echo command is displaying the current date and time on the terminal. Note that the date is in backticks.
  • Line #4: We want the user to enter a valid path.
  • Line #5: The read command reads the input and stores it in the variable the_path.
  • line #8: The ls command takes the variable with the stored path and displays the current files and folders.

Executing the bash script

To make the script executable, assign execution rights to your user using this command:

chmod u+x run_all.sh

Here,

  • chmod modifies the ownership of a file for the current user :u.
  • +x adds the execution rights to the current user. This means that the user who is the owner can now run the script.
  • run_all.sh is the file we wish to run.

You can run the script using any of the mentioned methods:

  • sh run_all.sh
  • bash run_all.sh
  • ./run_all.sh

Let’s see it running in action 🚀

run-script-bash-2

Bash Scripting Basics

Comments start with a # in bash scripting. This means that any line that begins with a # is a comment and will be ignored by the interpreter.

Comments are very helpful in documenting the code, and it is a good practice to add them to help others understand the code.

These are examples of comments:

# This is an example comment
# Both of these lines will be ignored by the interpreter

Variables and data types in Bash

Variables let you store data. You can use variables to read, access, and manipulate data throughout your script.

There are no data types in Bash. In Bash, a variable is capable of storing numeric values, individual characters, or strings of characters.

In Bash, you can use and set the variable values in the following ways:

  1. Assign the value directly:
country=Pakistan

2.  Assign the value based on the output obtained from a program or command, using command substitution. Note that $ is required to access an existing variable’s value.

same_country=$country
This assigns the value of countryto the new variable same_country

To access the variable value, append $ to the variable name.

zaira@Zaira:~$ country=Pakistan
zaira@Zaira:~$ echo $country
Pakistan
zaira@Zaira:~$ new_country=$country
zaira@Zaira:~$ echo $new_country
Pakistan
Assigning and printing variable values

Variable naming conventions

In Bash scripting, the following are the variable naming conventions:

  1. Variable names should start with a letter or an underscore (_).
  2. Variable names can contain letters, numbers, and underscores (_).
  3. Variable names are case-sensitive.
  4. Variable names should not contain spaces or special characters.
  5. Use descriptive names that reflect the purpose of the variable.
  6. Avoid using reserved keywords, such as if, then, else, fi, and so on as variable names.

Here are some examples of valid variable names in Bash:

name
count
_var
myVar
MY_VAR

And here are some examples of invalid variable names:

2ndvar (variable name starts with a number)
my var (variable name contains a space)
my-var (variable name contains a hyphen)

Following these naming conventions helps make Bash scripts more readable and easier to maintain.

Input and output in Bash scripts

Gathering input

In this section, we’ll discuss some methods to provide input to our scripts.

  1. Reading the user input and storing it in a variable

We can read the user input using the read command.

#!/bin/bash
echo "Today is " `date`

echo -e "\nenter the path to directory"
read the_path

echo -e "\nyour path has the following files and folders: "
ls $the_path

name-sh

2.  Reading from a file

This code reads each line from a file named input.txt and prints it to the terminal. We’ll study while loops later in this article.

while read line
do
  echo $line
done < input.txt

3.  Command line arguments

In a bash script or function, $1 denotes the initial argument passed, $2 denotes the second argument passed, and so forth.

This script takes a name as a command-line argument and prints a personalized greeting.

echo "Hello, $1!"

We have supplied Zaira as our argument to the script.

#!/bin/bash
echo "Hello, $1!"
The code for the script: greeting.sh

Output:

name-sh-1

Displaying output

Here we’ll discuss some methods to receive output from the scripts.

  1. Printing to the terminal:
echo "Hello, World!"

This prints the text «Hello, World!» to the terminal.

2.  Writing to a file:

echo "This is some text." > output.txt

This writes the text «This is some text.» to a file named output.txt. Note that the >operator overwrites a file if it already has some content.

3.  Appending to a file:

echo "More text." >> output.txt

This appends the text «More text.» to the end of the file output.txt.

4.  Redirecting output:

ls > files.txt

This lists the files in the current directory and writes the output to a file named files.txt. You can redirect output of any command to a file this way.

Basic Bash commands (echo, read, etc.)

Here is a list of some of the most commonly used bash commands:

  1. cd: Change the directory to a different location.
  2. ls: List the contents of the current directory.
  3. mkdir: Create a new directory.
  4. touch: Create a new file.
  5. rm: Remove a file or directory.
  6. cp: Copy a file or directory.
  7. mv: Move or rename a file or directory.
  8. echo: Print text to the terminal.
  9. cat: Concatenate and print the contents of a file.
  10. grep: Search for a pattern in a file.
  11. chmod: Change the permissions of a file or directory.
  12. sudo: Run a command with administrative privileges.
  13. df: Display the amount of disk space available.
  14. history: Show a list of previously executed commands.
  15. ps: Display information about running processes.

Conditional statements (if/else)

Expressions that produce a boolean result, either true or false, are called conditions. There are several ways to evaluate conditions, including if, if-else, if-elif-else, and nested conditionals.

Syntax:

if [[ condition ]];
then
	statement
elif [[ condition ]]; then
	statement 
else
	do this by default
fi
Syntax of bash conditional statements

We can use logical operators such as AND -a and OR -o to make comparisons that have more significance.

if [ $a -gt 60 -a $b -lt 100 ]
This statement checks if both conditions are true: a is greater than 60 AND b is less than 100.

Let’s see an example of a Bash script that uses if, if-else, and if-elif-else statements to determine if a user-inputted number is positive, negative, or zero:

#!/bin/bash

echo "Please enter a number: "
read num

if [ $num -gt 0 ]; then
  echo "$num is positive"
elif [ $num -lt 0 ]; then
  echo "$num is negative"
else
  echo "$num is zero"
fi
Script to determine if a number is positive, negative, or zero

The script first prompts the user to enter a number. Then, it uses an if statement to check if the number is greater than 0. If it is, the script outputs that the number is positive. If the number is not greater than 0, the script moves on to the next statement, which is an if-elif statement. Here, the script checks if the number is less than 0. If it is, the script outputs that the number is negative. Finally, if the number is neither greater than 0 nor less than 0, the script uses an else statement to output that the number is zero.

Seeing it in action 🚀

test-odd

Looping and Branching in Bash

While loop

While loops check for a condition and loop until the condition remains true. We need to provide a counter statement that increments the counter to control loop execution.

In the example below, (( i += 1 )) is the counter statement that increments the value of i. The loop will run exactly 10 times.

#!/bin/bash
i=1
while [[ $i -le 10 ]] ; do
   echo "$i"
  (( i += 1 ))
done
While loop that iterates 10 times.
image-187

For loop

The for loop, just like the while loop, allows you to execute statements a specific number of times. Each loop differs in its syntax and usage.

In the example below, the loop will iterate 5 times.

#!/bin/bash

for i in {1..5}
do
    echo $i
done
For loop that iterates 5 times.
image-186

Case statements

In Bash, case statements are used to compare a given value against a list of patterns and execute a block of code based on the first pattern that matches. The syntax for a case statement in Bash is as follows:

case expression in
    pattern1)
        # code to execute if expression matches pattern1
        ;;
    pattern2)
        # code to execute if expression matches pattern2
        ;;
    pattern3)
        # code to execute if expression matches pattern3
        ;;
    *)
        # code to execute if none of the above patterns match expression
        ;;
esac
Case statements syntax

Here, «expression» is the value that we want to compare, and «pattern1», «pattern2», «pattern3», and so on are the patterns that we want to compare it against.

The double semicolon «;;» separates each block of code to execute for each pattern. The asterisk «*» represents the default case, which executes if none of the specified patterns match the expression.

Let’s see an example.

fruit="apple"

case $fruit in
    "apple")
        echo "This is a red fruit."
        ;;
    "banana")
        echo "This is a yellow fruit."
        ;;
    "orange")
        echo "This is an orange fruit."
        ;;
    *)
        echo "Unknown fruit."
        ;;
esac
Example of case statement

In this example, since the value of «fruit» is «apple», the first pattern matches, and the block of code that echoes «This is a red fruit.» is executed. If the value of «fruit» were instead «banana», the second pattern would match and the block of code that echoes «This is a yellow fruit.» would execute, and so on. If the value of «fruit» does not match any of the specified patterns, the default case is executed, which echoes «Unknown fruit.»

How to Schedule Scripts using cron

Cron is a powerful utility for job scheduling that is available in Unix-like operating systems. By configuring cron, you can set up automated jobs to run on a daily, weekly, monthly, or specific time basis. The automation capabilities provided by cron play a crucial role in Linux system administration.

Below is the syntax to schedule crons:

# Cron job example
* * * * * sh /path/to/script.sh

Here, the *s represent minute(s) hour(s) day(s) month(s) weekday(s), respectively.

Below are some examples of scheduling cron jobs.

Schedule Description Example
0 0 * * * Run a script at midnight every day 0 0 * * * /path/to/script.sh
*/5 * * * * Run a script every 5 minutes */5 * * * * /path/to/script.sh
0 6 * * 1-5 Run a script at 6 am from Monday to Friday 0 6 * * 1-5 /path/to/script.sh
0 0 1-7 * * Run a script on the first 7 days of every month 0 0 1-7 * * /path/to/script.sh
0 12 1 * * Run a script on the first day of every month at noon 0 12 1 * * /path/to/script.sh

Using crontab

The crontab utility is used to add and edit the cron jobs.

crontab -l lists the already scheduled scripts for a particular user.

You can add and edit the cron through crontab -e.

You can read more about corn jobs in my other article here.

How to Debug and Troubleshoot Bash Scripts

Debugging and troubleshooting are essential skills for any Bash scripter. While Bash scripts can be incredibly powerful, they can also be prone to errors and unexpected behavior. In this section, we will discuss some tips and techniques for debugging and troubleshooting Bash scripts.

Set the set -x option

One of the most useful techniques for debugging Bash scripts is to set the set -x option at the beginning of the script. This option enables debugging mode, which causes Bash to print each command that it executes to the terminal, preceded by a + sign. This can be incredibly helpful in identifying where errors are occurring in your script.

#!/bin/bash

set -x

# Your script goes here

Check the exit code

When Bash encounters an error, it sets an exit code that indicates the nature of the error. You can check the exit code of the most recent command using the $? variable. A value of 0 indicates success, while any other value indicates an error.

#!/bin/bash

# Your script goes here

if [ $? -ne 0 ]; then
    echo "Error occurred."
fi

Use echo statements

Another useful technique for debugging Bash scripts is to insert echo statements throughout your code. This can help you identify where errors are occurring and what values are being passed to variables.

#!/bin/bash

# Your script goes here

echo "Value of variable x is: $x"

# More code goes here

Use the set -e option

If you want your script to exit immediately when any command in the script fails, you can use the set -e option. This option will cause Bash to exit with an error if any command in the script fails, making it easier to identify and fix errors in your script.

#!/bin/bash

set -e

# Your script goes here

Troubleshooting crons by verifying logs

We can troubleshoot crons using the log files. Logs are maintained for all the scheduled jobs. You can check and verify in logs if a specific job ran as intended or not.

For Ubuntu/Debian, you can find cronlogs at:

/var/log/syslog

The location varies for other distributions.

A cron job log file can look like this:

2022-03-11 00:00:01 Task started
2022-03-11 00:00:02 Running script /path/to/script.sh
2022-03-11 00:00:03 Script completed successfully
2022-03-11 00:05:01 Task started
2022-03-11 00:05:02 Running script /path/to/script.sh
2022-03-11 00:05:03 Error: unable to connect to database
2022-03-11 00:05:03 Script exited with error code 1
2022-03-11 00:10:01 Task started
2022-03-11 00:10:02 Running script /path/to/script.sh
2022-03-11 00:10:03 Script completed successfully
Cron log

Conclusion

In this article, we started with how to access the terminal and then ran some basic bash commands. We also studied what a bash shell is. We briefly looked at branching the code using loops and conditionals. Finally, we discussed automating the scripts using cron followed by some troubleshooting techniques.

Resources for learning more about Bash scripting

If you want to dig deeper into the world of bash scripting, I would suggest you have a look at this 6-hour course on Linux at freeCodeCamp.

What’s your favorite thing you learned from this tutorial? You can also connect with me on any of these platforms. 📧�

See you in the next tutorial, happy coding 😁

Banner image credits: Image by Freepik



Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started

Понравилась статья? Поделить с друзьями:
  • Укпр гмв руководство по эксплуатации
  • Амлодипин ретард инструкция по применению цена отзывы
  • Электродуховка самсунг инструкция по эксплуатации на русском
  • Должностная инструкция старшего смены на объекте судебного пристава по оупдс
  • Мультиварка redmond rmc m70 инструкция по эксплуатации