User Tools

Site Tools


bash:цикл_for

bash Управление потоком выполнения: цикл for

Цикл for отличается от циклов while и until поддержкой средств обработки последовательностей. Это очень полезная возможность. Как следствие, цикл for пользуется большой популярностью среди создателей сценариев для bash.

Цикл for реализован, что вполне естественно, в виде команды for. В современных версиях bash поддерживается две формы команды for.

for: традиционная форма

Оригинальный синтаксис команды for имеет следующий вид:

for переменная [in слова]; do
 команды
done

где переменная – это имя переменной, значение которой будет увеличиваться в ходе выполнения цикла, слова — необязательный список элементов, которые последовательно будут присваиваться переменной, и команды – это команды, выполняемые в каждой итерации.

Команду for удобно использовать в командной строке. Рассмотрим, как она работает:

for i in A B C D; do echo $i; done
A
B
C
D

В этом примере команда for получает список из четырех слов: A, B, C и D. Для обхода этого списка выполняется четыре итерации цикла. В начале каждой итерации переменной i присваивается очередное слово. Внутри цикла находится команда echo, она выводит значение i, чтобы показать, что присваивание действительно выполняется. Так же как в случае с циклами while и until, цикл for заканчивается ключевым словом done.

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

for i in {A..D}; do echo $i; done
A
B
C
D

или подстановку имен файлов:

for i in distros*.txt; do echo "$i"; done
 
distros-by-date.txt
distros-date.txt
distros-dates.txt
distros-key-names.txt
distros-key-vernum.txt
distros-keyvernum.txt
distros-name.txt
distros-vernums.txt
distros-versions.txt
distros.txt

Механизм подстановки имен файлов позволяет получить четкий список с именами файлов, который можно обработать в цикле. Единственное, о чем следует позаботиться, – убедиться, что механизм подстановки вернул действительные совпадения. По умолчанию, если операция подстановки не найдет ни одного файла, соответствующего шаблону, она вернет сам шаблон (в данном случае <html>distros*.txt</html>). Чтобы защититься от этой проблемы, предыдущий код можно преобразовать в сценарий, как показано ниже:

for i in distros*.txt; do
    if [[ -e "$i" ]]; then
        echo "$i"
    fi
done

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

#!/bin/bash
 
# longest-word : поиск самой длинной строки в файле
 
while [[ -n "$1" ]]; do
        if [[ -r "$1" ]]; then
                max_word=
                max_len=0
                for i in $(strings "$1"); do
                        len="$(echo "$i" | wc -c)"
                        if (( len > max_len )); then
                                max_len="$len"
                                max_word="$i"
                        fi
                done
                echo "$1: '$max_word' ($max_len characters)"
        fi
        shift
done

Этот пример осуществляет поиск самой длинной строки в файле. Когда в командной строке указано несколько имен файлов, сценарий вызывает процедуру strings (входит в состав пакета GNU binutils), чтобы получить список «слов» из каждого файла. Цикл for обрабатывает каждое слово по очереди и определяет, является ли оно самым длинным из встречавшихся до сих пор. По завершении цикла сценарий выводит самое длинное слово.

Обратите внимание, что здесь, вопреки обычной практике, мы не заключили подстановку команд <html>$(strings “$1”)</html> в кавычки. Объясняется это просто: здесь мы действительно хотим разбить строку на слова, чтобы получить список. Заключив подстановку команд в кавычки, мы получили бы одно слово, содержащее все строки из файла. А это не совсем то, что нам нужно.

Если необязательный компонент слова в команде for отсутствует, она по умолчанию обрабатывает позиционные параметры. Чтобы показать использование этого способа, изменим сценарий <html>longest-word</html>:

#!/bin/bash
 
# longest-word2 : поиск самой длинной строки в файле
 
for i; do
	if [[ -r "$i" ]]; then
		max_word=
		max_len=0
		for j in $(strings "$i"); do
			len="$(echo "$j" | wc -c)"
 			if (( len > max_len )); then
 				max_len="$len"
 				max_word="$j"
 			fi
 			done
 			echo "$i: '$max_word' ($max_len characters)"
	fi
done

Как видите, мы заменили внешний цикл while циклом for. Так как список слов в команде for отсутствует, она перебирает позиционные параметры. Во внутреннем цикле вместо переменной i теперь используется переменная j. Кроме того, нам больше не нужна команда shift. <callout type=“primary” icon=“true” title=“ПОЧЕМУ I?”>Вы могли заметить, что во всех примерах цикла for выше использовалась переменная i. Почему? В действительности за этим выбором не стоят какие-то определенные причины, кроме стремления следовать традициям. В команде for можно использовать любую допустимую переменную, но чаще всего используется переменная i, а также j и k.

Своими корнями эта традиция уходит в язык программирования Fortran. В Fortran необъявленные переменные, начинающиеся с букв I, J, K, L и M, автоматически становились целочисленными, тогда как переменные, начинающиеся с любой другой буквы, – действительными, или вещественными (способными хранить числа с дробной частью). Эта особенность вынуждала программистов использовать переменные I, J и K в качестве переменных цикла, так как использование их в качестве временных переменных (чем переменные цикла в действительности и являются) требовало меньших усилий.

Из-за этого даже в среде программистов на Fortran ходила острота: «GOD is real, unless declared integer» (Бог действителен, пока явно не объявлен целым).</callout>

for: форма в стиле языка C

В последние версии bash добавлена вторая форма синтаксиса команды for, напоминающая одноименный оператор в языке программирования C, которая поддерживается также многими другими языками.

for (( выражение1; выражение2; выражение3 )); do
    команды
done

выражение1 инициализирует цикл, выражение2 определяет условие завершения цикла, выражение3 выполняется в конце каждой итерации.

Ниже приводится пример типичного применения:

#!/bin/bash
 
# simple_counter : демонстрация команды for в стиле языка C
 
for (( i=0; i<5; i=i+1 )); do
    echo $i
done

Этот сценарий произведет следующий вывод:

simple_counter
0
1
2
3
4

Здесь выражение1 инициализирует переменную i значением 0, выражение2 позволяет продолжать итерации, пока значение i остается меньше 5, выражение3 увеличивает на единицу значение i в конце каждой итерации.

Форма команды for в стиле языка C выглядит предпочтительнее, если требуется работать с числовыми последовательностями.

sys_info_page

Познакомившись с командой for, внесем заключительное усовершенствование в наш сценарий sys_info_page. В настоящий момент функция report_home_space выглядит так:

report_home_space () {
        if [[ $(id -u) -eq 0 ]]; then
                cat <<- _EOF_
                        <h2>HOME SPACE UTILIZATION</h2>
                        <pre>$(du -sh /home/*)</pre>
                        _EOF_
        else
                cat <<- _EOF_
                        <h2>HOME SPACE UTILIZATION</h2>
                        <pre>$(du -sh $HOME)</pre>
                        _EOF_
                fi
                return
}

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

report_home_space () {
        local format="%8s%10s%10s\n"
        local i dir_list total_files total_dirs total_size user_name
 
        if [[ $(id -u) -eq 0 ]]; then
                dir_list=/home/*
                user_name="All Users"
        else
                dir_list="$HOME"
                user_name="$USER"
        fi
        echo "<h2>Home Space Utilization</h2>"
 
        for i in $dir_list; do
 
                total_files="$(find "$i" -type f | wc -l)"
                total_dirs="$(find "$i" -type d | wc -l)"
                total_size="$(du -sh "$i" | cut -f 1)"
                echo "<h3>$i</h3>"
                echo "<pre>"
                printf "$format" "Dirs" "Files" "Size"
                printf "$format" "____" "_____" "____"
                printf "$format" "$total_dirs" "$total_files" "$total_size"
        done
        return
 
}

В этой новой версии применено многое из того, что мы узнали к данному моменту. Она все еще проверяет наличие привилегий суперпользователя, но вместо того, чтобы выполнить полный набор операций в каждой из ветвей if, здесь устанавливаются некоторые переменные, которые затем используются в цикле for. В функции использованы несколько локальных переменных и команда printf для форматирования части вывода.

bash/цикл_for.txt · Last modified: 2023/04/06 10:18 (external edit)