Bash-скрипты, руководство в 11 частях — страница 3 из 19

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


Bash-скрипты, часть 2: циклы


В прошлый раз мы рассказали об основах программирования для bash. Даже то немногое, что уже разобрано, позволяет всем желающим приступить к автоматизации работы в Linux. В этом материале продолжим рассказ о bash-скриптах, поговорим об управляющих конструкциях, которые позволяют выполнять повторяющиеся действия. Речь идёт о циклах for и while, о методах работы с ними и о практических примерах их применения.

Циклы for


Оболочка bash поддерживает циклы for, которые позволяют организовывать перебор последовательностей значений. Вот какова базовая структура таких циклов:


for var in list

do

команды

done

В каждой итерации цикла в переменную var будет записываться следующее значение из списка list. В первом проходе цикла, таким образом, будет задействовано первое значение из списка. Во втором — второе, и так далее — до тех пор, пока цикл не дойдёт до последнего элемента.

Перебор простых значений


Пожалуй, самый простой пример цикла for в bash-скриптах — это перебор списка простых значений:


#!/bin/bash

for var in first second third fourth fifth

do

echo The  $var item

done

Ниже показаны результаты работы этого скрипта. Хорошо видно, что в переменную $var последовательно попадают элементы из списка. Происходит так до тех пор, пока цикл не дойдёт до последнего из них.


Простой цикл for

Обратите внимание на то, что переменная $var сохраняет значение при выходе из цикла, её содержимое можно менять, в целом, работать с ней можно как с любой другой переменной.

Перебор сложных значений


В списке, использованном при инициализации цикла for, могут содержаться не только простые строки, состоящие из одного слова, но и целые фразы, в которые входят несколько слов и знаков препинания. Например, всё это может выглядеть так:


#!/bin/bash

for var in first "the second" "the third" "I’ll do it"

do

echo "This is: $var"

done

Вот что получится после того, как этот цикл пройдётся по списку. Как видите, результат вполне ожидаем.


Перебор сложных значений

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


Ещё один способ инициализации цикла for заключается в передаче ему списка, который является результатом работы некоей команды. Тут используется подстановка команд для их исполнения и получения результатов их работы.


#!/bin/bash

file="myfile"

for var in $(cat $file)

do

echo " $var"

done

В этом примере задействована команда cat, которая читает содержимое файла. Полученный список значений передаётся в цикл и выводится на экран. Обратите внимание на то, что в файле, к которому мы обращаемся, содержится список слов, разделённых знаками перевода строки, пробелы при этом не используются.


Цикл, который перебирает содержимое файла

Тут надо учесть, что подобный подход, если ожидается построчная обработка данных, не сработает для файла более сложной структуры, в строках которого может содержаться по несколько слов, разделённых пробелами. Цикл будет обрабатывать отдельные слова, а не строки. Что, если это совсем не то, что нужно?

Разделители полей


Причина вышеописанной особенности заключается в специальной переменной окружения, которая называется IFS (Internal Field Separator) и позволяет указывать разделители полей. По умолчанию оболочка bash считает разделителями полей следующие символы:


   • Пробел

   • Знак табуляции

   • Знак перевода строки


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


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


IFS=$'\n'

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


#!/bin/bash

file="/etc/passwd"

IFS=$'\n'

for var in $(cat $file)

do

echo " $var"

done

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


Построчный обход содержимого файла в цикле for

Разделителями могут быть и другие символы. Например, выше мы выводили на экран содержимое файла /etc/passwd. Данные о пользователях в строках разделены с помощью двоеточий. Если в цикле нужно обрабатывать подобные строки, IFS можно настроить так:


IFS=:

Обход файлов, содержащихся в директории


Один из самых распространённых вариантов использования циклов for в bash-скриптах заключается в обходе файлов, находящихся в некоей директории, и в обработке этих файлов.


Например, вот как можно вывести список файлов и папок:


#!/bin/bash

for file in /home/likegeeks/*

do

if [ -d "$file" ]

then

echo "$file is a directory"

elif [ -f "$file" ]

then

echo "$file is a file"

fi

done

Если вы разобрались с предыдущим материалом из этой серии статей, вам должно быть понятно устройство конструкции if-then, а так же то, как отличить файл от папки. Если вам сложно понять вышеприведённый код, перечитайте этот материал.


Вот что выведет скрипт.


Вывод содержимого папки

Обратите внимание на то, как мы инициализируем цикл, а именно — на подстановочный знак «*» в конце адреса папки. Этот символ можно воспринимать как шаблон, означающий: «все файлы с любыми именами». он позволяет организовать автоматическую подстановку имён файлов, которые соответствуют шаблону.


При проверке условия в операторе if, мы заключаем имя переменной в кавычки. Сделано это потому что имя файла или папки может содержать пробелы.

Циклы for в стиле C


Если вы знакомы с языком программирования C, синтаксис описания bash-циклов for может показаться вам странным, так как привыкли вы, очевидно, к такому описанию циклов:


for (i = 0; i < 10; i++)

{

printf(“number is %d\n”, i);

}

В bash-скриптах можно использовать циклы for, описание которых выглядит очень похожим на циклы в стиле C, правда, без некоторых отличий тут не обошлось. Схема цикла при подобном подходе выглядит так:


for (( начальное значение переменной ; условие окончания цикла; изменение переменной ))

На bash это можно написать так:


for (( a = 1; a < 10; a++ ))

А вот рабочий пример:


#!/bin/bash

for (( i=1; i <= 10; i++ ))

do

echo "number is $i"

done

Этот код выведет список чисел от 1 до 10.


Работа цикла в стиле C

Цикл while


Конструкция for — не единственный способ организации циклов в bash-скриптах. Здесь можно пользоваться и циклами while. В таком цикле можно задать команду проверки некоего условия и выполнять тело цикла до тех пор, пока проверяемое условие возвращает ноль, или сигнал успешного завершения некоей операции. Когда условие цикла вернёт ненулевое значение, что означает ошибку, цикл остановится.


Вот схема организации циклов while


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

do

другие команды

done

Взглянем на пример скрипта с таким циклом:


#!/bin/bash

var1=5

while [ $var1 -gt 0 ]

do

echo $var1

var1=$[ $var1 - 1 ]

done

На входе в цикл проверяется, больше ли нуля переменная $var1. Если это так, выполняется тело цикла, в котором из значения переменной вычитается единица. Так происходит в каждой итерации, при этом мы выводим в консоль значение переменной до его модификации. Как только $var1 примет значение 0, цикл прекращается.

Результат работы цикла while

Если не модифицировать переменную $var1, это приведёт к попаданию скрипта в бесконечный цикл.

Вложенные циклы


В теле цикла можно использовать любые команды, в том числе — запускать другие циклы. Такие конструкции называют вложенными циклами:


#!/bin/bash

for (( a = 1; a <= 3; a++ ))

do

echo "Start $a:"

for (( b = 1; b <= 3; b++ ))

do

echo " Inner loop: $b"

done

done

Ниже показано то, что выведет этот скрипт. Как видно, сначала выполняется первая итерация внешнего цикла, потом — три итерации внутреннего, после его завершения снова в дело вступает внешний цикл, потом опять — внутренний.


Вложенные циклы

Обработка содержимого файла


Чаще всего вложенные циклы используют для обработки файлов. Так, внешний цикл занимается перебором строк файла, а внутренний уже работает с каждой строкой. Вот, например, как выглядит обработка файла /etc/passwd:


#!/bin/bash

IFS=$'\n'

for entry in $(cat /etc/passwd)

do

echo "Values in $entry –"

IFS=:

for value in $entry

do

echo " $value"

done

done

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