Главная >> Терминал >> Циклы Bash

Циклы 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:

vi for1.sh

!/bin/bash
for number in 1 2 3 4 5
do
echo $number
done

Дайте скрипту права на выполнение и запустите его:

chmod +x for1.sh
./for1.sh

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

!/bin/bash

for iface in $(ls /sys/class/net/)
do
echo $iface
done

Как вы уже поняли, этот цикл выводит список всех, подключенных к системе сетевых интерфейсов. Но в цикле вы можете не только их выводить, но и выполнять какие-либо действия.

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

for ((счетчик=1; счетчик < 10; счетчик++))
do
команда1
команда2
done

Этот цикл немного сложнее, но он позволяет сделать больше. С помощью такого цикла можно не только перебирать массивы, но и сделать необходимое количество повторений. Рассмотрим пример:

!/bin/bash
for ((i=1; i < 10; i++))
do
echo $i
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, выполняется набор команд, затем снова выполняется проверка, и так пока условие не вернет отрицательный результат. Это нам тоже нужно сделать вручную. Рассмотрим пример:

vi while.sh

!/bin/bash
x=1
while [ $x -lt 5 ]
do
echo "Значение счетчика: $x"
x=$(( $x + 1 ))
done

 

 

Здесь сначала мы устанавливаем значение счетчика в единицу, затем, в условии сравниваем его с 5, пока счетчик меньше пяти будут выполняться команды из блока do-done. Но еще в этом блоке нам нужно увеличивать значение счетчика на единицу, что мы и делаем.

Также с помощью while мы можем прочитать несколько строк из стандартного ввода:

vi while.sh

!/bin/bash
hile read line
do
echo $line
done

 

Программа будет запрашивать новые строки пока вы не передадите ей символ конца файла с помощью сочетания клавиш Ctrl+D. Бесконечный цикл while bash с помощью while еще проще организовать:

vi while.sh

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, как с ними работать и в каких ситуациях их можно использовать. Циклы могут стать отличным и незаменимым инструментом при создании сложных скриптов администрирования системы. Если у вас остались вопросы, спрашивайте в комментариях!

16 комментариев к “Циклы 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

      Ответить
  1. Наткнулся на такую ситуацию, не понял в чём проблема, а по поиску сильно в тему особо не находилось. Если переменная изменяется в цикле, получающем что-то из конвеера, после выхода цикла её значение останется, каким было до цикла. В итоге где-то подсказали, что в таком случае цикл запускается типа в дочернем потоке из которого нельзя вернуть значения переменных. Нужно обойтись без конвеера.
    Проблема:
    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 << EOF
    $(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]*\([0-9][0-9]*\)[ \t]*\(.*\)/\2\t\1/'

      Ответить
  2. Небольшой довесок к статье: циклом 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=''

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

      Ответить
  3. Удалить ненужные строки:
    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

    Ответить
  4. привет всем!
    я ещё совсем маленький олень..
    помогите чем могите, не догоняю как вставить дополнительное условие: 26 d_(){
    if [ "$1" ];
    cd "$1" && clear && echo " ${PWD##*/}/" && ls -l
    then [ "$2" ];
    cd "$2" && clear && echo " ${PWD}/" && ls -l
    else
    clear && echo " ${PWD##*/}" && ls -l
    fi
    }

    Ответить
  5. как вставить ещё одно условие if?
    26 d_(){
    27 if [ "$1" ];
    28 cd "$1" && clear && echo " ${PWD##*/}/" && ls -l
    29 then [ "$2" ];
    30 cd "$2" && clear && echo " ${PWD}/" && ls -l
    31 else
    32 clear && echo " ${PWD##*/}" && ls -l
    33 fi
    34 }

    Ответить
  6. дурак написал прогу для сообщений.
    тут можно было сделать детский мат - проверить строчки и вернуть их к первоначальному смыслу

    Ответить

Оставьте комментарий