Цикл по строкам файла: объяснение цикла for в bash

Я хочу выполнить цикл по строкам файла с помощью скрипта Bash, и один из способов сделать это — использовать цикл for.

Что такое цикл for?

Цикл for — одна из самых распространенных конструкций программирования, которая используется для выполнения заданного блока кода с заданным набором элементов в списке. Например, предположим, что вы хотите написать программу, которая выводит количество людей, проживающих в 10 крупнейших европейских городах. Программа может использовать цикл for для прохода по каждому городу в списке и вывода количества людей в этом городе.

Логика каждый раз одна и та же, меняется только город.

Ниже вы можете увидеть общий синтаксис цикла for в Bash:

for item in [LIST]
do
    command1
    command2
   ...
    commandN
done

СПИСОК может быть, например:

  • диапазон чисел.
  • последовательность строк, разделенных пробелами.
  • вывод команды Linux (например, команды ls).

Команды N между do и done выполняются для каждого элемента в списке.

Цикл For в Bash

В этой статье вы узнаете, как использовать цикл for в Bash и, в частности, как проходить по строкам файла.

Но зачем вам это делать? Просматривать строки файла?

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

В этом примере мы будем использовать простой файл.txt, в котором каждая строка содержит:

  • название города
  • количество людей, проживающих в этом городе.

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

Istanbul:15,067,724
Moscow:12,615,279
London:9,126,366
...  

Итак, как мы можем использовать цикл Bash for для просмотра содержимого этого файла?

Сначала мы сохраним имя файла в переменной

FILENAME="european-cities.txt"

После этого мы воспользуемся другой переменной и командой cat, чтобы получить все строки в файле:

LINES=$(cat $FILENAME)

Здесь мы используем подстановку команд, чтобы назначить вывод команды cat переменным LINES.

Наконец, цикл for позволяет пройтись по каждой строке файла:

for LINE in $LINES
do
    echo "$LINE"
done

Do и done используются для определения команд, которые будут выполняться на каждой итерации цикла for.

Например, если у вас есть файл с 10 строками, цикл for выполнит 10 итераций, и на каждой итерации он будет считывать одну строку файла.

Команду echo можно заменить любой последовательностью команд в зависимости от того, что вы хотите сделать с каждой строкой в ​​файле.

Вот окончательный вариант сценария:

#!/bin/bash
  
FILENAME="european-cities.txt"

LINES=$(cat $FILENAME)

for LINE in $LINES
do
    echo "$LINE"
done

И результат работы скрипта…

./cities.sh 
Istanbul:15,067,724
Moscow:12,615,279
London:9,126,366
Saint-Petersburg:5,383,890
Berlin:3,748,148
Kyiv:3,703,100
Madrid:3,223,334
Rome:2,857,321
Paris:2,140,526
Bucharest:2,106,144

Мы передаем список в цикл for с помощью команды cat.

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

Есть ли у вас на примете какие-либо другие возможные команды?

Кроме того, цикл for — не единственный вариант создания цикла в скрипте Bash, другим вариантом является цикл while.

Что такое счетчик в цикле For в Bash?

В цикле for вы также можете определить переменную, называемую counter. Вы можете использовать счетчик для отслеживания каждой итерации цикла.

Использование счетчика очень распространено во всех языках программирования. Его также можно использовать для доступа к элементам структуры данных внутри цикла (в нашем примере это не так).

Давайте изменим предыдущую программу и определим счетчик, значение которого выводится на каждой итерации:

#!/bin/bash
  
FILENAME="european-cities.txt"

LINES=$(cat $FILENAME)
COUNTER=0

for LINE in $LINES
do
    echo "Counter $COUNTER: $LINE"
    COUNTER=$((COUNTER+1))
done

Как вы можете видеть, я определил переменную COUNTER вне цикла for, присвоив ей начальное значение 0.

Затем на каждой итерации я печатаю значение счетчика вместе со строкой из файла.

После этого я использую арифметический оператор Bash, чтобы увеличить значение переменной COUNTER на 1.

А вот и результат работы скрипта:

Counter 0: Istanbul:15,067,724
Counter 1: Moscow:12,615,279
Counter 2: London:9,126,366
Counter 3: Saint-Petersburg:5,383,890
Counter 4: Berlin:3,748,148
Counter 5: Kyiv:3,703,100
Counter 6: Madrid:3,223,334
Counter 7: Rome:2,857,321
Counter 8: Paris:2,140,526
Counter 9: Bucharest:2,106,144

Break и Continue в цикле Bash For

Существуют способы изменить обычный ход выполнения цикла for в Bash.

Два оператора, которые позволяют это сделать, — break и continue:

  • break: прерывает выполнение цикла for и переходит на первую строку после цикла for.
  • continue: переходит к следующей итерации цикла for.

Определение счетчика помогает нам увидеть, что произойдет, если мы добавим break или continue к нашему существующему скрипту.

Начнем с перерыва…

Я добавлю оператор if, основанный на значении счетчика. Оператор break внутри if прерывает выполнение цикла, если счетчик равен 3:

#!/bin/bash
  
FILENAME="european-cities.txt"

LINES=$(cat $FILENAME)
COUNTER=0

for LINE in $LINES
do
    if [ $COUNTER -eq 3 ]; then
        break
    fi

    echo "Counter $COUNTER: $LINE"
    COUNTER=$((COUNTER+1))
done

И вот что получается:

Counter 0: Istanbul:15,067,724
Counter 1: Moscow:12,615,279
Counter 2: London:9,126,366

Как вы можете видеть, оператор break останавливает выполнение цикла for до достижения команды echo, поскольку COUNTER равен 3.

После этого давайте заменим break на continue и посмотрим, что получится. Остальной код я оставлю без изменений.

#!/bin/bash
  
FILENAME="european-cities.txt"

LINES=$(cat $FILENAME)
COUNTER=0

for LINE in $LINES
do
    if [ $COUNTER -eq 3 ]; then
        continue
    fi

    echo "Counter $COUNTER: $LINE"
    COUNTER=$((COUNTER+1))
done

А вот и результат работы скрипта:

Counter 0: Istanbul:15,067,724
Counter 1: Moscow:12,615,279
Counter 2: London:9,126,366

Странно…выход тот же. Почему?

Это происходит потому, что когда значение COUNTER равно 3, оператор continue переходит к следующей итерации цикла, но не увеличивает значение счетчика.

Таким образом, на следующей итерации значение СЧЕТЧИКА по-прежнему равно 3, и оператор continue выполняется снова, и так далее для всех остальных итераций.

Чтобы исправить это, нам нужно увеличить значение переменной COUNTER внутри оператора if:

#!/bin/bash
  
FILENAME="european-cities.txt"

LINES=$(cat $FILENAME)
COUNTER=0

for LINE in $LINES
do
    if [ $COUNTER -eq 3 ]; then
        COUNTER=$((COUNTER+1))
        continue
    fi

    echo "Counter $COUNTER: $LINE"
    COUNTER=$((COUNTER+1))
done

На этот раз мы видим правильный вывод:

Counter 0: Istanbul:15,067,724
Counter 1: Moscow:12,615,279
Counter 2: London:9,126,366
Counter 4: Berlin:3,748,148
Counter 5: Kyiv:3,703,100
Counter 6: Madrid:3,223,334
Counter 7: Rome:2,857,321
Counter 8: Paris:2,140,526
Counter 9: Bucharest:2,106,144

Как видите, «Счетчик 3: …» не выводится в терминале.

Написание цикла for в одну строку

Прежде чем закончить этот урок, давайте посмотрим, как можно написать цикл for в одну строку.

Это не рекомендуемая практика, поскольку она делает ваш код менее читаемым.

Но полезно знать, как написать цикл в одну строку, это углубит ваши знания Bash.

Общий синтаксис для цикла Bash for в одну строку выглядит следующим образом:

for i in [LIST]; do [COMMAND]; done

Давайте распечатаем содержимое нашего текстового файла с помощью однострочного цикла:

#!/bin/bash

FILENAME="european-cities.txt"
LINES=$(cat $FILENAME)

for LINE in $LINES; do echo $LINE; done

Для упрощения я удалил COUNTER и оператор if. Если бы они были, цикл for в одну строку было бы намного сложнее читать.

Постарайтесь избегать однострочного кода, если он затрудняет чтение кода.

Заключение

В заключение следует отметить, что в этом уроке вы узнали, как:

  1. Сохраните строки файла в переменной
  2. Используйте цикл for для прохода по каждой строке.
  3. Используйте счетчик в цикле for.
  4. Измените ход выполнения цикла с помощью break и continue.
  5. Напишите цикл for в одну строку.