Перейти к основному содержанию
Рецепты Linux

Main navigation

  • Основы
  • Система
  • Команды
  • Программы
  • Дистро
  • Интерфейсы
  • Устройства
  • Доки
User account menu
  • Войти

Строка навигации

  1. Главная
  2. ABS Guide
  3. Часть 3. Углубленный материал
  4. Глава 10. Циклы и ветвления

10.1. Циклы

Цикл -- это блок команд, который исполняется многократно до тех пор, пока не будет выполнено условие выхода из цикла.

циклы for

for (in)

Это одна из основных разновидностей циклов. И она значительно отличается от аналога в языке C.

forarg in [list]
do
 команда(ы)...
done

Note

На каждом проходе цикла, переменная-аргумент цикла arg последовательно, одно за другим, принимает значения из списка list.

for arg in "$var1" "$var2" "$var3" ... "$varN"
# На первом проходе, $arg = $var1
# На втором проходе, $arg = $var2
# На третьем проходе, $arg = $var3
# ...
# На N-ном проходе, $arg = $varN

# Элементы списка заключены в кавычки для того, чтобы предотвратить возможное разбиение их на отдельные аргументы (слова).

Элементы списка могут включать в себя шаблонные символы.

Есл ключевое слово do находится в одной строке со словом for, то после списка аргументов (перед do) необходимо ставить точку с запятой.

forarg in [list] ; do

Пример 10-1. Простой цикл for

#!/bin/bash
# Список планет.

for planet in Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон
do
 echo $planet
done

echo

# Если 'список аргументов' заключить в кавычки, то он будет восприниматься как единственный аргумент .
for planet in "Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон"
do
 echo $planet
done

exit 0
Note

Каждый из элементов [списка] может содержать несколько аргументов. Это бывает полезным при обработке групп параметров. В этом случае, для принудительного разбора каждого из аргументов в списке, необходимо использовать инструкцию set (см. Пример 11-14).

Пример 10-2. Цикл for с двумя параметрами в каждом из элементов списка

#!/bin/bash
# Список планет.

# Имя кажой планеты ассоциировано с расстоянием от планеты до Солнца (млн. миль).

for planet in "Меркурий 36" "Венера 67" "Земля 93" "Марс 142" "Юпитер 483"
do
 set -- $planet # Разбиение переменной "planet" на множество аргументов (позиционных параметров).
 # Конструкция "--" предохраняет от неожиданностей, если $planet "пуста" или начинается с символа "-".

 # Если каждый из аргументов потребуется сохранить, поскольку на следующем проходе они будут "забиты" новыми значениями,
 # То можно поместить их в массив,
 # original_params=("$@")

 echo "$1 в $2,000,000 миль от Солнца"
 #----две табуляции---к параметру $2 добавлены нули
done

# (Спасибо S.C., за разъяснения.)

exit 0

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

Пример 10-3. Fileinfo: обработка списка файлов, находящегося в переменной

#!/bin/bash
# fileinfo.sh

FILES="/usr/sbin/privatepw
/usr/sbin/pwck
/usr/sbin/go500gw
/usr/bin/fakefile
/sbin/mkreiserfs
/sbin/ypbind" # Список интересующих нас файлов.
 # В список добавлен фиктивный файл /usr/bin/fakefile.

echo

for file in $FILES
do

 if [ ! -e "$file" ] # Проверка наличия файла.
 then
 echo "Файл $file не найден."; echo
 continue # Переход к следующей итерации.
 fi

 ls -l $file | awk '{ print $8 " размер: " $5 }' # Печать 2 полей.
 whatis `basename $file` # Информация о файле.
 echo
done 

exit 0

В [списке] цикла for могут быть использованы имена файлов, которые в свою очередь могут содержать символы-шаблоны.

Пример 10-4. Обработка списка файлов в цикле for

#!/bin/bash
# list-glob.sh: Создание список файлов в цикле for с использованием
# операции подстановки имен файлов ("globbing").

echo

for file in *
do
 ls -l "$file" # Список всех файлов в $PWD (текущем каталоге).
 # Напоминаю, что символу "*" соответствует любое имя файла,
 # однако, в операциях подстановки имен файлов ("globbing"),
 # имеются исключения -- имена файлов, начинающиеся с точки.

 # Если в каталоге нет ни одного файла, соответствующего шаблону,
 # то за имя файла принимается сам шаблон.
 # Чтобы избежать этого, используйте ключ nullglob
 # (shopt -s nullglob).
 # Спасибо S.C.
done

echo; echo

for file in [jx]*
do
 rm -f $file # Удаление файлов, начинающихся с "j" или "x" в $PWD.
 echo "Удален файл \"$file\"".
done

echo

exit 0

Если [список] в цикле for не задан, то в качестве оного используется переменная $@ -- список аргументов командной строки. Оень остроумно эта особенность проиллюстрирована в Пример A-18.

Пример 10-5. Цикл for без списка аргументов

#!/bin/bash

# Попробуйте вызвать этот сценарий с аргументами и без них и посмотреть на результаты.

for a
do
 echo -n "$a "
done

# Список аргументов не задан, поэтому цикл работает с переменной '$@'
#+ (список аргументов командной строки, включая пробельные символы).

echo

exit 0

При создании списка аргументов, в цикле for допускается пользоваться подстановкой команд. См. Пример 12-42, Пример 10-10 и Пример 12-36.

Пример 10-6. Создание списка аргументов в цикле for с помощью операции подстановки команд

#!/bin/bash
# Цикл for со [списком], созданным с помощью подстановки команд.

NUMBERS="9 7 3 8 37.53"

for number in `echo $NUMBERS` # for number in 9 7 3 8 37.53
do
 echo -n "$number "
done

echo 
exit 0

Более сложный пример использования подстановки команд при создании списка аргументов цикла.

Пример 10-7. grep для бинарных файлов

#!/bin/bash
# bin-grep.sh: Поиск строк в двоичных файлах.

# замена "grep" для бинарных файлов.
# Аналогично команде "grep -a"

E_BADARGS=65
E_NOFILE=66

if [ $# -ne 2 ]
then
 echo "Порядок использования: `basename $0` string filename"
 exit $E_BADARGS
fi

if [ ! -f "$2" ]
then
 echo "Файл \"$2\" не найден."
 exit $E_NOFILE
fi


for word in $( strings "$2" | grep "$1" )
# Инструкция "strings" возвращает список строк в двоичных файлах.
# Который затем передается по конвейеру команде "grep", для выполнения поиска.
do
 echo $word
done

# Как указывает S.C., вышепрведенное объявление цикла for может быть упрощено
# strings "$2" | grep "$1" | tr -s "$IFS" '[\n*]'


# Попробуйте что нибудь подобное: "./bin-grep.sh mem /bin/ls"

exit 0

Еще один пример.

Пример 10-8. Список всех пользователей системы

#!/bin/bash
# userlist.sh

PASSWORD_FILE=/etc/passwd
n=1 # Число пользователей

for name in $(awk 'BEGIN{FS=":"}{print $1}'  "$PASSWORD_FILE" )
# Разделитель полей = : ^^^^^^
# Вывод первого поля ^^^^^^^^
# Данные берутся из файла паролей ^^^^^^^^^^^^^^^^^
do
 echo "Пользователь #$n = $name"
 let "n += 1"
done


# Пользователь #1 = root
# Пользователь #2 = bin
# Пользователь #3 = daemon
# ...
# Пользователь #30 = bozo

exit 0

И заключительный пример использования подстановки команд при создании [списка].

Пример 10-9. Проверка авторства всех бинарных файлов в текущем каталоге

#!/bin/bash
# findstring.sh:
# Поиск заданной строки в двоичном файле.

directory=/usr/local/bin/
fstring="Free Software Foundation" # Поиск файлов от FSF.

for file in $( find $directory -type f -name '*' | sort )
do
 strings -f $file | grep "$fstring" | sed -e "s%$directory%%"
 # Команде "sed" передается выражение (ключ -e),
 #+ для того, чтобы изменить обычный разделитель "/" строки поиска и строки замены
 #+ поскольку "/" - один из отфильтровываемых символов.
 # Использование такого символа порождает сообщение об ошибке (попробуйте).
done

exit 0

# Упражнение:
# ---------------
# Измените сценарий таким образом, чтобы он брал
#+ $directory и $fstring из командной строки.

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

Пример 10-10. Список символических ссылок в каталоге

#!/bin/bash
# symlinks.sh: Список символических ссылок в каталоге.


directory=${1-`pwd`}
# По-умолчанию в текущем каталоге,
# Блок кода, который выполняет аналогичные действия.
# ----------------------------------------------------------
# ARGS=1 # Ожидается один аргумент командной строки.
#
# if [ $# -ne "$ARGS" ] # Если каталог поиска не задан...
# then
# directory=`pwd` # текущий каталог
# else
# directory=$1
# fi
# ----------------------------------------------------------

echo "символические ссылки в каталоге \"$directory\""

for file in "$( find $directory -type l )" # -type l = символические ссылки
do
 echo "$file"
done | sort # В противном случае получится неотсортированный список.

# Как отмечает Dominik 'Aeneas' Schnitzer,
#+ в случае отсутствия кавычек для $( find $directory -type l )
#+ сценарий "подавится" именами файлов, содержащими пробелы.

exit 0

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

Пример 10-11. Список символических ссылок в каталоге, сохраняемый в файле

#!/bin/bash
# symlinks.sh: Список символических ссылок в каталоге.

OUTFILE=symlinks.list # файл со списком

directory=${1-`pwd`}
# По-умолчанию -- текущий каталог,

echo "символические ссылки в каталоге \"$directory\"" > "$OUTFILE"
echo "---------------------------" >> "$OUTFILE"

for file in "$( find $directory -type l )" # -type l = символические ссылки
do
 echo "$file"
done | sort >> "$OUTFILE" # перенаправление вывода
# ^^^^^^^^^^^^^ в файл.

exit 0

Оператор цикла for имеет и альтернативный синтаксис записи -- очень похожий на синтаксис оператора for в языке C. Для этого используются двойные круглые скобки.

Пример 10-12. C-подобный синтаксис оператора цикла for

#!/bin/bash
# Два вапианта оформления цикла.

echo

# Стандартный синтаксис.
for a in 1 2 3 4 5 6 7 8 9 10
do
 echo -n "$a "
done

echo; echo

# +==========================================+

# А теперь C-подобный синтаксис.

LIMIT=10

for ((a=1; a = LIMIT ; a++)) # Двойные круглые скобки и "LIMIT" без "$".
do
 echo -n "$a "
done # Конструкция заимствована из 'ksh93'.

echo; echo

# +=========================================================================+

# Попробуем и C-шный оператор "запятая".

for ((a=1, b=1; a = LIMIT ; a++, b++)) # Запятая разделяет две операции, которые выполняются совместно.
do
 echo -n "$a-$b "
done

echo; echo

exit 0

См. так же Пример 25-15, Пример 25-16 и Пример A-7.

---

А сейчас пример сценария, который может найти "реальное" применение.

Пример 10-13. Работа с командой efax в пакетном режиме

#!/bin/bash

EXPECTED_ARGS=2
E_BADARGS=65

if [ $# -ne $EXPECTED_ARGS ]
# Проверка наличия аргументов командной строки.
then
 echo "Порядок использования: `basename $0` phone# text-file"
 exit $E_BADARGS
fi


if [ ! -f "$2" ]
then
 echo "Файл $2 не является текстовым файлом"
 exit $E_BADARGS
fi


fax make $2 # Создать fax-файлы из текстовых файлов.

for file in $(ls $2.0*) # Все файлы, получившиеся в результате преобразования.
 # Используется шаблонный символ в списке.
do
 fil="$fil $file"
done

efax -d /dev/ttyS3 -o1 -t "T$1" $fil # отправить.


# Как указывает S.C., в цикл for может быть вставлена сама команда отправки в виде:
# efax -d /dev/ttyS3 -o1 -t "T$1" $2.0*
# но это не так поучительно [;-)].

exit 0
while

Оператор while проверяет условие перед началом каждой итерации и если условие истинно (если код возврата равен 0), то управление передается в тело цикла. В отличие от циклов for, циклы while используются в тех случаях, когда количество итераций заранее не известно.

while [condition]
do
 command...
done

Как и в случае с циклами for/in, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ ";" перед do.

while [condition] ; do

Обратите внимание: в отдельных случаях, таких как использование конструкции getopts совместно с оператором while, синтаксис несколько отличается от приводимого здесь.

Пример 10-14. Простой цикл while

#!/bin/bash

var0=0
LIMIT=10

while [ "$var0" -lt "$LIMIT" ]
do
 echo -n "$var0 " # -n подавляет перевод строки.
 var0=`expr $var0 + 1` # допускается var0=$(($var0+1)).
done

echo

exit 0

Пример 10-15. Другой пример цикла while

#!/bin/bash

echo

while [ "$var1" != "end" ] # возможна замена на while test "$var1" != "end"
do
 echo "Введите значение переменной #1 (end - выход) "
 read var1 # Конструкция 'read $var1' недопустима (почему?).
 echo "переменная #1 = $var1" # кавычки обязательны, потому что имеется символ "#".
 # Если введено слово 'end', то оно тоже выводится на экран.
 # потому, что проверка переменной выполняется в начале итерации (перед вводом).
 echo
done 

exit 0

Оператор while может иметь несколько условий. Но только последнее из них определяет возможность продолжения цикла. В этом случае синтаксис оператора цикла должен быть несколько иным.

Пример 10-16. Цикл while с несколькими условиями

#!/bin/bash

var1=unset
previous=$var1

while echo "предыдущее значение = $previous"
 echo
 previous=$var1 # запомнить предыдущее значение
 [ "$var1" != end ]
 # В операторе "while" присутствуют 4 условия, но только последнее управляет циклом.
 # *последнее* условие - единственное, которое вычисляется.
do
echo "Введите значение переменной #1 (end - выход) "
 read var1
 echo "текущее значение = $var1"
done

# попробуйте самостоятельно разобраться в сценарии works.

exit 0

Как и в случае с for, цикл while может быть записан в C-подобной нотации, с использованием двойных круглых скобок (см. так же Пример 9-29).

Пример 10-17. C-подобный синтаксис оформления цикла while

#!/bin/bash
# wh-loopc.sh: Цикл перебора от 1 до 10.

LIMIT=10
a=1

while [ "$a" -le $LIMIT ]
do
 echo -n "$a "
 let "a+=1"
done # Пока ничего особенного.

echo; echo

# +=================================================================+

# А теперь оформим в стиле языка C.

((a = 1)) # a=1
# Двойные скобки допускают наличие лишних пробелов в выражениях.

while (( a = LIMIT )) # В двойных скобках символ "$" перед переменными опускается.
do
 echo -n "$a "
 ((a += 1)) # let "a+=1"
 # Двойные скобки позволяют наращивание переменной в стиле языка C.
done

echo

# Теперь, программисты, пишущие на C, могут чувствовать себя в Bash как дома.

exit 0
Note

Стандартное устройство ввода stdin, для цикла while, можно перенаправить на файл с помощью команды перенаправления < в конце цикла.

until

Оператор цикла until проверяет условие в начале каждой итерации, но в отличие от while итерация возможна только в том случае, если условие ложно.

until [condition-is-true]
do
 command...
done

Обратите внимание: оператор until проверяет условие завершения цикла ПЕРЕД очередной итерацией, а не после, как это принято в некоторых языках программирования.

Как и в случае с циклами for/in, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ ";" перед do.

until [condition-is-true] ; do

Пример 10-18. Цикл until

#!/bin/bash

until [ "$var1" = end ] # Проверка условия производится в начале итерации.
do
 echo "Введите значение переменной #1 "
 echo "(end - выход)"
 read var1
 echo "значение переменной #1 = $var1"
done 

exit 0

Перекрёстные ссылки книги для 10.1. Циклы

  • Глава 10. Циклы и ветвления
  • Вверх
  • 10.2. Вложенные циклы

Book navigation

  • Содержание
  • Часть 1. Введение
  • Часть 2. Основы
  • Часть 3. Углубленный материал
    • Глава 9. К вопросу о переменных
    • Глава 10. Циклы и ветвления
      • 10.1. Циклы
      • 10.2. Вложенные циклы
      • 10.3. Управление ходом выполнения цикла
      • 10.4. Операторы выбора
    • Глава 11. Внутренние команды
    • Глава 12. Внешние команды, программы и утилиты
    • Глава 13. Команды системного администрирования
    • Глава 14. Подстановка команд
    • Глава 15. Арифметические подстановки
    • Глава 16. Перенаправление ввода/вывода
    • Глава 17. Встроенные документы
  • Часть 4. Материал повышенной сложности
  • Глава 35. Замечания и дополнения
  • Библиография
  • Приложение A. Дополнительные примеры сценариев
  • Приложение B. Справочная информация
  • Приложение C. Маленький учебник по Sed и Awk
  • Приложение D. Коды завершения, имеющие предопределенный смысл
  • Приложение E. Подробное введение в операции ввода-вывода и перенаправление ввода-вывода
  • Приложение F. Системные каталоги
  • Приложение G. Локализация
  • Приложение H. История команд
  • Приложение I. Пример файла .bashrc
  • Приложение J. Преобразование пакетных (*.bat) файлов DOS в сценарии командной оболочки
  • Приложение K. Упражнения
  • Приложение L. Хронология
  • Приложение M. Авторские права

Последние материалы

  • Приложение scanimage
    1 day ago
  • Утилита sensors
    5 days ago
  • Сканер Rkhunter
    1 week 6 days ago
  • Программа resize2fs
    2 weeks 4 days ago
  • Аудиопроигрыватель QMMP
    3 weeks 3 days ago
RSS feed

Secondary menu

  • О проекте

© 2008–2025 Олег Меньшенин mensh@yandex.ru