Циклы Bash
В этой статье мы продолжим наш цикл про написание скриптов на Bash. Мы уже рассмотрели как работать с архивами и создавать функции, но этого еще недостаточно. Любой уважающий себя язык программирования должен содержать циклы. Цикл — это такая последовательность, которая позволяет выполнять определенный участок кода необходимое количество раз.
С помощью циклов вы можете очень сильно сократить количество строк кода, которые необходимо написать для однотипных операций. В этой статье мы рассмотрим что такое циклы Bash, как их создавать и использовать.
Циклы Bash
Как я уже сказал, циклы позволяют выполнять один и тот же участок кода необходимое количество раз. В большинстве языков программирования существует несколько типов циклов. Большинство из них поддерживаются оболочкой Bash. Мы рассмотрим их все в сегодняшней статье, но сначала поговорим какими они бывают:
- for — позволяет перебрать все элементы из массива или использует переменную-счетчик для определения количества повторений;
- while — цикл выполняется пока условие истинно;
- until — цикл выполняется пока условие ложно.
Циклы Bash, это очень полезная вещь и разобраться с ними будет несложно. Bash позволяет использовать циклы как в скриптах, так и непосредственно в командной оболочке. Дальше мы рассмотрим каждый из этих видов циклов.
Цикл for
Цикл for bash применяется очень часто из-за своей специфики. Его проще всего использовать когда вы знаете сколько раз нужно повторить операцию или вам нужно просто обработать по очереди все элементы массива и вы не хотите контролировать количество повторений.
Цикл for имеет несколько синтаксисов и может вести себя по разному. Для перебора элементов списка удобно использовать такой синтаксис:
for переменная in список
do
команда1
команда2
done
Каждый цикл for независимо от типа начинается с ключевого слова for. Дальше все зависит от типа. В этом случае после for указывается имя переменной, в которую будет сохранен каждый элемент списка, затем идет ключевое слово in и сам список. Команды, которые нужно выполнять в цикле размещаются между словами do и done.
Проверим все на практике и напишем небольшой скрипт, который будет выводить номера от 1 до 5:
!/bin/bash
for number in 1 2 3 4 5
do
echo $number
done
Дайте скрипту права на выполнение и запустите его:
Вы увидите, что все выполняется так, как и предполагалось. Программа выведет цифры от 1 до 5, которые мы перечислили в массиве. Вы можете передать циклу любой массив, например, вывод какой-либо другой команды:
for iface in $(ls /sys/class/net/)
do
echo $iface
done
Как вы уже поняли, этот цикл выводит список всех, подключенных к системе сетевых интерфейсов. Но в цикле вы можете не только их выводить, но и выполнять какие-либо действия.
Следующий тип цикла for похож на его реализацию в языках программирования Си и С++. Он состоит из трех частей, инициализатора счетчика, условия продолжения выполнения и действия над счетчиком. Вот синтаксис:
for (( счетчик = 1 ; счетчик < 10 ; счетчик ++ ))
do
команда1
команда2
done
Этот цикл немного сложнее, но он позволяет сделать больше. С помощью такого цикла можно не только перебирать массивы, но и сделать необходимое количество повторений. Рассмотрим пример:
Результат странный, правда? Обратите внимание, как выполняется проверка условия. Значение счетчика сравнивается с эталоном. Действие с переменной счетчиком выполняется сразу же после завершения основного блока команд, а уже потом делается сравнение. Таким образом, у нас при первом выполнении i равно 1, а после него уже два 2. Теперь к завершению девятого прохода значение счетчика будет увеличено на единицу и станет 10. Условие сравнения 10 < 10 не верно, поэтому цикл дальше не выполняется.
С помощью этого же синтаксиса можно сделать бесконечные циклы bash linux:
!/bin/bash
for ((;;))
do
echo «Бесконечный цикл, нажмите CTRL+C для выхода»
done
Если необходимо, вы можете выйти из цикла преждевременно. Но так делать не рекомендуется. Для выхода используйте команду break:
!/bin/bash
for (i=1;i <10;i++)
do
echo Значение счетчика $i
if [[ i -gt 5]]
break
fi
done
Со циклами for мы завершили, теперь вы знаете как они выглядят и что с ними делать. Дальше мы рассмотрим циклы while и until. Они намного проще и более универсальны.
Цикл While
Суть цикла While в том, что он будет повторяться до тех пор, пока будет выполняться условие, указанное в объявлении цикла. Здесь нет никаких счетчиков и если они вам нужны, то организовывать их вам придется самим. Bash цикл while имеет такой синтаксис:
while [ условие ]
do
команда1
команда2
команда3
done
Это работает так: сначала выполняется проверка на правильность условия, если true, выполняется набор команд, затем снова выполняется проверка, и так пока условие не вернет отрицательный результат. Это нам тоже нужно сделать вручную. Рассмотрим пример:
!/bin/bash
x=1
while [ $x -lt 5 ]
do
echo «Значение счетчика: $x»
x=$(( $x + 1 ))
done
Здесь сначала мы устанавливаем значение счетчика в единицу, затем, в условии сравниваем его с 5, пока счетчик меньше пяти будут выполняться команды из блока do-done. Но еще в этом блоке нам нужно увеличивать значение счетчика на единицу, что мы и делаем.
Также с помощью while мы можем прочитать несколько строк из стандартного ввода:
!/bin/bash
hile read line
do
echo $line
done
Программа будет запрашивать новые строки пока вы не передадите ей символ конца файла с помощью сочетания клавиш Ctrl+D. Бесконечный цикл while bash с помощью while еще проще организовать:
while :
do
echo «Бесконечный цикл bash, для выхода нажмите Ctrl+C»
done
Цикл until
Нам осталось рассмотреть последний цикл. Это цикл until. Он очень похож на while и отличается от него только работой условия. Если в первом нужно чтобы условие всегда было истинным, то здесь все наоборот. Цикл будет выполняться пока условие неверно. Синтаксис:
until [ условие ]
do
команда1
команда2
done
Думаю после рассмотрения реального примера со счетчиком будет намного понятнее как это работает:
!/bin/bash
count=1
until [ $count -gt 10 ]
do
echo «Значение счетчика: $count»
count=$(( $count + 1 ))
done
Мы задаем значение счетчика 1 и увеличиваем его на единицу при каждом проходе. Как только условие счетчик больше 10 выполнится, сразу цикл будет остановлен. В циклах while и until тоже можно использовать команды break и continue для выхода из цикла и завершения текущего прохода, но мы не будем это очень подробно рассматривать.
Выводы
В этой статье мы рассмотрели циклы Bash, как с ними работать и в каких ситуациях их можно использовать. Циклы могут стать отличным и незаменимым инструментом при создании сложных скриптов администрирования системы. Если у вас остались вопросы, спрашивайте в комментариях!
Обнаружили ошибку в тексте? Сообщите мне об этом. Выделите текст с ошибкой и нажмите Ctrl+Enter.
Похожие записи
Оцените статью
Об авторе
Основатель и администратор сайта losst.ru, увлекаюсь открытым программным обеспечением и операционной системой Linux. В качестве основной ОС сейчас использую Ubuntu. Кроме Linux, интересуюсь всем, что связано с информационными технологиями и современной наукой.
11 комментариев к “Циклы Bash”
Да тут всем похер.
3 (три) года прошло, а ошибка все еще на месте
Комменты никто не читает
з.ы. ребята с гигбрейнс тут? Ответить
cat ./’текстовый файл’ | sed ‘/^ненужная строка$/d’
Если ненужных строк много — то можно создать список не нужных строк и крутануть его через for, но, если есть пробельные символы (пробел или табуляция) в строках, то есть нюансы:
1. в sed подставльть можно так: sed ‘/^'»$string»‘/d’
2. будет трабла в выводе for, но об этом напишу в коментарии ниже, как добавление к этой замечательной статье
Если в списке ненужных строк будет встречаться символ косой черты / — будет трабла с sed, как вариант рещения — подготовка строки для sed: echo «$string» | sed ‘s/\//\\\//g’ или echo «$string» | sed ‘s!/!\\/!g’ Ответить
Что-то типа:
#!/bin/bash
del_table # сюда пишем ненужные строки, например считаем файл: del_table=$(cat ./file_string)
my_text # сюда пишем свой исходный текст, например считаем файл: del_table=$(cat ./file_text)
IFS=$’\n’
for string in $del_table
do
string=$(echo «$string» | sed ‘s/\//\\\//g’)
my_text=$(echo «$my_text» | sed ‘/^'»$string»‘$/d’)
done
echo «$my_text»
exit 0 Ответить
Наткнулся на такую ситуацию, не понял в чём проблема, а по поиску сильно в тему особо не находилось. Если переменная изменяется в цикле, получающем что-то из конвеера, после выхода цикла её значение останется, каким было до цикла. В итоге где-то подсказали, что в таком случае цикл запускается типа в дочернем потоке из которого нельзя вернуть значения переменных. Нужно обойтись без конвеера.
Проблема:
cd \
kkk=0
ls -1 | while read sss
do
echo -n «$sss »
let «kkk+=1»
echo -e «\t$kkk»
done
echo -e «\n$kkk»
# $kkk после цикла таки — 0 Ожидаемая работа:
cd /
kkk=0
while read sss
do
echo -n «$sss»
let «kkk+=1»
echo -e «\t$kkk»
done $(ls -1)
EOF
echo -e «\n$kkk» Ответить
Да и цикл у Вас работает в другом подпроцессе, и read. но вся трабла в read — уберите его и будет Вам фэншуй 😉 а цикл сразу же вернёт то, что надо.
Варианты:
1. Вариант с циклом без read ():
cd /
kkk=0
IFS=$’\n’
for sss in `ls -1`
do echo -e «$sss\t$(( kkk++ ))»
done
2. Вариант без цикла с cat (самый быстрый в работе):
cd /
ls -1 | cat -n
Чтоб точно соответствовало вашему выводу:
ls -1 ~ | cat -n | sed ‘s/^[ \t]*\(66*\)[ \t]*\(.*\)/\2\t\1/’ Ответить
Небольшой довесок к статье: циклом for можно управлять, т.е. если мы на вход пододим несколько строк с пробелами, то for сделает нам каку, т.к. по умолчанию, как разделитель, используются и пробельные символы, и символы окончания строки.
1. В некоторых случаях это просто обходится, например, подаём нашему скрипту аргументы с пробелами, здесь нет символа окончания строки, поэтому всё просто:
for arg in «$@»
do echo «\»$arg\»»
done
Если подали (назовём скрипт test), например: ./test ‘привет losst’ ‘:)’, то получим две строки:
«привет losst»
«:)»
А если бы было без кавычек — for arg in $@, то получили бы двух входящих, три выходящие строки:
«привет»
«losst»
«:)»
2. Если мы будем использовать ещё и символы окончания строки, например, отрывок вывода ls -1 ~:
Общедоступные
Рабочий стол
то for снова «некорректно» отработает и при наборе for string in `ls -1 ~`; do echo «\»$string\»»; done, выдаст:
«Общедоступные»
«Рабочий»
«стол»
А при убирании в кавычки — for string in «`ls -1 ~`»; do echo «\»$string\»»; done, вообще одну строку:
«Общедоступные
Рабочий стол»
Решение: есть переменная окружения IFS, которая управляет разделитялями в BASH, т.е. исходя из примера выше, просто прикажем циклу for использовать в качестве разделителя только символ окончания строки — IFS=$’\n’; for string in `ls -1 ~`; do echo «\»$string\»»; done и теперь, неиспользуя кавычки, мы получаем заведома нужное:
«Общедоступные»
«Рабочий стол»
Если вдруг будете писать это не на bash, а на голом sh, то получиться, что IFS=» Ответить
Спасибо за полезный комментарий. Пригодилось, когда надо было выводить строку, которая содержит в качестве разделителя табуляцию между полями. Ответить
Удалить ненужные строки:
ls /sys/class/net/|egrep -vx «$(echo -e «^eno\n^lo»|paste -sd «|»)»
Вместо ls /sys/class/net/ — пишешь cat «исходный файл»
Вместо echo -e «^eno\n^lo» — cat «файл с ненужными строками»
Если «файл с фрагментами ненужных строк» (то есть, надо удалить строки, СОДЕРЖАЩИЕ подстроки, а не полностью совпадающие), то вместо egrep -vx надо писать egrep -v Ответить
do. while or do. until in POSIX shell script
There is the well known while condition; do . ; done loop, but is there a do. while style loop that guarantees at least one execution of the block?
Many thanks Shawn, I didn’t expect the answer to become the selected answer so fast. So: thanks for the selection.
2 Answers 2
A very versatile version of a do . while has this structure:
#i=16 while echo "this command is executed at least once $i" : $ # capture the starting value of i # some other commands # needed for the loop (( ++i < 20 )) # Place the loop ending test here. do :; done echo "Final value of $i///$start" echo "The loop was executed $(( i - start )) times "
As it is (no value set for i ) the loop executes 20 times.
UN-Commenting the line that sets i to 16 i=16 , the loop is executed 4 times.
For i=16 , i=17 , i=18 and i=19 .
If i is set to (let's say 26) at the same point (the start), the commands still get executed the first time (until the loop break command is tested).
The test for a while should be truthy (exit status of 0).
The test should be reversed for an until loop, i.e.: be falsy (exit status not 0).
A POSIX version needs several elements changed to work:
i=16 while echo "this command is executed at least once $i" : $ # capture the starting value of i # some other commands # needed for the loop i="$((i+1))" # increment the variable of the loop. [ "$i" -lt 20 ] # test the limit of the loop. do :; done echo "Final value of $i///$start" echo "The loop was executed $(( i - start )) times "
./script.sh this command is executed at least once 16 this command is executed at least once 17 this command is executed at least once 18 this command is executed at least once 19 Final value of 20///16 The loop was executed 4 times