Все, что вам нужно знать о Bash For Loops в Linux

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

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

Как работают скрипты Bash

Сценарий bash – это простой текстовый файл, содержащий серию команд, которые оболочка bash может читать и выполнять. Bash – это оболочка по умолчанию в macOS до Catalina и в большинстве дистрибутивов Linux.

Если вы никогда раньше не работали со сценарием оболочки, вам следует начать с самого простого случая. Это позволит вам отработать ключевые концепции, включая создание сценария и его выполнение.

Сначала создайте следующий файл в удобном месте (в идеале откройте терминал и сначала перейдите в нужный каталог):

 #!/bin/bash
echo "Hello, World"

Первая строка сообщает тому, что запускает эту программу, как ее запускать (то есть с помощью интерпретатора bash). Вторая – это просто команда, подобная любой другой, которую вы можете ввести в командной строке. Сохраните этот файл как hello_world.sh , затем:

 $ chmod +x hello_world.sh
$ ./hello_world.sh

Команда chmod в первой строке делает файл исполняемым, то есть его можно запустить, набрав его имя, как во второй строке.

Если вы видите слова «Hello, World», напечатанные в строке вашего терминала, значит, все работает должным образом.

Как работают циклы For

В общем программировании существует два основных типа цикла for: числовой и foreach . Числовой тип традиционно является наиболее распространенным, но при использовании bash обычно наоборот.

Числовые циклы for обычно фокусируются на одном целом числе, которое определяет, сколько итераций будет выполнено, например:

 for (i = 0; i < 100; i++) {
/* statements to execute repeatedly */
}

Это знакомый на вид цикл for, который будет повторяться ровно 100 раз, если только i не будет изменен внутри цикла или другой оператор не вызовет остановку выполнения цикла for.

Циклы Foreach, напротив, обычно работают со структурами, такими как списки или массивы, и повторяются для каждого элемента в этой коллекции:

 people = [ "Peter", "Paul", "Mary" ]
foreach (people as person) {
if (person == "Paul") {
...
}
}

Некоторые языки используют немного другой синтаксис, который меняет порядок сбора и элемента:

 people = [ "Peter", "Paul", "Mary" ]
for (person in people) {
if (person == "Paul") {
...
}
}

For in Loops

В bash чаще встречается цикл foreach или for in . Базовый синтаксис прост:

 for arg in [list]
do
/* statements to execute repeatedly */
/* the value of arg can be obtained using $arg */
done

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

 for file in one.c two.c three.c
do
ls "$file"
done

Если такие файлы существуют в текущем каталоге, вывод этого сценария будет:

 one.c
two.c
three.c

Вместо фиксированного набора файлов список можно получить с помощью шаблона глобуса (один включает подстановочные знаки – специальные символы, представляющие другие символы). В следующем примере цикл for выполняет итерацию по всем файлам (в текущем каталоге), имена которых заканчиваются на «.xml»:

 for file in *.xml
do
ls -l "$file"
done

Вот пример вывода:

 $ -rw-r--r-- 1 bobby staff 2436 3 Nov 2019 feed.xml
$ -rw-r--r-- 1 bobby staff 6447 27 Oct 16:24 sitemap.xml

Это может выглядеть очень скучно:

 $ ls -l *.xml

Но есть существенная разница: цикл for выполняет программу ls 2 раза, каждый раз передавая ему одно имя файла. В отдельном примере ls шаблон glob (* .xml) сначала сопоставляет имена файлов, а затем отправляет их все как отдельные параметры командной строки одному экземпляру ls .

Вот пример, в котором используется программа wc (подсчет слов), чтобы сделать разницу более очевидной:

 $ wc -l *.xml
44 feed.xml
231 sitemap.xml
275 total

Программа wc подсчитывает количество строк в каждом файле отдельно, а затем выводит общее количество строк по всем из них. Напротив, если wc работает в цикле for:

 for file in *.xml
do
wc -l $file
done

Вы по-прежнему будете видеть счетчик для каждого файла:

 44 feed.xml
231 sitemap.xml

Но общего итогового итога нет, потому что wc запускается изолированно при каждой итерации цикла.

Когда список не является списком

При работе с циклами for возникает очень простая и распространенная ошибка, связанная с тем, как bash обрабатывает аргументы / строки в кавычках. Перебирать список файлов нужно так:

 for file in one.c two.c

Не так:

 for file in "one.c two.c"

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

 FILES="one.c two.c"
for file in $FILES
do
...
done

Обратите внимание, что само объявление переменной должно заключать свое значение в двойные кавычки!

Без списка

Не имея ничего, что нужно перебирать, цикл for работает с любыми аргументами командной строки, которые были предоставлены сценарию при вызове. Например, если у вас есть сценарий с именем args.sh, содержащий следующее:

 #!/bin/sh
for a
do
echo $a
done

Затем выполнение args.sh даст вам следующее:

 $ ./args.sh one two three
one
two
three

Bash распознает этот случай и рассматривает do как эквивалент для в $ @ do, где $ @ – специальная переменная, представляющая аргументы командной строки.

Эмуляция традиционного числового цикла For

Сценарии Bash часто имеют дело со списками файлов или строками вывода других команд, поэтому часто используется цикл for in. Однако традиционная операция в стиле c по-прежнему поддерживается:

 for (( i=1; i<=5; i++ ))
do
echo $i
done

Это классическая форма из трех частей, в которой:

  1. переменная инициализируется (i = 1) при первом обнаружении цикла
  2. цикл продолжается, пока выполняется условие (i <= 5)
  3. каждый раз в цикле переменная увеличивается (i ++)

Итерация между двумя значениями является достаточно распространенным требованием, поэтому существует более короткая и менее запутанная альтернатива:

 for i in {1..5}
do
echo $i
done

Расширение фигурных скобок эффективно переводит приведенный выше цикл for в:

 for i in 1 2 3 4

Более точное управление циклом с прерыванием и продолжением

Более сложные циклы for часто нуждаются в способе преждевременного выхода или немедленного перезапуска основного цикла со следующим значением по очереди. Для этого bash заимствует операторы break и continue, которые распространены в других языках программирования. Вот пример, в котором оба используются для поиска первого файла длиной более 100 символов:

 #!/bin/bash
for file in *
do
if [ ! -f "$file" ]
then
echo "$file is not a file"
continue
fi
num_chars=$(wc -c < "$file")
echo $file is "$num_chars characters long"
if [ $num_chars -gt 100 ]
then
echo "Found $file"
break
fi
done

Цикл for здесь работает со всеми файлами в текущем каталоге. Если файл не является обычным файлом (например, если это каталог), оператор continue используется для перезапуска цикла со следующим файлом по очереди. Если это обычный файл, второй условный блок определит, содержит ли он более 100 символов. Если это так, оператор break используется для немедленного выхода из цикла for (и достижения конца скрипта).

Вывод

Сценарий bash – это файл, содержащий набор инструкций, которые могут быть выполнены. Цикл for позволяет многократно повторять часть скрипта. С помощью переменных, внешних команд и операторов break и continue сценарии bash могут применять более сложную логику и выполнять широкий спектр задач.