Как писать Bash-скрипты в операционной системе Linux

Встроенные команды в среде bash (и ее аналоги sh, zsh и другие) совместимы с любым приложением, соответствующим стандартам POSIX, в операционной системе Linux. Это дает вам возможность включить в свой bash-скрипт любое совместимое приложение, расширяя ваши возможности в области автоматизации повседневных задач администрирования систем Linux, развертывания и сборки приложений, а также выполнения разнообразных пакетных операций, включая обработку аудио и видео.

Командная строка представляет собой наиболее мощный интерфейс для взаимодействия с системой на данный момент. Получить базовое понимание ее работы довольно просто. Рекомендуется изучить руководство по командам bash, для чего можно воспользоваться командой man bash.

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

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


Какой редактор выбрать?

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

  • vim
  • nano
  • mcedit

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


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

Обычно, первой программой, которую разрабатывают программисты, является “Hello, World!” – простой вывод этой фразы. Мы также начнем с этого. Для вывода строки в консоль используется команда echo. Просто введите echo “Hello, World!” в командной строке, и вы увидите соответствующий вывод:

root@linux:~ # 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@linux:~ # which bash
/usr/bin/bash

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

Для запуска вашего скрипта или команды существуют два способа.

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

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

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

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

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

Аргументы

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

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

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

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

Давайте рассмотрим пример: создадим новый файл с помощью команды touch hellousername.sh и предоставим ему права на выполнение с помощью команды chmod +x hellousername.sh.

Затем откроем файл hellousername.sh в редакторе nano.

Вот код примера:

#!/bin/bash 

echo "Hello, $1!"

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

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

Эта программа небольшая, но она демонстрирует, как использовать аргументы на самом базовом уровне. В данном случае она принимает один аргумент, “Dima”, и сразу же использует его без каких-либо дополнительных проверок.

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

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

Способ №1

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

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

Способ №2

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

echo "Hello, $1!"

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

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

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

Давайте рассмотрим один из вышеупомянутых примеров.

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

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

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

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

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

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

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

Выражение “$1” = “Dima” проверяет, совпадают ли строки между собой. Блок команд, следующий за оператором else, выполняется только в случае, если выше в коде не было обнаружено другого более подходящего блока для выполнения.

&& и ||

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

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

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

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

Переменные

Как гласит один из основных принципов программирования — “Не повторяй себя” (DRY). Поэтому мы перепишем предыдущий пример, используя переменные, чтобы избежать повторного вызова команды echo каждый раз.

В языке программирования Bash переменные создаются с помощью оператора присваивания, например: x="foo bar" или z=$1. Мы присвоили переменной x строку “foo bar”, а переменной z – значение первого аргумента. Использование именованных переменных гораздо удобнее, чем использование $1, особенно когда значение аргумента нужно использовать во многих местах.

Кроме того, аргументы могут быть представлены в различном порядке. Мы также подчеркиваем важность использования осмысленных имен переменных. Это помогает избежать путаницы, особенно при дальнейшем развитии программы. Избегайте использования неинформативных имен переменных (и функций), таких как “a”, “b”, “zzzz” и т.д.

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

#!/bin/bash

greetString="Hello"
nameString="stranger"

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

if [ "$1" = "Dima" ]; 
then
    nameString="Dmitry"
 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
  "Dima" )
    nameString="Dmitry"
 greetString="Whatsup"
  ;;
  "Masha" )
 greetString="Hey"
 nameString="Maria"
  ;;
  * )
 greetString="Hello"
 nameString="stranger"
  ;;
esac

echo "$greetString, $nameString!"

Циклы

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

Цикл for

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

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

#!/bin/bash

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

Вывод:

root@linux:~ # ./cycle.sh 
Hello, Maria!
Hello, Dima!
Hello, Michael!
Hello, stranger!

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

Теперь давайте попробуем усовершенствовать этот пример:

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

Давайте создадим файл с именем “names”, а затем запишем в него список имен для приветствия с помощью команды “touch names”.

Maria
Dmitry
Ivan
Nikolai
Innokentiy

Вывод:

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

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

В нашей программе есть небольшая ошибка. Давайте внесем изменения в файл с именами:

Erich Maria Remarque
Dmitry
Ivan
Nikolai
Innokentiy

Запустим программу:

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

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

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

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

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

#!/bin/bash
for (( i=1; i <= 10; i++ ))
do
echo "number is $i"
done

Вывод:

root@linux:~ # ./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 команда проверки условия
do
другие команды
done

Cпособ сделать бесконечную петлю:

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@linux:~ # ./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@linux:~ # ./cycle.sh 
count: 1
count: 2
count: 3
count: 4
count: 5
count: 6

Заключение

Несмотря на огромную конкуренцию в сфере автоматизации от языков программирования, таких как Python, Ruby и Perl, Bash по-прежнему остается востребованным. Он прост в изучении и использовании, гибок и почти всегда доступен в большинстве дистрибутивов Linux.

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

Что будем искать? Например,Cisco

Мы в социальных сетях