На этой лекции мы продолжаем знакомиться с bash . Хочу напомнить, что мы рассматриваем те элементы bash , которые помогут нам понимать скрипты операционной системы. Такими элементами безусловно являются циклы и функции. Если кто-то изучал программирование, то трудностей с пониманием этих вопросов не возникнет.
Цикл for
Цикл for в bash имеет два вида. Рассмотрим сначала классический вариант for . Общий вид следующий:
Между элементами for и in задается переменная, которая по очереди принимает значение из последовательности значений заданной между in и do . Между do и done находятся команды которые выполняются каждый раз когда переменная меняет свое значение. Цикл прекращает работу когда переменная примет последнее значение из последовательности. Значения в последовательности задаются через пробел.
А вот практический пример:
Последовательность значений можно задавать разными способами. Явно — как в примере выше, или с помощью других переменных, или с помощью специальных команд. Рассмотрим некоторые примеры. Так как значения задаются через пробел, то в качестве таких значений может быть любая переменная, которая содержит строку с пробелами:
Результат будет таким же как и в первом примере.
Если необходимо задать последовательность чисел, то можно воспользоваться командой seq и механизмом подстановки. Команда seq возвращает на экран последовательность числовых значений. Синтаксис прост и будет понятен из примера ниже:
Результат:
Вернемся ко второму виду for . Часто в скриптах можно встретить так называемый С-подобный вариант for , которых используется для циклов на основе чисел. Рассмотрим сразу пример:
Цикл выполняется пока проверяемое в выражении условие верно. Как только выражение возвращает ложь выполнение цикла прекращается.
Практический пример:
#!/bin/bash
i=1
while [ $i -lt 7 ]
do
echo $i
let i=i+1
done
В нашем примере проверяется, что переменная i меньше (-lt), числа 7 и если это так, то значение переменной выводится на экран. Выражение let i=i+1 , увеличивает переменную на единицу, снова происходит проверка и т.д. let говорит интерпретатору о том, что аргументы следует распознавать как числовые значения. Это строку можно было записать как let i++ (c-подобный вариант). При увеличении числа более чем на единицу можно записать так: let i+=2 — в этом случае i будет увеличиваться с шагом 2. Еще один вариант увеличения переменной это использование встроенного калькулятора (работает только с целыми числами). Доступ к калькулятору можно получить через двойные скобки: i=$(($i+1)) или через квадратные: i=$[$i+1] Пользоваться калькулятором можно и в командной строке:
С циклами нужно быть аккуратными, чтобы не получить вариант бесконечного цикла. Кстати для отладки bash скриптов можно изменить первую строку на#!/bin/bash -x или запускать скрипт командой bash -x :
[email protected]:~/linux$ bash -x ./testfor.sh
+ i=1
+ ‘[‘ 1 -gt 5 ‘]’
+ echo i=1
i=1
+ let i=i+1
+ ‘[‘ 2 -gt 5 ‘]’
+ echo i=2
i=2
+ let i=i+1
+ ‘[‘ 3 -gt 5 ‘]’
+ echo i=3
i=3
+ let i=i+1
+ ‘[‘ 4 -gt 5 ‘]’
+ echo i=4
i=4
+ let i=i+1
+ ‘[‘ 5 -gt 5 ‘]’
+ echo i=5
i=5
+ let i=i+1
+ ‘[‘ 6 -gt 5 ‘]’
Обязательно потренируйтесь в написании небольших скриптов для закрепления понимания работы циклов вbash .
Функции в bash
Функции применяются в bash очень широко. Описываются функции двумя способами: с ключевым словомfunction , и без него.
Первый способ:
function имя_функции
{
тело функции
}
Второй способ:
имя_функции ()
{
тело функции
}
Вызывается функция по имени в любом месте скрипта, но только после описания самой функции. Функции также можно передавать параметры, которые задаются через пробел после вызова (имени) функции. Рассмотрим пример скрипта bash :
#!/bin/bash
function primer
{
if [ $# -ne 0 ]
then
local a=1
echo «Количество переданных параметров — $#»
for i in [email protected]
do
echo «$a-й параметр — $i»
let a++
done
return 0
else
echo «Параметры не передавались»
return 1
fi
}
echo «Вызываем функцию с параметрами:»
primer a b c
echo $?
echo «Вызываем функцию без параметров:»
primer
echo $?
В данном примере задана функция с именем primer . Вызов функции с параметрами: primer a b c и без параметров: primer . В теле функции все конструкции вам должны быть знакомы, за исключением $# , $i и [email protected] .$# — возвращает количество параметров переданных функции. В нашем примере это будет число 3 .[email protected] возвращает все параметры одной строкой. В примере это будет a b c . А через $1 , $2 , $3 и т.д. можно обращаться к каждому параметру персонально. $? — содержит код выполнения последней команды. В нашем примере код выполнения функции.
Функция может также возвращать числовое значение через ключевое слово return . Как правило возвращают 0, если функция выполнена без ошибок или отличное от нуля значение, если что-то пошло не так. В примере, в случае вызова функции с параметрами, идет возврат значения 0, а если функция вызывалась без параметров, то будет возврат кода 1.
Все, что касается передачи параметров в функцию, работает точно так же и для скрипта. Скрипту точно также можно передавать параметры и точно также манипулировать ими при помощи $#, [email protected], $N . Из этой же категории и вариант — $0 — который возвращает имя команды запустившей скрипт. Если скрипт запускался по команде./script.sh , то echo $0 вернет значение ./script.sh , а если по команде /home/igor/linux/script.sh , то будет возвращено значение /home/igor/linux/script.sh .
5
Ваш скрипт закодирован опасным образом.
Во-первых, я предполагаю, что вы используете оболочку Bash, так как вы отметили ее "/ bash" и "/ for".
В моем ответе я процитирую это замечательное Bash guide , что, вероятно, является лучшим источником для изучения Bash.
1) Никогда не использовать Command Substitution , из либо рода, без кавычек. Здесь существует большая проблема: использование некотируемого расширения для разделения вывода на аргументы.
Конкретно говоря, это $(find$ DIRWORK -type d -name work) и $(find$ DIR -type f) претерпит Word Splitting , таким образом, если find находит файл с пробелами в его имени, то есть «имя файла», слово Расщепление результат Bash будет проходить 2 аргумент для for команды итерации по, т. е. один для «файла» и один для «имени». В этом случае вы хотите надеяться, что вы получите «файл: нет такого файла или каталога» и «имя: нет такого файла или каталога», а не потенциально наносить им ущерб, если они действительно существуют.
2) По соглашению переменные среды (PATH, EDITOR, SHELL, ...) и внутренние переменные оболочки (BASH_VERSION, RANDOM, ...) полностью капитализируются. Все остальные имена переменных должны быть строчными. Поскольку имена переменных чувствительны к регистру, это соглашение позволяет избежать случайного переопределения экологических и внутренних переменных.
Это является более безопасной версией вашего сценария, который я рекомендую вам использовать вместо:
My_home="/root/mydir" my_dir=" $my_home/var" dir_work="$ my_home/Local" while IFS= read -r -d "" f; do # I"m guessing that you also want to ignore stderr; # this is where the 2>&1 came from. if lsof -n " $f" | grep "" > /dev/null 2>&1; then echo "hey, I"m safer now!" fi done < <(find "$ dir_work" -type f -print0) while IFS= read -r -d "" f; do echo "2" done < <(find "$dir_work" -type d -name "work" -print0)
Как вы можете видеть, переменная IFS устанавливается равным пуста, таким образом предотвращая read от обрезки переднего и заднего пространства из строки. команда read использует пустую строку (-d "") в качестве разделителя, чтобы читать до он достигает a \ 0. find должен быть m соответственно, поэтому он использует опцию -print0 , чтобы разграничить свои данные с помощью \ 0 вместо новой строки, что, что удивительно и злонамеренно, может быть частью имени файла. Разбиение такого файла на \ n на две части нарушит наш код.
Предыдущий ответ, в котором указано, что find ... | while read name; do ...; done следует использовать для считывания find s выход может быть также плохим. Цикл while выполняется в новой подоболочке со своей копией переменных, скопированной из родителя. Затем эта копия используется для всех, что вам нравится. Когда цикл while закончен, копия подоболочки будет отброшена, а исходные переменные родителя не будут изменены.
Если вы намереваетесь изменить некоторые переменные внутри этого цикла while и использовать их впоследствии в родительском, рассмотрите возможность использования более безопасного скрипта, который предотвратит потерю данных.
0
«Никогда не используйте Command Substitution, любого вида, без кавычек». Это просто nitpicking, но можно использовать подстановку команд без кавычек, когда вы устанавливаете переменную: "something = $ (basename" filename with spaces ")". - Smith John 22 апр. 13 2013-04-22 21:43:10
2
For i in $(find$ DIRWORK -type d -name work); do echo "2" done
будет первым выполнить эту линию
Find $DIRWORK -type d -name work
ждать, пока find не завершит выполнение, а затем взять выходной и положить его обратно в петлю for
For i in the output of find; do echo "2" done
только тогда цикл for начнет выполнение.
Так что если find занимает много времени, чтобы закончить цикл for , необходимо подождать много времени, прежде чем он сможет начать.
Попробуйте временные команды find в интерактивном режиме
$ time find $DIRWORK -type d -name work
и посмотреть, как долго это берет.
Также обратите внимание: вы не должны использовать for цикл для перебора имен файлов. Используйте while петлю с read , как это:
Find$ DIRWORK -type d -name work | while read name; do echo "2" done
Бонус: он выполняет цикл while параллельно с find . Это означает, что цикл while выполнит одну итерацию, как только find распечатает одну строку. Для завершения выполнения не нужно ждать find .
Основы 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"
А теперь результат работы скрипта:
[email protected]:~$ ./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 divisorDnd=$dividend #мы будем изменять переменные dividend и divisor,
#сохраним их знания в других переменных, т.к. они нам
#понадобятся
dvs=$divisor
remainder=1Until [ "$remainder" -eq 0 ]
do
let "remainder = dividend % divisor"
dividend=$divisor
divisor=$remainder
doneEcho "НОД чисел $dnd и $dvs = $dividend"
Результат выполнения скрипта:
[email protected]:~$ ./bash2_primer3.sh
Введите числитель:
100
Введите знаменатель:
90
НОД чисел 100 и 90 = 10
Математические операции
Команда let.Команда let производит арифметические операции над числами и переменными.
Рассмотрим небольшой пример, в котором мы производим некоторые вычисления над введенными числами:
#!/bin/bash
echo "Введите a: "
read a
echo "Введите b: "
read bLet "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 "
Результат выполнения:
[email protected]:~$ ./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 (абсолютно бесполезная операция:))) или
записать в файл listing содержание текущего каталога (уже полезней)
Если есть необходимость дописывать в файл(при использовании ">" он заменятеся), необходимо вместо ">" использовать ">>"
после просьбы sudo ввести пароль, он возьмется из файла my_password, как будто вы его ввели с клавиатуры.
Если необходимо записать в файл только ошибки, которые могли возникнуть при работе программы, то можно использовать:
./program_with_error 2> error_fileцифра 2 перед ">" означает что нужно перенаправлять все что попадет в дескриптор 2(stderr).
Если необходимо заставить stderr писать в stdout, то это можно можно след. образом:
символ "&" означает указатель на дескриптор 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 и Linux в целом. Критика, как водится приветствуется. Следующая статья возможно будет посвещена использованию таких программ как sed, awk.
Оболочка 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
Вот что получится после того, как этот цикл пройдётся по списку. Как видите, результат вполне ожидаем.
Перебор сложных значений
TNW-CUS-FMP - промо-код на 10% скидку на наши услуги, доступен для активации в течение 7 дней"
Инициализация цикла списком, полученным из результатов работы команды
Ещё один способ инициализации цикла for заключается в передаче ему списка, который является результатом работы некоей команды. Тут используется подстановка команд для их исполнения и получения результатов их работы. #!/bin/bash
file="myfile"
for var in $(cat $file)
do
echo " $var"
done
В этом примере задействована команда cat , которая читает содержимое файла. Полученный список значений передаётся в цикл и выводится на экран. Обратите внимание на то, что в файле, к которому мы обращаемся, содержится список слов, разделённых знаками перевода строки, пробелы при этом не используются.
Цикл, который перебирает содержимое файла
Тут надо учесть, что подобный подход, если ожидается построчная обработка данных, не сработает для файла более сложной структуры, в строках которого может содержаться по несколько слов, разделённых пробелами. Цикл будет обрабатывать отдельные слова, а не строки.
Что, если это совсем не то, что нужно?
Разделители полей
Причина вышеописанной особенности заключается в специальной переменной окружения, которая называется IFS (Internal Field Separator) и позволяет указывать разделители полей. По умолчанию оболочка 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 можно настроить так:
Обход файлов, содержащихся в директории
Один из самых распространённых вариантов использования циклов 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
В этом скрипте два цикла. Первый проходится по строкам, используя в качестве разделителя знак перевода строки. Внутренний занят разбором строк, поля которых разделены двоеточиями.
Обработка данных файла
Такой подход можно использовать при обработке файлов формата CSV, или любых подобных файлов, записывая, по мере надобности, в переменную окружения IFS символ-разделитель.
Управление циклами
Возможно, после входа в цикл, нужно будет остановить его при достижении переменной цикла определённого значения, которое не соответствует изначально заданному условию окончания цикла. Надо ли будет в такой ситуации дожидаться нормального завершения цикла? Нет конечно, и в подобных случаях пригодятся следующие две команды:- break
- continue
Команда break
Эта команда позволяет прервать выполнение цикла. Её можно использовать и для циклов for , и для циклов while: #!/bin/bash
for var1 in 1 2 3 4 5 6 7 8 9 10
do
if [ $var1 -eq 5 ]
then
break
fi
echo "Number: $var1"
done
Такой цикл, в обычных условиях, пройдётся по всему списку значений из списка. Однако, в нашем случае, его выполнение будет прервано, когда переменная $var1 будет равна 5.
Досрочный выход из цикла for
Вот - то же самое, но уже для цикла while:
#!/bin/bash
var1=1
while [ $var1 -lt 10 ]
do
if [ $var1 -eq 5 ]
then
break
fi
echo "Iteration: $var1"
var1=$(($var1 + 1))
done
Команда break , исполненная, когда значение $var1 станет равно 5, прерывает цикл. В консоль выведется то же самое, что и в предыдущем примере.
Команда continue
Когда в теле цикла встречается эта команда, текущая итерация завершается досрочно и начинается следующая, при этом выхода из цикла не происходит. Посмотрим на команду continue в цикле for: #!/bin/bash
for ((var1 = 1; var1 < 15; var1++))
do
if [ $var1 -gt 5 ] && [ $var1 -lt 10 ]
then
continue
fi
echo "Iteration number: $var1"
done
Когда условие внутри цикла выполняется, то есть, когда $var1 больше 5 и меньше 10, оболочка исполняет команду continue . Это приводит к пропуску оставшихся в теле цикла команд и переходу к следующей итерации.
Команда continue в цикле for
Обработка вывода, выполняемого в цикле
Данные, выводимые в цикле, можно обработать, либо перенаправив вывод, либо передав их в конвейер. Делается это с помощью добавления команд обработки вывода после инструкции done .Например, вместо того, чтобы показывать на экране то, что выводится в цикле, можно записать всё это в файл или передать ещё куда-нибудь:
#!/bin/bash
for ((a = 1; a < 10; a++))
do
echo "Number is $a"
done > myfile.txt
echo "finished."
Оболочка создаст файл myfile.txt и перенаправит в этот файл вывод конструкции for . Откроем файл и удостоверимся в том, что он содержит именно то, что ожидается.
Перенаправление вывода цикла в файл
Пример: поиск исполняемых файлов
Давайте воспользуемся тем, что мы уже разобрали, и напишем что-нибудь полезное. Например, если надо выяснить, какие именно исполняемые файлы доступны в системе, можно просканировать все папки, записанные в переменную окружения PATH . Весь арсенал средств, который для этого нужен, у нас уже есть, надо лишь собрать всё это воедино: #!/bin/bash
IFS=:
for folder in $PATH
do
echo "$folder:"
for file in $folder/*
do
if [ -x $file ]
then
echo " $file"
fi
done
done
Такой вот скрипт, небольшой и несложный, позволил получить список исполняемых файлов, хранящихся в папках из PATH .
Поиск исполняемых файлов в папках из переменной PATH
Итоги
Сегодня мы поговорили о циклах for и while в bash-скриптах, о том, как их запускать, как ими управлять. Теперь вы умеете обрабатывать в циклах строки с разными разделителями, знаете, как перенаправлять данные, выведенные в циклах, в файлы, как просматривать и анализировать содержимое директорий.Если предположить, что вы - разработчик bash-скриптов, который знает о них только то, что изложено в этого цикла статей, и в этой, второй, то вы уже вполне можете написать кое-что полезное. Впереди - третья часть, разобравшись с которой, вы узнаете, как передавать bash-скриптам параметры и ключи командной строки, и что с этим всем делать.
Внимание:
в посте спрятана выгода!
Циклы 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
Вот что получится после того, как этот цикл пройдётся по списку. Как видите, результат вполне ожидаем.
Перебор сложных значений
TNW-CUS-FMP - промо-код на 10% скидку на наши услуги, доступен для активации в течение 7 дней"
Инициализация цикла списком, полученным из результатов работы команды
Ещё один способ инициализации цикла 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 можно настроить так:
Обход файлов, содержащихся в директории
Один из самых распространённых вариантов использования циклов 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