sonyps4.ru

BASH: описание циклов for, while, until и примеры использования. BASH: описание циклов for, while, until и примеры использования Логическое и в цикле while bash

И вот настал черёд пятого топика цикла. В нём я продолжу рассматривать управляющие структуры командного интерпретатора bash. В предыдущем посте были разобраны условные операторы. Теперь настал черёд таких неотъемлемых языковых структур, как операторы цикла. Циклы, как и условия, являются теми инструментальными средствами, которые обеспечивают управление порядком исполнения команд.
Bash поддерживает циклы типа for, while, until и select. Теперь перейдём к подробному рассмотрению каждого из них.

Оператор цикла for

Общий синтаксис оператора for:

for переменная
do
список команд
done

Цикл for в bash значительно отличается от цикла for в таких языках программирования, как C или PHP. Поэтому если вы программируете на C, вам нужно будет привыкнуть к этим отличиям, чтобы не путаться.
В результате подстановки шаблона получается список слов. При каждой итерации переменная принимает каждое последующее значение этого списка, и выполняются команды, стоящие между do и done. Итерации прекращаются по исчерпании слов в списке.
$ for day in Mon Tue Wed Thu Fri do echo “Сегодня $day” done
Зарезервированные слова оператора for также можно писать в одну строчку, как это было в предыдущем посте для оператора if. Для этого нужно ставить ";". Перепишем предыдущий пример с учётом данной возможности:
$ for day in Mon Tue Wed Thu Fri; do echo “Сегодня $day”; done
Отсутствие конструкции эквивалентно записи in $@. О параметре $@ говорилось в .

Оператор цикла while

Общий синтаксис оператора while:

while список1
do
список2
done

Сперва выполняется список1. Если он завершается успешно, т. е. возвращает нулевой код, управление переходит списку2. Итерации продолжаются до тех пор, пока результат выполнения списка1 не станет ненулевым. Например:
i=10 while [ $i –gt 0 ]; do echo $i i=$(($i-1)) done; echo "end"

Оператор цикла until

Общий синтаксис оператора until:

until список1
do
список2
done

Сперва выполняется список1. Если он завершается неуспешно, то есть с ненулевым кодом возврата, управление переходит списку2. Итерации продолжаются до тех пор, пока результат выполнения списка1 не станет нулевым. Например:
i=10 until [ $i –lt 0 ]; do echo $i i=$(($i-1)) done; echo "end"

Оператор цикла select

Общий синтаксис оператора select:

select переменная
do
список
done

В результате подстановки шаблона получается список слов. К этим словам оператор добавляет порядковые номера и выводит весь набор в стандартный поток ошибок. Отсутствие конструкции эквивалентно записи in $@. После этого выводится приглашение и считывается строка из стандартного потока ввода. Если строка содержит номер, соответствующий какому-либо слову из списка, то переменная получает его в качестве значения. Если подходящего слова не было найдено, то значением переменной становится пустая строка. После этого выполняется список команд, и итерации продолжаются до тех пор, пока в строке ввода не попадётся символ конца файла или пока в списке команд не встретится break или return.
Команды break и return служат для управления ходом выполнения цикла. Команда break прерывает исполнение цикла, в то время как return возвращает код результата (нулевой или ненулевой).
Оператор select полезен для создания нумерованных меню. Например, в каталоге ~/temp находится 3 файла: proto.txt, file.txt и readme. Пример фрагмента скрипта, позволяющего быстро просмотреть любой из них.
echo "Выберите файл для просмотра:" select file in ~/temp/* Quit; do if [ -f $file ]; then cat $file; else break; fi done
Запустив данный скрипт, мы увидим на экране:
Выберите файл для просмотра:
1) /home/istergul/temp/file.txt
2) /home/istergul/temp/proto.txt
3) /home/istergul/temp/readme
4) Quit

В следующем топике будут рассмотрены условные подстановки параметров. Жду ваших комментариев.

for VAR in 1 2 3...N do done или в одну строку: for VAR in 1 2 3...N; do ; done
В переменную допускается подставлять как числовые значения, так и ASCII символы.
Пример: $ for i in 1 2 A B Abc ; do echo $i; done 1 2 A B Abc Пример перечисления в переменную файлов по "маске" для перекодирования видео: for i in *.avi; do ; done

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

for VAR in $(); do ; done
Пример использования результатов команды seq: for i in $(seq [КЛЮЧ]); do echo $i; done $ for i in $(seq 3); do echo $i; done 1 2 3 $ for i in $(seq 3 5); do echo $i; done 3 4 5 $ for i in $(seq 2 2 6); do echo $i; done 2 4 6 Пример использования результатов команды ls: $ for i in $(ls /$HOME/Video); do echo $i; done 001.avi 002.avi 003.avi

3. Подстановка используя Си-стиль (C-style)

for ((EXPR1; EXPR2; EXPR3)) do <список команд> done for ((i=1; i<=3 ; i++)); do echo $i; done $ for ((i=1; i<=3 ; i++)); do echo $i; done 1 2 3 Подробнее о применении C-style в Bash

4. Перечисление при помощи фигурных скобок {..}

Применение синтаксиса {START..END} поддерживается начиная с bash version 3.0+ , а начиная с bash version 4.0+ поддерживается синтаксис {START..END..INCREMENT} :

for VAR in {..} do done или for VAR in {....} do done Примеры: $ for i in {1..3}; do echo $i; done 1 2 3 или $ for i in {4..8..2}; do echo $i; done 4 6 8 Отсчёт возможен как на приращение, так и на уменьшение значений: $ for i in {6..-4..3}; do echo $i; done 6 3 0 -3

5. Подстановка параметров (in "$@")

Выполняет команды для каждого параметра, который был передан сценарию. for VAR in $@ do done или в одну строку: for VAR in $@; do ; done
Так если создать скрипт test.sh #!/bin/sh for VAR in $@ do echo $VAR done то при запуске его с параметрами: $ ./test.sh param1 param2 param3 param1 param2 param3 Часть in $@ можно опускать. Тогда скрипт test.sh перепишется: #!/bin/sh for VAR do echo $VAR done
Приведу пару примеров (с in и без): $ function FUNC_1 { for VAR in $@; do echo $VAR; done; } $ FUNC_1 param1 param2 param3 param1 param2 param3 $ function FUNC_2 { for VAR; do echo $VAR; done; } $ FUNC_2 param1 param2 param3 param1 param2 param3

6. Применение continue и break в цикле for

Для всех вышеперечисленных конструкций возможно использовать команды "continue" для перехода к следующему элементу цикла или "break" для выхода из цикла.

Пример (завершить при i=6 и не выполнять при i=3 и i=5): for i in {1..8}; do if [ $i -eq 6 ]; then break; fi if [ $i -eq 3 ] || [ $i -eq 5 ]; then continue; fi echo $i done Результат выполнения: $ $ for i in {1..8}; do \ > if [ $i -eq 6 ]; then break; fi; \ > if [ $i -eq 3 ] || [ $i -eq 5 ]; then continue; fi; \ > echo $i; \ > done 1 2 4

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

For var in list do команды done
В каждой итерации цикла в переменную var будет записываться следующее значение из списка list . В первом проходе цикла, таким образом, будет задействовано первое значение из списка. Во втором - второе, и так далее - до тех пор, пока цикл не дойдёт до последнего элемента.

Перебор простых значений

Пожалуй, самый простой пример цикла for в bash-скриптах - это перебор списка простых значений:

#!/bin/bash for var in first second third fourth fifth do echo The $var item done
Ниже показаны результаты работы этого скрипта. Хорошо видно, что в переменную $var последовательно попадают элементы из списка. Происходит так до тех пор, пока цикл не дойдёт до последнего из них.


Простой цикл for

Обратите внимание на то, что переменная $var сохраняет значение при выходе из цикла, её содержимое можно менять, в целом, работать с ней можно как с любой другой переменной.

Перебор сложных значений

В списке, использованном при инициализации цикла for , могут содержаться не только простые строки, состоящие из одного слова, но и целые фразы, в которые входят несколько слов и знаков препинания. Например, всё это может выглядеть так:

#!/bin/bash for var in first "the second" "the third" "I’ll do it" do echo "This is: $var" done
Вот что получится после того, как этот цикл пройдётся по списку. Как видите, результат вполне ожидаем.


Перебор сложных значений
TNW-CUS-FMP - промо-код на 10% скидку на наши услуги, доступен для активации в течение 7 дней"

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

Ещё один способ инициализации цикла for заключается в передаче ему списка, который является результатом работы некоей команды. Тут используется подстановка команд для их исполнения и получения результатов их работы.

#!/bin/bash file="myfile" for var in $(cat $file) do echo " $var" done
В этом примере задействована команда cat , которая читает содержимое файла. Полученный список значений передаётся в цикл и выводится на экран. Обратите внимание на то, что в файле, к которому мы обращаемся, содержится список слов, разделённых знаками перевода строки, пробелы при этом не используются.


Цикл, который перебирает содержимое файла

Тут надо учесть, что подобный подход, если ожидается построчная обработка данных, не сработает для файла более сложной структуры, в строках которого может содержаться по несколько слов, разделённых пробелами. Цикл будет обрабатывать отдельные слова, а не строки.

Что, если это совсем не то, что нужно?

Разделители полей

Причина вышеописанной особенности заключается в специальной переменной окружения, которая называется IFS (Internal Field Separator) и позволяет указывать разделители полей. По умолчанию оболочка bash считает разделителями полей следующие символы:
  • Пробел
  • Знак табуляции
  • Знак перевода строки
Если bash встречает в данных любой из этих символов, он считает, что перед ним - следующее самостоятельное значение списка.

Для того, чтобы решить проблему, можно временно изменить переменную среды IFS . Вот как это сделать в bash-скрипте, если исходить из предположения, что в качестве разделителя полей нужен только перевод строки:

IFS=$"\n"
После добавления этой команды в bash-скрипт, он будет работать как надо, игнорируя пробелы и знаки табуляции, считая разделителями полей лишь символы перевода строки.

#!/bin/bash file="/etc/passwd" IFS=$"\n" for var in $(cat $file) do echo " $var" done
Если этот скрипт запустить, он выведет он именно то, что от него требуется, давая, в каждой итерации цикла, доступ к очередной строке, записанной в файл.


Построчный обход содержимого файла в цикле for

Разделителями могут быть и другие символы. Например, выше мы выводили на экран содержимое файла /etc/passwd . Данные о пользователях в строках разделены с помощью двоеточий. Если в цикле нужно обрабатывать подобные строки, IFS можно настроить так:

Обход файлов, содержащихся в директории

Один из самых распространённых вариантов использования циклов for в bash-скриптах заключается в обходе файлов, находящихся в некоей директории, и в обработке этих файлов.

Например, вот как можно вывести список файлов и папок:

#!/bin/bash for file in /home/likegeeks/* do if [ -d "$file" ] then echo "$file is a directory" elif [ -f "$file" ] then echo "$file is a file" fi done
Если вы разобрались с предыдущим материалом из этой серии статей, вам должно быть понятно устройство конструкции if-then , а так же то, как отличить файл от папки. Если вам сложно понять вышеприведённый код, перечитайте этот материал.

Вот что выведет скрипт.


Вывод содержимого папки

Обратите внимание на то, как мы инициализируем цикл, а именно - на подстановочный знак «*» в конце адреса папки. Этот символ можно воспринимать как шаблон, означающий: «все файлы с любыми именами». он позволяет организовать автоматическую подстановку имён файлов, которые соответствуют шаблону.

При проверке условия в операторе if , мы заключаем имя переменной в кавычки. Сделано это потому что имя файла или папки может содержать пробелы.

Циклы for в стиле C

Если вы знакомы с языком программирования C, синтаксис описания bash-циклов for может показаться вам странным, так как привыкли вы, очевидно, к такому описанию циклов:

For (i = 0; i < 10; i++) { printf("number is %d\n", i); }
В bash-скриптах можно использовать циклы for , описание которых выглядит очень похожим на циклы в стиле C, правда, без некоторых отличий тут не обошлось. Схема цикла при подобном подходе выглядит так:

For ((начальное значение переменной; условие окончания цикла; изменение переменной))
На bash это можно написать так:

For ((a = 1; a < 10; a++))
А вот рабочий пример:

#!/bin/bash for ((i=1; i <= 10; i++)) do echo "number is $i" done
Этот код выведет список чисел от 1 до 10.

Работа цикла в стиле C

Цикл while

Конструкция for - не единственный способ организации циклов в bash-скриптах. Здесь можно пользоваться и циклами while . В таком цикле можно задать команду проверки некоего условия и выполнять тело цикла до тех пор, пока проверяемое условие возвращает ноль, или сигнал успешного завершения некоей операции. Когда условие цикла вернёт ненулевое значение, что означает ошибку, цикл остановится.

Вот схема организации циклов while
while команда проверки условия
do
другие команды
done

Взглянем на пример скрипта с таким циклом:

#!/bin/bash var1=5 while [ $var1 -gt 0 ] do echo $var1 var1=$[ $var1 - 1 ] done
На входе в цикл проверяется, больше ли нуля переменная $var1 . Если это так, выполняется тело цикла, в котором из значения переменной вычитается единица. Так происходит в каждой итерации, при этом мы выводим в консоль значение переменной до его модификации. Как только $var1 примет значение 0, цикл прекращается.

Результат работы цикла while

Если не модифицировать переменную $var1 , это приведёт к попаданию скрипта в бесконечный цикл.

Вложенные циклы

В теле цикла можно использовать любые команды, в том числе - запускать другие циклы. Такие конструкции называют вложенными циклами:

#!/bin/bash for ((a = 1; a <= 3; a++)) do echo "Start $a:" for ((b = 1; b <= 3; b++)) do echo " Inner loop: $b" done done
Ниже показано то, что выведет этот скрипт. Как видно, сначала выполняется первая итерация внешнего цикла, потом - три итерации внутреннего, после его завершения снова в дело вступает внешний цикл, потом опять - внутренний.

Вложенные циклы

Обработка содержимого файла

Чаще всего вложенные циклы используют для обработки файлов. Так, внешний цикл занимается перебором строк файла, а внутренний уже работает с каждой строкой. Вот, например, как выглядит обработка файла /etc/passwd:

#!/bin/bash IFS=$"\n" for entry in $(cat /etc/passwd) do echo "Values in $entry –" IFS=: for value in $entry do echo " $value" done done
В этом скрипте два цикла. Первый проходится по строкам, используя в качестве разделителя знак перевода строки. Внутренний занят разбором строк, поля которых разделены двоеточиями.

Обработка данных файла

Такой подход можно использовать при обработке файлов формата CSV, или любых подобных файлов, записывая, по мере надобности, в переменную окружения IFS символ-разделитель.

Управление циклами

Возможно, после входа в цикл, нужно будет остановить его при достижении переменной цикла определённого значения, которое не соответствует изначально заданному условию окончания цикла. Надо ли будет в такой ситуации дожидаться нормального завершения цикла? Нет конечно, и в подобных случаях пригодятся следующие две команды:
  • break
  • continue

Команда break

Эта команда позволяет прервать выполнение цикла. Её можно использовать и для циклов for , и для циклов while:

#!/bin/bash for var1 in 1 2 3 4 5 6 7 8 9 10 do if [ $var1 -eq 5 ] then break fi echo "Number: $var1" done
Такой цикл, в обычных условиях, пройдётся по всему списку значений из списка. Однако, в нашем случае, его выполнение будет прервано, когда переменная $var1 будет равна 5.

Досрочный выход из цикла for

Вот - то же самое, но уже для цикла while:

#!/bin/bash var1=1 while [ $var1 -lt 10 ] do if [ $var1 -eq 5 ] then break fi echo "Iteration: $var1" var1=$(($var1 + 1)) done
Команда break , исполненная, когда значение $var1 станет равно 5, прерывает цикл. В консоль выведется то же самое, что и в предыдущем примере.

Команда continue

Когда в теле цикла встречается эта команда, текущая итерация завершается досрочно и начинается следующая, при этом выхода из цикла не происходит. Посмотрим на команду continue в цикле for:

#!/bin/bash for ((var1 = 1; var1 < 15; var1++)) do if [ $var1 -gt 5 ] && [ $var1 -lt 10 ] then continue fi echo "Iteration number: $var1" done
Когда условие внутри цикла выполняется, то есть, когда $var1 больше 5 и меньше 10, оболочка исполняет команду continue . Это приводит к пропуску оставшихся в теле цикла команд и переходу к следующей итерации.

Команда continue в цикле for

Обработка вывода, выполняемого в цикле

Данные, выводимые в цикле, можно обработать, либо перенаправив вывод, либо передав их в конвейер. Делается это с помощью добавления команд обработки вывода после инструкции done .

Например, вместо того, чтобы показывать на экране то, что выводится в цикле, можно записать всё это в файл или передать ещё куда-нибудь:

#!/bin/bash for ((a = 1; a < 10; a++)) do echo "Number is $a" done > myfile.txt echo "finished."
Оболочка создаст файл myfile.txt и перенаправит в этот файл вывод конструкции for . Откроем файл и удостоверимся в том, что он содержит именно то, что ожидается.

Перенаправление вывода цикла в файл

Пример: поиск исполняемых файлов

Давайте воспользуемся тем, что мы уже разобрали, и напишем что-нибудь полезное. Например, если надо выяснить, какие именно исполняемые файлы доступны в системе, можно просканировать все папки, записанные в переменную окружения PATH . Весь арсенал средств, который для этого нужен, у нас уже есть, надо лишь собрать всё это воедино:

#!/bin/bash IFS=: for folder in $PATH do echo "$folder:" for file in $folder/* do if [ -x $file ] then echo " $file" fi done done
Такой вот скрипт, небольшой и несложный, позволил получить список исполняемых файлов, хранящихся в папках из PATH .

Поиск исполняемых файлов в папках из переменной PATH

Итоги

Сегодня мы поговорили о циклах for и while в bash-скриптах, о том, как их запускать, как ими управлять. Теперь вы умеете обрабатывать в циклах строки с разными разделителями, знаете, как перенаправлять данные, выведенные в циклах, в файлы, как просматривать и анализировать содержимое директорий.

Если предположить, что вы - разработчик bash-скриптов, который знает о них только то, что изложено в первой части этого цикла статей, и в этой, второй, то вы уже вполне можете написать кое-что полезное. Впереди - третья часть, разобравшись с которой, вы узнаете, как передавать bash-скриптам параметры и ключи командной строки, и что с этим всем делать.

Цикл for немного отличается от аналогов в других языках программирования. Прежде всего, он предоставляет Вам возможность выполнять последовательные действия над "словами" в строке.

Цикл while выполняет кусок кода, если тестируемым выражением является истина; и останавливается при условии, если им является ложь (или внутри исполняемого кода встречается явно заданное прерывание цикла).

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

Если Вы предполагаете, что while и until очень похожи, Вы правы.

7.1 Пример цикла for

#!/bin/bash for i in $(ls); do echo item: $i done

Во второй строке мы представляем i в качестве переменной, которая получает различные значения, содержащиеся в $(ls).

При необходимости третья строка могла бы быть длиннее; или там могло бы находиться несколько строк перед done (4-я строка).

"done" (4-я строка) показывает, что код, в котором используется значение $i, заканчивается и $i получает новое значение.

Данный скрипт не предполагает большой важности. Более полезным применением цикла for было бы использование его для отбора только каких-то определённых файлов в предыдущем примере.

7.2 C-подобный for

fiesh предложил добавить эту форму цикла. Это цикл for, наиболее похожий на for в языках C, Perl и т.п.

#!/bin/bash for i in `seq 1 10`; do echo $i done

7.3 Пример цикла while:

#!/bin/bash COUNTER=0 while [ $COUNTER -lt 10 ]; do echo The counter is $COUNTER let COUNTER=COUNTER+1 done

Данный скрипт "эмулирует" широко известную (в языках C, Pascal, perl и т.д.) структуру "for".

5

Ваш скрипт закодирован опасным образом.

Во-первых, я предполагаю, что вы используете оболочку Bash, так как вы отметили ее "/ bash" и "/ for".

В моем ответе я процитирую это замечательное Bash guide , что, вероятно, является лучшим источником для изучения Bash.

1) Никогда не использовать Command Substitution , из либо рода, без кавычек. Здесь существует большая проблема: использование некотируемого расширения для разделения вывода на аргументы.

Конкретно говоря, это $(find$ DIRWORK -type d -name work) и $(find$ DIR -type f) претерпит Word Splitting , таким образом, если find находит файл с пробелами в его имени, то есть «имя файла», слово Расщепление результат Bash будет проходить 2 аргумент для for команды итерации по, т. е. один для «файла» и один для «имени». В этом случае вы хотите надеяться, что вы получите «файл: нет такого файла или каталога» и «имя: нет такого файла или каталога», а не потенциально наносить им ущерб, если они действительно существуют.

2) По соглашению переменные среды (PATH, EDITOR, SHELL, ...) и внутренние переменные оболочки (BASH_VERSION, RANDOM, ...) полностью капитализируются. Все остальные имена переменных должны быть строчными. Поскольку имена переменных чувствительны к регистру, это соглашение позволяет избежать случайного переопределения экологических и внутренних переменных.

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

My_home="/root/mydir" my_dir=" $my_home/var" dir_work="$ my_home/Local" while IFS= read -r -d "" f; do # I"m guessing that you also want to ignore stderr; # this is where the 2>&1 came from. if lsof -n " $f" | grep "" > /dev/null 2>&1; then echo "hey, I"m safer now!" fi done < <(find "$ dir_work" -type f -print0) while IFS= read -r -d "" f; do echo "2" done < <(find "$dir_work" -type d -name "work" -print0)

Как вы можете видеть, переменная IFS устанавливается равным пуста, таким образом предотвращая read от обрезки переднего и заднего пространства из строки. команда read использует пустую строку (-d "") в качестве разделителя, чтобы читать до он достигает a \ 0. find должен быть m соответственно, поэтому он использует опцию -print0 , чтобы разграничить свои данные с помощью \ 0 вместо новой строки, что, что удивительно и злонамеренно, может быть частью имени файла. Разбиение такого файла на \ n на две части нарушит наш код.

Предыдущий ответ, в котором указано, что find ... | while read name; do ...; done следует использовать для считывания find s выход может быть также плохим. Цикл while выполняется в новой подоболочке со своей копией переменных, скопированной из родителя. Затем эта копия используется для всех, что вам нравится. Когда цикл while закончен, копия подоболочки будет отброшена, а исходные переменные родителя не будут изменены.

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

0

«Никогда не используйте Command Substitution, любого вида, без кавычек». Это просто nitpicking, но можно использовать подстановку команд без кавычек, когда вы устанавливаете переменную: "something = $ (basename" filename with spaces ")". - Smith John 22 апр. 13 2013-04-22 21:43:10

2

For i in $(find$ DIRWORK -type d -name work); do echo "2" done

будет первым выполнить эту линию

Find $DIRWORK -type d -name work

ждать, пока find не завершит выполнение, а затем взять выходной и положить его обратно в петлю for

For i in the output of find; do echo "2" done

только тогда цикл for начнет выполнение.

Так что если find занимает много времени, чтобы закончить цикл for , необходимо подождать много времени, прежде чем он сможет начать.

Попробуйте временные команды find в интерактивном режиме

$ time find $DIRWORK -type d -name work

и посмотреть, как долго это берет.

Также обратите внимание: вы не должны использовать for цикл для перебора имен файлов. Используйте while петлю с read , как это:

Find$ DIRWORK -type d -name work | while read name; do echo "2" done

Бонус: он выполняет цикл while параллельно с find . Это означает, что цикл while выполнит одну итерацию, как только find распечатает одну строку. Для завершения выполнения не нужно ждать find .



Загрузка...