Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Разработка сценариев командной оболочки для начинающих. Часть 2: Циклы for

Оригинал: The Beginner’s Guide to Shell Scripting 2: For Loops
Автор: Yatri Trivedi
Дата публикации: 15 июля 2011 г.
Перевод: А.Панин
Дата перевода: 20 октября 2016 г.

Разработка сценариев командной оболочки для начинающих. Часть 2: Циклы for

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

Возвращаемся к сценарию datecp

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

В своем комментарии Samuel Dionne-Riel рассказал о том, что существует гораздо лучший способ обработки значений переменных:

В командной оболочке Bash аргументы разделяются с помощью пробелов, причем при раскрытии команд символы пробелов также обрабатываются в полном объеме. Команда cp $1 $2.$date_formatted из вашего сценария будет корректно функционировать лишь тогда, когда значения раскрывающихся переменных не будут содержать символов пробелов. Если же вы осуществите вызов сценария datecp "my old name" "my new name", будет сформирована команда cp my new name my old name.date, содержащая 6 аргументов.

Для того, чтобы обойти данную проблему, следует использовать следующую строку в качестве последней строки сценария: cp "$1" "$2.$date_formatted".

Очевидно, что замена команды:

cp -iv $1 $2.$date_formatted

на команду:

cp -iv "$1" "$2".$date_formatted

позволит избежать описанного поведения при использовании путей к файлам, содержащим символы пробела в именах. Также Samuel обратил внимание на то, что при копировании кода с данной страницы веб-сайта (а также любой другой страницы произвольного Интернет-ресурса) с целью последующей вставки в открытый файл сценария следует обращать внимание на то, что в некоторых случаях символы дефисов и кавычек автоматически заменяются на лучше выглядящие "типографские эквиваленты". Разумеется, мы делаем все возможное, чтобы код можно было просто копировать и вставлять в область редактирования текстового редактора ;-)

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

tastyfile.mp3.07_14_11-12.34.56

мы получим имя файла:

tastyfile.07_14_11-12.34.56.mp3

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

date_formatted=$(date +%Y-%m-%d_%H.%M%S)
file_extension=$(echo "$1"|awk -F . '{print $NF}')
file_name=$(basename $1 .$file_extension)

cp -iv $1 $file_name-$date_formatted.$file_extension

Я немного изменил форматирование, но вы без труда обнаружите, что Myles использовал команду для получения информации о текущей дате в строке 1. Однако, в строке 2 он использует команду "echo" с первым аргументом сценария для вывода имени файла. Далее он использует программный канал для передачи вывода этой команды на вход другой команды. Этой другой командой является команда "awk", основанная на одноименной утилите для разбора данных в соответствии с заданным шаблоном. Благодаря использованию флага -F утилита получает информацию о том, что следующим символом (после пробела) является так называемый "символ разделения полей". В данном случае это символ точки.

После получения данной информации утилита awk может сделать вывод о том, что имя файла "tastyfile.mp3" состоит из двух полей: "tastyfile" и "mp3". Наконец, он использует строку форматирования:

'{print $NF}'

для вывода значения последнего поля. В том случае, если имя файла содержит несколько символов точки, awk разделит его на несколько полей и все равно выведет значение последнего поля, которое и будет его расширением.

В строке 3 создается новая переменная для хранения имени файла и используется команда "basename" для получения всего имени файла из переменной $1 без расширения. Для этой цели утилите basename в качестве аргумента передается значение переменной $1, после чего добавляется символ пробела и значение переменной, в которой было сохранено расширение файла. Добавление расширения файла происходит автоматически ввиду того, что оно было ранее сохранено в переменной в строке 2. Таким образом, оригинальное имя файла:

tastyfile.mp3

будет превращено в:

tastyfile

После чего в последней строке Myles использует команду для окончательного формирования нового имени файла. Обратите внимание на то, что в данном фрагменте кода нет ссылки на переменную $2, содержащую второй аргумент сценария. Это объясняется тем, что данный сценарий осуществляет копирование файла с заданным именем в текущую директорию. Отличная работа, Samuel и Myles!

Запуск сценариев и переменная $PATH

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

./script
~/bin/script

Но в том случае, если вы разместите ваши сценарии в директории ~/bin/, вы сможете просто вводить их имена для исполнения.

Комментаторы достаточно долго полемизировали на тему того, насколько корректным является данное решение, так как данная директория по умолчанию не создается ни в одном из современных дистрибутивов Linux. Более того, ни в одном из современных дистрибутивов путь к этой директории не добавляется в список путей, хранящийся в переменной окружения $PATH, что необходимо для запуска сценариев по аналогии с системными командами. Я был в замешательстве, ведь после проверки значения директории $PATH оказалось, что комментаторы были правы, но при этом у меня корректно работали все сценарии. В конце концов я выяснил причину такого поведения: во многих современных дистрибутивах Linux в домашней директории каждого из пользователей создается специальный файл с именем .profile.

Специальный файл с именем .profile

Этот файл читается командной оболочкой Bash (в том случае, если в домашней директории пользователя нет файла .bash_profile), причем в его конце расположен специальный фрагмент кода, добавляющий директорию ~/bin/ в список директорий, хранящийся в переменной $PATH. Таким образом, данная тайна была раскрыта. В остальных статьях серии я продолжу размещать сценарии в директории ~/bin/, так как мы разрабатываем пользовательские сценарии, которые должны исполняться пользователями. И, как оказалось, нам не придется разбираться с значением переменной окружения $PATH вручную для того, чтобы сценарии корректно исполнялись.

Повторение команд с помощью циклов

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

Базовая синтаксическая структура цикла for выглядит следующим образом:

for ПЕРЕМЕННАЯ in СПИСОК; do
команда1
команда2
…
командаn
done

ПЕРЕМЕННАЯ может быть любой переменной, хотя, в соответствии с негласным соглашением, чаще всего используется переменная с именем в нижнем регистре "i". СПИСОК является списком элементов; вы можете самостоятельно перечислить элементы этого списка (разделяя их с помощью символа пробела), указать внешний текстовый файл или использовать символ звездочки (*) для перечисления всех файлов из текущей директории. В соответствии с соглашением, команды из тела цикла выделяются с помощью отступов для упрощения чтения кода с вложенными синтаксическими конструкциями, то есть, выделения циклов, вложенных в другие циклы (да, вы сможете использовать циклы, расположенные в других циклах).

Так как для разделения элементов списков используются символы пробелов, то есть, каждый пробел указывает на необходимость перемещения к следующему элементу списка, файлы, в именах которых используются символы пробела, не будут обрабатываться корректно. На текущий момент я предлагаю работать с файлами, в именах которых нет символов пробела. Начнем с разработки простого сценария, который будет выводить имена файлов из текущей директории. Создайте новый файл сценария с именем "loopscript" в вашей директории ~/bin/. Если вы не помните как сделать это (а также, как сделать его исполняемым и добавить хэш-банг), обратитесь к вводной статье серии.

После этого добавьте в этот файл сценария следующий код:

for i in item1 item2 item3 item4 item5 item6; do
echo "$i"
done

Код сценария loopscript

После запуска сценария вы должны увидеть список всех перечисленных элементов списка.

Вывод сценария loopscript

Очень просто, не так ли? Давайте посмотрим, что случится в том случае, если мы немного изменим этот сценарий. Приведите код сценария к следующему виду:

for i in *; do
echo "$i"
done

Вариант сценария loopscript для вывода имен файлов

Если вы снова запустите этот сценарий, вы получите список всех файлов из текущей директории.

Вывод модифицированного сценария loopscript

Теперь давайте заменим команду echo на что-либо более полезное, скажем, команду zip. По сути, мы будем добавлять файлы в архив. Конечно, же нам придется добавить еще несколько аргументов!

for i in $@; do
zip archive "$i"
done

Вариант сценария loopscript для архивации файлов

В сценарии появилась новая синтаксическая конструкция! Последовательность символов "$@" является ссылкой на "$1 $2 $3 ... $n". Другими словами, это ссылка на полный список аргументов, переданных сценарию. Теперь посмотрите, что случится при запуске сценария с передачей путей к нескольким файлам.

Вывод модифицированного сценария loopscript

Вы можете ознакомиться со списком файлов из моей текущей директории. Я использовал шесть аргументов, после чего каждый из файлов был добавлен в архив с именем "archive.zip". Просто, не так ли?

Циклы for являются крайне полезными. Благодаря их существованию вы сможете применять функции по отношению к наборам файлов, например, вы сможете поместить все файлы, пути к которым переданы сценарию в формате аргументов, в zip-фрхив, переместить оригиналы в отдельную директорию и осуществить автоматическое безопасное копирование созданного zip-архива на удаленный компьютер. Если вы создадите файлы ключей для SSH, вам не даже придется вводить пароль, причем вы также сможете модифицировать сценарий с целью удаления созданного zip-архива после его копирования!

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

В заключение мне хотелось бы узнать мнение начинающих разработчиков сценариев командной оболочки Bash: "Есть ли у вас какие-либо предложения? Вы уже создали какие-либо полезные сценарии, использующие циклы? Вы хотите поделиться своим мнением о статьях серии?" Вы можете написать обо всем этом, а также помочь другим начинающим разработчикам в разделе комментариев!