Ци

Цикли у Bash

{one: "1 вправа", few: "%{count} вправи", many: "%{count} вправ", other: "%{count} вправи"}

Про концепцію Цикли

У Bash є дві форми циклів:

  • Повторювати якісь команди для кожного елемента зі списку елементів,
  • Повторювати якісь команди поки деяка умова є правдою (або неправдою).

Цикли for

Щоб перебрати список елементів, використовуємо цикл for.

for varname in words; do COMMANDS; done

words розгортається за допомогою розбиття на слова та розгортання імен файлів (про які ми дізналися в концепції лапок). Кожне слово по черзі присвоюється змінній varname, і виконуються COMMANDS.

Ось кілька прикладів, які показують, коли цикл for може стати в пригоді

  • Виконати якісь команди над групою файлів.

    # source all the bash library files in the current directory
    for file in ./*.bash; do
        source "$file"
    done
    
  • Виконати якусь команду для кожного з параметрів скрипта або функції.

    for arg in "$@"; do
        echo "argument: ${arg}"
    done
    
  • Виконати якусь команду для кожного слова, відокремленого пробільними символами, у вихідних даних команди.

    for i in $(seq 10); do
        echo "${i} squared is $((i * i))"
    done
    

    Далі ми побачимо кілька застережень щодо цього стилю.

Арифметичний цикл for

У Bash справді є арифметичний цикл. Дещо знайомим він буде тим, хто програмував іншими мовами.

for ((INITIALIZATION; CONDITION; INCREMENT)); do COMMANDS; done

Подвійні дужки в Bash позначають арифметичний контекст (докладніше про арифметику в Bash ми дізнаємося в одній із наступних концепцій). Наведений вище приклад із seq можна записати так

for ((i = 1; i <= 10; i++)); do
    echo "${i} squared is $((i * i))"
done

Коли не варто використовувати for

Не читаймо рядки за допомогою for.

Це антипатерн, який часто можна побачити: перебирання вихідних даних cat.

for line in $(cat some.file); do 
    do_something_with "$line"
done

Тут помилка з двох причин.

  1. Підстановка команди не взята в лапки, тому підпадає під розбиття на слова. Розбиття на слова типово ділить за пробільними символами, а не лише за символами нового рядка. Цей цикл for перебирає слова у файлі, а не рядки.
  2. Підстановка команди не взята в лапки, тому підпадає під розгортання імен файлів. Кожне слово у файлі буде зіставлено як шаблон.

Як ми дізналися з концепції лапок, розбиттям на слова можна керувати за допомогою змінної IFS, а розгортання імен файлів можна вимкнути. Але таке рішення зазвичай виявляється крихким. Для перебирання рядків файлу for не годиться. Ідіоматичний спосіб читати файл використовує цикл while.

Цикли while

Використовуємо while, щоб повторювати послідовність команд, поки деяка умова є правдою.

while CONDITION_COMMANDS; do COMMANDS; done

Як і з if (про це ми дізналися з концепції умовних конструкцій), для CONDITION_COMMANDS немає спеціального синтаксису. Статус виходу списку команд визначатиме «правду» чи «неправду».

Керування циклами

Команда break перериває цикл. Керування повертається до команди, що йде після завершувача done циклу.

Команда continue переходить до наступної ітерації циклу.

Читання рядків файлу

Як уже згадувалося, ідіоматичний спосіб читати файл використовує цикл while.

while IFS= read -r line; do
    do_something_with "$line"
done < some.file
  • Вміст файлу подається на вхід циклу за допомогою перенаправлення <.

  • Команда read повертає «правду», якщо може прочитати повний рядок зі своїх вхідних даних. Коли вхідні дані вичерпано, або якщо останній рядок закінчується без завершального символу нового рядка, read повертає «неправду».

    • По-справжньому ідіоматичний спосіб читати рядки файлу, навіть якщо останній рядок не закінчується символом нового рядка, такий:

      while IFS= read -r line || [[ -n "$line" ]]; do ...
      
  • Опція -r вказує read не замінювати послідовності зі зворотною скосою рискою: зворотна скоса риска є просто звичайним символом.

  • IFS= присвоює порожній рядок тексту змінній IFS лише на час виконання команди read. Це тимчасово вимикає розбиття на слова, щоб будь-які пробільні символи на початку чи в кінці вхідного рядка тексту зберігалися.

Until

Конструкція until повторює послідовність команд, поки деяка умова є неправдою. Цикл повторюється, поки умова не стане правдою.

until CONDITION_COMMANDS; do COMMANDS; done

until використовують рідко. Частіше використовують цикл «while not».

while ! CONDITION_COMANDS; do ...

Нескінченні цикли

Іноді нам потрібно, щоб цикл виконувався вічно. Ось два способи це зробити.

  1. цикл while з умовою, яка завжди має успішний статус виходу

    while true; do ...
    
  2. арифметичний цикл for з порожніми виразами

    for ((;;)); do ...
    

Do-While

Явної конструкції do-while немає, але того самого ефекту можна досягти так:

while true; do
    COMMANDS

    if END_CONDITION; then
        break
    fi
done
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці