Циклы в bash скриптах. Основы Bash: циклы. Инициализация цикла списком, полученным из результатов работы команды

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

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

Цикл for предназначен для повторения действий до тех пор, пока они все не будут выполнены. Представьте, например, что у вас есть каталог с изображениями, и вам необходимо преобразовать их из одного формата в другой. Вы можете использовать цикл for совместно с программой convert из пакета ImageMagick (или какой-либо другой программой), например, для того, чтобы преобразовать изображения из формата JPEG в формат PNG. Или, например, вам может понадобиться преобразовать множество звуковых файлов из MP3 в OGG Vorbis .

Цикл while используется для повторения действий пока выполняется (является истинным ) какое-то условие. Цикл until работает несколько по другому: он выполняет действие до тех пор , пока не выполнится условие. Так, например, вы можете счётчик и выполнять действие до тех пор , пока его значение не достигнет 10. Рассмотрим это более подробно на примерах.

Начнём с цикла for . Его формат таков:

For i in $(command); do command $i; done

Если вы используете цикл for в скрипте, лучше отформатировать его так:

#!/bin/bash for i in $(command); do command $i done

Так, например, если вам нужно сделать резервные копии всех HTML-файлов, находящихся в каталоге, вы можете использовать следующую команду:

For i in $(ls *html); do cp $i $i.bak; done

Здесь создаётся локальная переменная $i , выполняется команда ls *html , результаты выполнения которой и будут являться данными, инициализирующими значение переменной $i при каждой итерации цикла (в нашем примере это будет список файлов, возвращаемый командой ls , по одному за каждую итерацию). Далее, выполняется команда cp , которой среди параметров передаётся переменная $i .

Кто-то может спросить, обязательно ли использовать букву «i» в качестве имени переменной? Нет. Вы можете использовать любое корректное для Bash имя переменной. Конечно же, в сценариях лучше использовать более осмысленные имена переменных, вроде $input или $html .

Я привёл очень краткий и простой пример использования цикла for . Вместо команды, выполняющейся в блоке do ,используйте echo для того, чтобы увидеть параметры, передаваемые ей. Это очень полезная практика на стадии тестирования скриптов, а также хороший способ помочь вам более подробно разобраться с работой for .

while и until

Рассмотрим теперь конструкции while и until . Также, мы немного воспользуемся условными выражениями bash . В нашем примере мы будем использовать их для того, чтобы определять, например, является ли значение переменной большим или меньшим, чем число X; существует ли файл и является ли он каталогом. Вы также можете использовать условные выражения для определения, например, доступности файла для чтения или присутствия в его правах доступа GID-бита.

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

#!/bin/bash i=0 while [ $i -lt 22 ] do touch $i i=$[$i+1] done

Этот скрипт создаст 22 файла с именами от 0 до 21. Цикл будет работать до тех пор, пока значение переменной $i меньше (-lt ) 22.

Теперь давайте избавимся от созданных файлов при помощи цикла until :

#!/bin/bash i=0 until [ $i -eq 22 ] do rm $i i=$[$i+1] done

Здесь мы заменили while на until , а в условном выражении заменили «меньше» (-lt ) на «равно» (-eq ). Таким образом, наш скрипт будет работать до тех пор, пока значение $i не достигнет 22. И вместо touch мы использовали rm , чтобы удалять файлы, а не создавать их. Просто, не так ли?

Основы 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 (абсолютно бесполезная операция:))) или
записать в файл 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.

Цикл for немного отличается от аналогов в других языках программирования. Прежде всего, он предоставляет Вам возможность выполнять последовательные действия над "словами" в строке.

Цикл while выполняет кусок кода, если тестируемым выражением является истина; и останавливается при условии, если им является ложь (или внутри исполняемого кода встречается явно заданное прерывание цикла).

Цикл until практически идентичен циклу while. Отличие заключается только в том, что код выполняется при условии, если проверяемым выражением является ложь.

Если Вы предполагаете, что while и until очень похожи, Вы правы.

7.1 Пример цикла for

#!/bin/bash for i in $(ls); do echo item: $i done

Во второй строке мы представляем i в качестве переменной, которая получает различные значения, содержащиеся в $(ls).

При необходимости третья строка могла бы быть длиннее; или там могло бы находиться несколько строк перед done (4-я строка).

"done" (4-я строка) показывает, что код, в котором используется значение $i, заканчивается и $i получает новое значение.

Данный скрипт не предполагает большой важности. Более полезным применением цикла for было бы использование его для отбора только каких-то определённых файлов в предыдущем примере.

7.2 C-подобный for

fiesh предложил добавить эту форму цикла. Это цикл for, наиболее похожий на for в языках C, Perl и т.п.

#!/bin/bash for i in `seq 1 10`; do echo $i done

7.3 Пример цикла while:

#!/bin/bash COUNTER=0 while [ $COUNTER -lt 10 ]; do echo The counter is $COUNTER let COUNTER=COUNTER+1 done

Данный скрипт "эмулирует" широко известную (в языках C, Pascal, perl и т.д.) структуру "for".

Циклы это крайне удобная штука при написании любых программ или скриптов, скорее даже необходимая. Они позволяют нам выполнять некоторый участок кода заданное количество раз. Естественно в bash есть несколько видов циклов. Мы опишем циклы for in, for, while, until . Хотя for in и for считаются разным синтаксисом одного оператора, на мой взгляд они отличаются друг от друга больше чем while от until.

Цикл со счетчиком for in :

Цикл for in это цикл со счётчиком. Блок кода находящийся в теле цикла повторяется столько раз, сколько значений содержится в списке оператора for in при том, при каждом повторе переменная счётчика (тут она названа var, но естественно можно называть её как угодно) имеет значение следующего элемента списка.
Если ключевое слово do находится в одной строке со словом for, то после списка аргументов (перед do) необходимо ставить точку с запятой.
Каждый из элементов <список> может содержать несколько аргументов. Это бывает полезным при обработке групп параметров. В этом случае, для принудительного разбора каждого из аргументов в <списке>, необходимо использовать инструкцию set
В качестве списка, в цикле for, можно использовать переменную.
В <списке> цикла for могут быть использованы имена файлов, которые в свою очередь могут содержать символы-шаблоны. Это может очень пригодится при работе с большим количеством файлов.
Если <список> в цикле for не задан, то в качестве оного используется переменная $@ - список аргументов командной строки.
При создании списка аргументов, в цикле for можно использовать подстановку команд.
Вывод цикла может быть перенаправлен со stdout в файл или куда нибудь ещё (подробнее это можно узнать разбирая перенаправление ввода-вывода).

Синтаксис:
for var in <список>
do
<выполняемые команды>
done

Пример:
for names in name1 name2 name3 name4
do
echo $names
done

Оператор цикла for имеет ещё один способ записи - очень похожий на синтаксис оператора for в языке C. В этом случае при инициализации счётчиков задаются начальные значения переменных или одной переменной и после каждого прохода цикла проверяется условие, если проверка возвращает истину, то начинается следующий проход цикла. В блоке <приращение счётчиков> значение наших переменных счётчиков обязательно должны изменятся (не обязательно в большую сторону) так чтобы при проверки условия рано или поздно мы получили значение лож, иначе цикл никогда не закончится. Очень удобный и главное привычный вариант, в случае если какую либо операцию нужно повторить заданное количество раз.

С подобный синтаксис:
for ((<инициализация счётчиков>; <проверка условия>; <приращение счётчиков>))
do
<выполняемые команды>
done

Пример:
for ((var=1; var <= LIMIT ; var++))
do
echo $var
done

Цикл while:

Это достаточно простая конструкция, которая проверяет условие стоящее за оператором while и в случае истинности этого условия выполняет блок команд находящийся между словами do и done и затем опять переходит к проверки условия. В случае если проверка вернет лож, то цикл заканчивается и начинаю выполнятся команды следующие за done . Нужно обязательно следить за тем чтобы <проверка условия> зависела от кода выполняющегося в цикле иначе, если результат проверки не изменяется вы получите бесконечный цикл.
Стандартное устройство ввода, для цикла while, можно перенаправить на файл с помощью команды перенаправления < в конце цикла.

Синтаксис:
while <Проверка условия>
do
<Блок команд, обязательно меняющий переменные влияющие на проверку условия>
done

Пример:
while [ $var0 -eq 100 ]
do
echo $var
var++
done

Оператор while может иметь несколько условий. Но только последнее из них определяет возможность продолжения цикла. В этом случае синтаксис оператора цикла будет отличатся от обычного.
Синтаксис (Ещё раз повторюсь, что на выполнение цикла влияет только последнее условие):
while
<условие1>
<условие2>

<условиеN>
do
<выполняемые команды - тело цикла>
done

Цикл until:

Оператор until очень похож на while, он тоже вычисляет условие, но выполняет тело цикла в случае если результатом вычисления будет лож. Может показаться непривычным, но until вычисляет условие перед первым проходом цикла, как и while, а не после него. Как и в случае с циклами for/in, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ ";" перед do.
Как и в предыдущем случае важно помнить что условие должно зависеть от операций в теле цикла, иначе наш скрипт никогда не завершится.

Синтаксис:
until <Проверка условия>
do
<Блок команд, обязательно меняющий переменные влияющие на проверку условия>
done

Пример:
until [ $var0 -gt 100] # Проверка условия производится в начале итерации.
do
echo $var
var--
done

Наверное пока хватит. :)

  • Назад
  • Вперёд

Новые статьи:

  • Не включается сетевое обнаружение в Windows 7/8/2008/2012
  • Ошибка: This application failed to start because it could not find or load the Qt platform plugin «windows».
  • Настройка автоматического перезапуска рабочих процессов rphost.exe сервера 1С 8.3
  • Как уменьшить размер лога транзакций (.ldf) в MS SQL 2008/20012

    MS SQL как любая порядочная СУБД промышленного назначения вместе с базой данных ведёт логи транзакция, которые позволяют откатывать состояние...

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 .