Типичным считается цикл по последовательности имен файлов, и оператор
for
языка
shell
является единственной структурой управления, которую обычно задают с терминала, а не помещают в файл для последующего выполнения. Синтаксис оператора
for
таков:
for перем in список_слов
do
команды
done
Например, для получения эха имен файлов по одному на строке достаточно задать:
$ for i in *
> do
> echo $i
> done
Вместо
i
можно применять любую переменную языка
shell
, но это обозначение традиционно. Заметьте, что значение переменной получается с помощью
$i
, однако в заголовке цикла переменную указывают как
i
. Мы задействовали
*
для выбора всех файлов текущего каталога, но можно использовать и любой другой список аргументов. Обычно нужно сделать что-нибудь более интересное, чем печать имен файлов. Нам часто приходилось сравнивать набор файлов с их предыдущими версиями, например старую версию гл. 2 (хранимую в каталоге
old
) с текущей:
$ ls ch2. * | 5
ch2.1 ch2.2 ch2.3 ch2.4 ch2.5
ch2.6 ch2.7
$ for i in ch2.*
> do
> echo $i
> diff -b old/$i $i
> echo
Добавим пустую строку для красоты
> done | pr -h "diff `pwd`/old `pwd` | lpr &
3712
Номер процесса
$
Выходной поток направлен по конвейеру через команды
pr
и
lpr
просто для того, чтобы показать, что это возможно: стандартный выходной поток программ, находящихся внутри цикла
for
, попадает в стандартный выходной поток самой команды
for
. С помощью флага
-h
в команде pr мы поместили в выходной поток заголовок с "архитектурными излишествами", используя два вложенных обращения к
pwd
. Вся последовательность команд запущена асинхронно (
&
), так что не нужно ждать ее окончания;
&
применяется ко всякому циклу и конвейеру.
Мы предпочитаем указанный формат для цикла
for
, но вы можете сократить его. Единственное ограничение заключается в том, что
do
и
done
распознаются как ключевые слова, только если они появляются сразу после перевода строки или точки с запятой. В зависимости от размера цикла
for
иногда лучше помещать все на одной строке:
for i in список; do команды; done
Следует использовать цикл
for
для обработки составных команд или в тех случаях, когда не подходит встроенная обработка отдельных команд. Но не применяйте его там, где в отдельной команде есть цикл по именам файлов:
# Плохая идея:
for i in $*
do
chmod +x $i
done
Предпочтительнее сделать так:
chmod +x $*
поскольку в цикле
for
отдельная команда
chmod
выполняется для каждого файла, что требует больших вычислительных ресурсов. (Убедитесь в том, что вы понимаете разницу между командами
for i in *
в которой цикл выполняется по всем именам файлов текущего каталога, и
for i in $*
в которой цикл выполняется по всем аргументам командного файла.)
Список аргументов для цикла
for
часто получают путем выбора имен файлов по шаблону, но можно получать и любым другим способом, в частности:
for i in `cat ...`
или просто вводом аргументов. Например, ранее в этой главе мы создали ряд программ для печати в несколько столбцов под именами
2
,
3
и т.д. Они являются связями с одним файлом, которые можно установить следующим образом (при условии, что программа
2
написана):
$ for i in 3 4 5 6; do ln 2 $i; done
$
Цикл
for
имеет и более интересное назначение. Выберем с помощью команды
pick
те файлы, которые будут сравниваться с файлами из каталога старых версий:
$ for i in `pick ch2.*`
> do
> echo $i:
> diff old/$i $i
> done | pr | lpr
ch2.1? y
ch2.2
ch2.3
ch2.4? y
ch2.5? y
ch2.6?
ch2.7?
$
Очевидно, данный цикл следует поместить в командный файл, чтобы уменьшить ввод в следующий раз (ведь если вы что-то сделали дважды, вероятно, вы сделаете это и в третий раз).
Упражнение 3.15
Если цикл с командой
diff
хранится в командном файле, поместите ли вы туда команду pick? Объясните, почему.
Упражнение 3.16
Что произойдет, если последняя строка приведенного цикла будет иметь вид:
> done | pr | lpr &
т.е. кончаться амперсандом? Попробуйте сделать прогноз, а затем проверьте его.
3.9 Программа
bundle
: соберем все воедино Чтобы лучше понять, как создаются командные файлы, обратимся к такому примеру. Предположим, вы получили почту от приятеля с другой машины:
"где-то!боб"
(Существует несколько вариантов обозначений для адресата на другой машине. Наиболее общим является следующее:
машина!пользователь[10]. См. справочное руководство по
mail(1)
), и он хотел бы скопировать командные файлы из вашего каталога
bin
. Самый простой способ их пересылки заключается в ответной почте, так что вы могли бы начать вводить: