Книга: Искусство программирования на языке сценариев командной оболочки

10.1. Циклы

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

циклы for

for (in)

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

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


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

for arg in "$var1" "$var2" "$var3" ... "$varN"

# На первом проходе, $arg = $var1

# На втором проходе, $arg = $var2

# На третьем проходе, $arg = $var3

# ...

# На N-ном проходе, $arg = $varN

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

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

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

for arg 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


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

Пример 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-39, Пример 10-10 и Пример 12-33.

Пример 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-10, Пример 25-11 и Пример 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-28).

Пример 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


Стандартное устройство ввода 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

Оглавление книги

Оглавление статьи/книги

Генерация: 0.045. Запросов К БД/Cache: 0 / 2
поделиться
Вверх Вниз