Библиотека сайта rus-linux.net
Разработка сценариев командной оболочки для начинающих. Часть 2: Циклы for
Оригинал: The Beginner’s Guide to Shell Scripting 2: For Loops
Автор: Yatri Trivedi
Дата публикации: 15 июля 2011 г.
Перевод: А.Панин
Дата перевода: 20 октября 2016 г.
Если вы хотите узнать немного больше о разработке сценариев командной оболочки, вам определенно стоит прочитать эту, вторую по счету, статью серии. Для начала обсудим несколько исправлений и улучшений сценария, рассмотренного в прошлой статье серии, после чего перейдем к рассмотрению вопросов использования циклов в рамках сценариев командной оболочки.
Возвращаемся к сценарию datecp
В первой статье серии о разработке сценариев командной оболочки мы создали сценарий, предназначенный для копирования файла в директорию для резервных копий данных с добавлением к его имени информации о текущей дате.
В своем комментарии
В командной оболочке 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 обратил внимание на то, что при копировании кода с данной страницы веб-сайта (а также любой другой страницы произвольного Интернет-ресурса) с целью последующей вставки в открытый файл сценария следует обращать внимание на то, что в некоторых случаях символы дефисов и кавычек автоматически заменяются на лучше выглядящие "типографские эквиваленты". Разумеется, мы делаем все возможное, чтобы код можно было просто копировать и вставлять в область редактирования текстового редактора ;-)
Другой комментатор,
tastyfile.mp3.07_14_11-12.34.56
мы получим имя файла:
tastyfile.07_14_11-12.34.56.mp3
которое будет более удобным для большинства пользователей. Его вариант сценария размещен в
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
.
Этот файл читается командной оболочкой 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
После запуска сценария вы должны увидеть список всех перечисленных элементов списка.
Очень просто, не так ли? Давайте посмотрим, что случится в том случае, если мы немного изменим этот сценарий. Приведите код сценария к следующему виду:
for i in *; do echo "$i" done
Если вы снова запустите этот сценарий, вы получите список всех файлов из текущей директории.
Теперь давайте заменим команду echo
на что-либо более полезное, скажем, команду zip
. По сути, мы будем добавлять файлы в архив. Конечно, же нам придется добавить еще несколько аргументов!
for i in $@; do zip archive "$i" done
В сценарии появилась новая синтаксическая конструкция! Последовательность символов "$@"
является ссылкой на "$1 $2 $3 ... $n"
. Другими словами, это ссылка на полный список аргументов, переданных сценарию. Теперь посмотрите, что случится при запуске сценария с передачей путей к нескольким файлам.
Вы можете ознакомиться со списком файлов из моей текущей директории. Я использовал шесть аргументов, после чего каждый из файлов был добавлен в архив с именем "archive.zip"
. Просто, не так ли?
Циклы for
являются крайне полезными. Благодаря их существованию вы сможете применять функции по отношению к наборам файлов, например, вы сможете поместить все файлы, пути к которым переданы сценарию в формате аргументов, в zip-фрхив, переместить оригиналы в отдельную директорию и осуществить автоматическое безопасное копирование созданного zip-архива на удаленный компьютер. Если вы создадите файлы ключей для SSH, вам не даже придется вводить пароль, причем вы также сможете модифицировать сценарий с целью удаления созданного zip-архива после его копирования!
Использование циклов for
значительно упрощает выполнение последовательностей действий по отношению к файлам из заданной директории. Вы сможете скомбинировать несколько команд с аргументами для обработки файлов из списка и это всего лишь вершина айсберга.
В заключение мне хотелось бы узнать мнение начинающих разработчиков сценариев командной оболочки Bash: "Есть ли у вас какие-либо предложения? Вы уже создали какие-либо полезные сценарии, использующие циклы? Вы хотите поделиться своим мнением о статьях серии?" Вы можете написать обо всем этом, а также помочь другим начинающим разработчикам в разделе комментариев!