Advanced Bash-Scripting Guide
bash (от англ. Bourne again shell, каламбур «Born again» shell — «возрождённый» shell) — усовершенствованная и модернизированная вариация командной оболочки Bourne shell. Одна из наиболее популярных современных разновидностей командной оболочки UNIX. Особенно популярна в среде Linux, где она часто используется в качестве предустановленной командной оболочки.
Bash — это командный процессор, работающий, как правило, в интерактивном режиме в текстовом окне. Bash также может читать команды из файла, который называется скриптом (или сценарием). Как и все Unix-оболочки, он поддерживает автодополнение имён файлов и директорий, подстановку вывода результата команд, переменные, контроль за порядком выполнения, операторы ветвления и цикла. Ключевые слова, синтаксис и другие основные особенности языка были заимствованы из sh. Другие функции, например, история, были скопированы из csh и ksh. Bash в основном удовлетворяет стандарту POSIX, но с рядом расширений.
Название «bash» является акронимом от Bourne-again-shell («ещё-одна-командная-оболочка-Борна») и представляет собой игру слов: Bourne-shell — одна из популярных разновидностей командной оболочки для UNIX (sh), автором которой является Стивен Борн (1978), усовершенствована в 1987 году Брайаном Фоксом. Фамилия Bourne (Борн) перекликается с английским словом born, означающим «родившийся», отсюда: рождённая-вновь-командная оболочка.
В сентябре 2014 года в bash была обнаружена широко эксплуатируемая уязвимость Bashdoor.
Содержание
- 1 Часть первая. Ввведение
- 2 Часть вторая. Основы
- 3 Часть третья. Углублённый материал
- 3.1 К вопросу о переменных
- 3.2 Циклы и ветвления
- 3.3 Внутренние команды
- 3.4 Внешние команды, программы и утилиты
- 3.4.1 Базовые команды
- 3.4.2 Более сложные команды
- 3.4.3 Команды для работы с датой и временем
- 3.4.4 Команды обработки текста
- 3.4.5 Команды для работы с файлами и архивами
- 3.4.6 Команды для работы с сетью
- 3.4.7 Команды управления терминалом
- 3.4.8 Команды выполнения математических операций
- 3.4.9 Прочие команды
- 3.5 Команды системного администрирования
- 3.6 Подстановка команд
- 3.7 Арифметические подстановки
- 3.8 Перенаправление ввода/вывода
- 3.9 Встроенные документы
- 4 Часть четвёртая. Материал повышенной сложности
- 4.1 Регулярные выражения
- 4.2 Подоболочки или Subshells
- 4.3 Ограниченный режим командной оболочки
- 4.4 Подстановка процессов
- 4.5 Функции
- 4.6 Псевдонимы
- 4.7 Списки команд
- 4.8 Массивы
- 4.9 Файлы
- 4.10 /dev и /proc
- 4.11 /dev/zero и /dev/null
- 4.12 Отладка сценариев
- 4.13 Необязательные параметры(ключи)
- 4.14 Широко распространенные ошибки
- 4.15 Стиль программирования
- 4.16 Разное
- 4.16.1 Интерактивный и неинтерактивный режим работы
- 4.16.2 Сценарии-обертки
- 4.16.3 Операции сравнения: Альтернативные решения
- 4.16.4 Рекурсия
- 4.16.5 "Цветные" сценарии
- 4.16.6 Оптимизация
- 4.16.7 Разные советы
- 4.16.8 Проблемы безопасности
- 4.16.9 Проблемы переносимости
- 4.16.10 Сценарии командной оболочки под Windows
- 4.17 Bash, версия 2
- 5 Замечания и дополнения
- 6 Библиография
- 6.1 Дополнительные примеры сценариев
- 6.2 Справочная информация
- 6.3 Маленький учебник по Sed и Awk
- 6.4 Коды завершения, имеющие предопределённый смысл
- 6.5 Подробное введение в операции ввода-вывода и перенаправление ввода-вывода
- 6.6 Системные каталоги
- 6.7 Локализация
- 6.8 История команд
- 6.9 Пример файла .bashrc
- 6.10 Преобразование пакетных (*.bat) файлов DOS в сценарии командной оболочки
- 6.11 Упражнения
- 6.12 Хронология
Часть первая. Ввведение
Shell — это командная оболочка. Но это не просто промежуточное звено между пользователем и операционной системой, это ещё и мощный язык программирования. Программы на языке shell называют сценариями, или скриптами. Фактически, из скриптов доступен полный набор команд, утилит и программ Unix. Если этого недостаточно, то к вашим услугам внутренние команды shell — условные операторы, операторы циклов и пр., которые увеличивают мощь и гибкость сценариев. Shell-скрипты исключительно хороши при программировании задач администрирования системы и др., которые не требуют для своего создания полновесных языков программирования.
Зачем необходимо знание языка Shell?
Знание языка командной оболочки является залогом успешного решения задач администрирования системы. Даже если вы не предполагаете заниматься написанием своих сценариев. Во время загрузки Linux выполняется целый ряд сценариев из /etc/rc.d, которые настраивают конфигурацию операционной системы и запускают различные сервисы, поэтому очень важно четко понимать эти скрипты и иметь достаточно знаний, чтобы вносить в них какие либо изменения. Язык сценариев легок в изучении, в нем не так много специфических операторов и конструкций.
Синтаксис языка достаточно прост и прямолинеен, он очень напоминает команды, которые приходится вводить в командной строке. Короткие скрипты практически не нуждаются в отладке, и даже отладка больших скриптов отнимает весьма незначительное время.
Shell-скрипты очень хорошо подходят для быстрого создания прототипов сложных приложений, даже не смотря на ограниченный набор языковых конструкций и определенную "медлительность". Такой метод позволяет детально проработать структуру будущего приложения, обнаружить возможные"ловушки" и лишь затем приступить к кодированию на C, C++, Java, или Perl.
Скрипты возвращают нас к классической философии Unix — "разделяй и властвуй" т.е. разделение сложного проекта на ряд простых подзадач. Многие считают такой подход наилучшим или, по меньшей мере, наиболее эстетичным способом решения возникающих проблем, нежели использование нового поколения языков — "всё-в-одном", таких как Perl.
Для каких задач неприменимы скрипты:
- для ресурсоемких задач, особенно когда важна скорость исполнения (поиск, сортировка и т.п.)
- для задач, связанных с выполнением математических вычислений, особенно это касается вычислений с плавающей запятой, вычислений с повышенной точностью, комплексных чисел(для таких задач лучше использовать C++ или FORTRAN)
- для кросс-платформенного программирования (для этого лучше подходит язык C)
- для сложных приложений, когда структурирование является жизненной необходимостью(контроль за типами переменных, прототипами функций и т.п.)
- для целевых задач, от которых может зависеть успех предприятия
- когда во главу угла поставлена безопасность системы, когда необходимо обеспечить целостность системы и защитить её от вторжения, взлома и вандализма
- для проектов, содержащих компоненты, очень тесно взаимодействующие между собой
- для задач, выполняющих огромный объем работ с файлами
- для задач, работающих с многомерными массивами
- когда необходимо работать со структурами данных, такими как связанные списки или деревья
- когда необходимо предоставить графический интерфейс с пользователем (GUI)
- когда необходим прямой доступ к аппаратуре компьютера
- когда необходимо выполнять обмен через порты ввода-вывода или сокеты
- когда необходимо использовать внешние библиотеки
- для проприетарных, "закрытых" программ (скрипты представляют из себя исходные тексты программ, доступные для всеобщего обозрения)
Если выполняется хотя бы одно из вышеперечисленных условий, то вам лучше обратиться к более мощным скриптовым языкам программирования, например Perl, Tcl, Python, Ruby или к высокоуровневым компилирующим языкам — C, C++ или Java. Но даже в этом случае, создание прототипа приложения на языке shell может существенно облегчить разработку.
Название BASH — это аббревиатура от "Bourne-Again Shell" и игра слов от, ставшего уже классикой,"Bourne Shell" Стефена Бурна (Stephen Bourne). В последние годы BASH достиг такой популярности,что стал стандартной командной оболочкой de facto для многих разновидностей Unix. Большинство принципов программирования на BASH одинаково хорошо применимы и в других командных оболочках, таких как Korn Shell (ksh), от которой Bash позаимствовал некоторые особенности,
и CShell и его производных(Примечательно, что C Shell не рекомендуется к использованию из-за отдельных проблем, отмеченных Томом Кристиансеном (Tom Christiansen) в октябре 1993 года на Usenet post).
Далее, в тексте документа вы найдете большое количество примеров скриптов, иллюстрирующихвозможности shell. Все примеры -- работающие. Они были протестированы, причем некоторые из них могут пригодиться в повседневной работе. Уважаемый читатель может "поиграть" с рабочим кодом скриптов, сохраняя их в файлы, с именами scriptname.sh.
Не забудьте выдать этим файлам право на исполнение (chmod u+rx scriptname.sh), после чего сценарии можно будет запустить на исполнение и проверить результат их работы. Вам следует помнить, что описание некоторых примеров следует после исходного кода этого примера, поэтому, прежде чем запустить сценарий у себя — ознакомьтесь с его описанием. Скрипты были написаны автором книги, если не оговаривается иное.
Для начала о Sha-Bang
В простейшем случае, скрипт — это ни что иное, как простой список команд системы, записанный в файл. Создание скриптов поможет сохранить ваше время и силы, которые тратятся на ввод последовательности команд всякий раз, когда необходимо их выполнить.
Пример. cleanup: Сценарий очистки лог-файлов в /var/log
# cleanup # Для работы сценария требуются права root. cd /var/log cat /dev/null > messages cat /dev/null > wtmp echo "Лог-файлы очищены."
Пример. cleanup: Расширенная версия предыдущего сценария.
#!/bin/bash # cleanup, version 2 # Для работы сценария требуются права root. LOG_DIR=/var/log ROOT_UID=0 # Только пользователь с $UID 0 имеет привилегии root. LINES=50 # Количество сохраняемых строк по-умолчанию. E_XCD=66 # Невозможно сменить каталог? E_NOTROOT=67 # Признак отсутствия root-привилегий. if [ "$UID" -ne "$ROOT_UID" ]; then echo "Для работы сценария требуются права root." exit $E_NOTROOT fi if [ -n "$1" ]; then # Проверка наличия аргумента командной строки lines=$1 else lines=$LINES # Значение по-умолчанию, если число не задано в командной строке fi # Stephane Chazelas предложил следующее, #+ для проверки корректности аргумента, переданного из командной строки, #+ правда это достаточно сложно для данного руководства. # # E_WRONGARGS=65 # Не числовой аргумент # # case "$1" in # "" ) lines=50;; # *[!0-9]*) echo "Usage: `basename $0` file-to-cleanup"; exit $E_WRONGARGS;; # * ) lines=$1;; # esac # #* Конец проверки корректности аргумента cd $LOG_DIR if [ `pwd` != "$LOG_DIR" ]; then # или if [ "$PWD" != "$LOG_DIR" ] # не в /var/log? echo "Невозможно перейти в каталог $LOG_DIR." exit $E_XCD fi # Проверка каталога перед очисткой лог-файлов. # более эффективный вариант: # # cd /var/log || { # echo "Невозможно перейти в требуемый каталог." >&2# exit $E_XCD; # } tail -$lines messages > mesg.temp # Сохранить последние строки в лог-файле. mv mesg.temp messages # cat /dev/null > messages #* Необходимость этой команды отпала, поскольку очистка выполняется выше. cat /dev/null > wtmp # команды ': > wtmp' и '> wtmp' имеют тот же эффект. echo "Лог-файлы очищены." exit 0 # Возвращаемое значение 0 #+ указывает на успешное завершение работы сценария.
Если вы не желаете полностью вычищать системные логи, то выше представлена улучшенная версия предыдущего сценария. Здесь сохраняются последние несколько строк (по-умолчанию — 50).
Если файл сценария начинается с последовательности #!, которая в мире Unix называется sha-bang, то это указывает системе какой интерпретатор следует использовать для исполнения сценария. Это двухбайтовая последовательность, или — специальный маркер, определяющий тип сценария, в данном случае — сценарий командной оболочки (см. man magic). Более точно, sha-bang определяет интерпретатор, который вызывается для исполнения сценария, это может быть командная оболочка(shell), иной интерпретатор или утилита:
#!/bin/bash #!/usr/bin/perl #!/usr/bin/tcl #!/bin/sed -f #!/usr/awk -f
Каждая, из приведенных выше сигнатур, приводит к вызову различных интерпретаторов, будь то /bin/sh — командный интерпретатор по-умолчанию (bash для Linux-систем), либо иной. При переносе сценариев с сигнатурой #!/bin/sh на другие Unix системы, где в качестве командного интерпретатора задан другой shell, вы можете лишиться некоторых особенностей, присущих bash. Поэтому такие сценарии должны быть POSIX совместимыми.
Обратите внимание на то, что сигнатура должна указывать правильный путь к интерпретатору, впротивном случае вы получите сообщение об ошибке -- как правило это "Command not found".
Сигнатура #! может быть опущена, если вы не используете специфичных команд. Во втором примере(см. выше) использование сигнатуры #! обязательно, поскольку сценарий использует специфичную конструкцию присваивания значения переменной lines=50. Ещё раз замечу, что сигнатура #!/bin/sh вызывает командный интерпретатор по-умолчанию — /bin/bash в Linux-системах.
В данном руководстве приветствуется модульный подход к построению сценариев. Записывайте, собирайте свою коллекцию участков кода, который может вам встретиться. В конечном итоге вы соберете свою "библиотеку" подпрограмм, которые затем сможете использовать при написании своих сценариев. Например, следующий отрывок сценария проверяет количество аргументов в командной строке:
if [ $# -ne Number_of_expected_args ]; then echo "Usage: `basename $0` whatever" exit $WRONG_ARGS fi
Запуск сценария
Запустить сценарий можно командой sh scriptname или bash scriptname. (Не рекомендуется запуск сценария командой sh < scriptname, поскольку это запрещает использование устройства стандартного ввода stdin в скрипте). Более удобный вариант — сделать файл скрипта исполняемым, командой chmod.
Это:
chmod 555 scriptname (выдача прав на чтение/исполнение любому пользователю в системе)
или
chmod +rx scriptname (выдача прав на чтение/исполнение любому пользователю в системе)
chmod u+rx scriptname (выдача прав на чтение/исполнение только "владельцу" скрипта)
После того, как вы сделаете файл сценария исполняемым, вы можете запустить его примерно такой командой ./scriptname.
Если, при этом, текст сценария начинается с корректной сигнатуры ("sha-bang"), то для его исполнения будет вызван соответствующий интерпретатор.
И наконец, завершив отладку сценария, вы можете поместить его в каталог /usr/local/bin(естественно, что для этого вы должны обладать правами root), чтобы сделать его доступным для себя и других пользователей системы. После этого сценарий можно вызвать, просто напечатав название файла в командной строке и нажав клавишу [ENTER].
Упражнения
1) Системные администраторы часто создают скрипты для автоматизации своего труда. Подумайте, для выполнения каких задач могут быть написаны сценарии.
2) Напишите сценарий, который выводит дату, время, список зарегистрировавшихся пользователей, и uptime системы и сохраняет эту информацию в системном журнале.
Часть вторая. Основы
Служебные символы
Служебные символы, используемые в текстах сценариев:
- # — Комментарии.
Строки, начинающиеся с символа # (за исключением комбинации #!) являются комментариями.
# Эта строка — комментарий.
Комментарии могут располагаться и в конце строки с исполняемым кодом.
echo "Далее следует комментарий." # Это комментарий.
Комментариям могут предшествовать пробелы (пробел, табуляция).
# Перед комментарием стоит символ табуляции.
Исполняемые команды не могут следовать за комментарием в той же самой строке. Пока что еще не существует способа отделения комментария от "исполняемого кода", следующего за комментарием в той же строке.
Само собой разумеется, экранированный символ # в операторе echo не воспринимается как начало комментария. Более того, он может использоваться в операциях подстановки параметров и в константных числовых выражениях.
echo "Символ # не означает начало комментария." echo 'Символ # не означает начало комментария.' echo Символ \# не означает начало комментария. echo А здесь символ # означает начало комментария. echo ${PATH#*:} # Подстановка — не комментарий. echo $(( 2#101011 )) # База системы счисления — не комментарий.
Двойные кавычки("), одинарная кавычка(') и символ обратного "слеша"(\) экранируют действие символа #.
В операциях поиска по шаблону символ # так же не воспринимается как начало комментария.
- ; — Разделитель команд. [Точка-с-запятой]
Позволяет записывать две и более команд в одной строке.
echo hello; echo there if [ -x "$filename" ]; then # Обратите внимание: "if" И "then" разделены точкой с запятой. # Почему? echo "Файл $filename найден."; cp $filename $filename.bak else echo "Файл $filename не найден."; touch $filename fi; echo "Конец."
Следует отметить, что символ ; иногда так же как и # необходимо экранировать.
- ;; — Ограничитель в операторе выбора case. [Двойная-точка-с-запятой]
case "$variable" in abc) echo "$variable = abc" ;; xyz) echo "$variable = xyz" ;; esac
- . — Команда "точка"(.)
Эквивалент команды source. Это встроенная команда bash. "точка" может являться частью имени файла. Если имя файла начинается с точки, то это "скрытый" файл, т.е. команда ls при обычных условиях его не отображает:
Если подразумевается имя каталога, то одна точка означает текущий каталог и две точки — каталог уровнем выше или родительский каталог:
Символ . ("точка") довольно часто используется для обозначения каталога назначения в операциях копирования/перемещения файлов.
bash$ cp /home/bozo/current_work/junk/* .
Символ . ("точка") в операциях поиска. При выполнении поиска по шаблону, в регулярных выражениях, символ . ("точка") обозначает одиночный символ.
- " — Двойные кавычки(")
В строке "STRING", ограниченной двойными кавычками не выполняется интерпретация большинства служебных символов, которые могут находиться в строке.
- ' — Одинарные кавычки(')
Одинарные кавычки 'STRING' экранируют все служебные символы в строке STRING. Это более строгая форма экранирования.
- , — Запятая(,)
Оператор запятая(,) используется для вычисления серии арифметических выражений. Вычисляются все выражения, но возвращается результат последнего выражения.
let "t2 = ((a = 9, 15 / 3))" # Присваивает значение переменной "a" и вычисляет "t2".
- \ — Обратный слэш(\)
Комбинация \X "экранирует" символ X. Аналогичный эффект имеет комбинация с "одинарными кавычками", т.е. 'X'. Символ \ может использоваться для экранирования кавычек " и '.
- / — Слэш(/)
Разделитель, используемый в указании пути к каталогам и файлам. Слэш отделяет элементы пути к каталогам и файлам (например, /home/bozo/projects/Makefile). В арифметических операциях — это оператор деления.
- ` — Обратная кавычка(`)
С помощью символа обратной кавычки(`) осуществляется подстановка команд. Обратные кавычки могут использоваться для записи в переменную команды `command`.
- : — Двоеточие(:)
Пустая команда. Это эквивалент операции "NOP" (no op, нет операции). Может рассматриваться как синоним встроенной команды true. Команда ":" так же является встроенной командой Bash, которая всегда возвращает "true" (0).
: echo $? # 0
Бесконечный цикл:
while : do operation-1 operation-2 ... operation-n done # То же самое: # while true # do # ... # done
Символ-заполнитель в условном операторе if/then:
if condition then : # Никаких действий не производится и управление передаётся дальше else take-some-action fi
Как символ-заполнитель в операциях, которые предполагают наличие двух операндов:
: ${username=`whoami`} # ${username=`whoami`} без символа : выдает сообщение об ошибке, # если "username" не является командой...
Как символ-заполнитель для оператора вложенного документа. В операциях с подстановкой параметров:
: ${HOSTNAME?} ${USER?} ${MAIL?} #Вывод сообщения об ошибке, если одна или более переменных не определены.
В операциях замены подстроки с подстановкой значений переменных. В комбинации с оператором > (оператор перенаправления вывода), усекает длину файла до нуля. Если указан несуществующий файл — то он создаётся.
: > data.xxx # Файл "data.xxx" — пуст # Тот же эффект имеет команда cat /dev/null >data.xxx # Однако в данном случае не производится создание нового процесса, поскольку ":" является встроенной командой.
В комбинации с оператором >> — если задано имя существующего файла, то эта комбинация на него никак не влияет (: >> target_file). Если задано имя несуществующего файла, то он создаётся. Вышеизложенное применимо только к обычным файлам и неприменимо к конвейерам, символическим ссылкам и другим специальным файлам. Символ : может использоваться для создания комментариев, хотя и не рекомендуется. Если строка комментария начинается с символа #, то такая строка не проверяется интерпретатором на наличие ошибок. Однако в случае оператора : это не так.
: Это комментарий, который генерирует сообщение об ошибке, ( if [ $x -eq 3] ).
Символ : может использоваться как разделитель полей в /etc/passwd и переменной $PATH.
bash$ echo $PATH /usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/sbin:/usr/sbin:/usr/games
- ! — Восклицательный знак(!)
Инверсия (или логическое отрицание) используемое в условных операторах. Оператор ! инвертирует код завершения команды, к которой он применен. Также используется для логического отрицания в операциях сравнения, например, операция сравнения "равно"(=), при использовании оператора отрицания, преобразуется в операцию сравнения — "не равно" ( != ). Символ ! является зарезервированным ключевым словом BASH. В некоторых случаях символ ! используется для косвенного обращения к переменным. Кроме того, из командной строки оператор ! запускает механизм историй Bash. Примечательно, что этот механизм недоступен из сценариев(т.е. исключительно из командной строки).
- * — Символ "звёздочка"(*)
Символ * ("звёздочка") служит "шаблоном" для подстановки в имена файлов. Одиночный символ * означает любое имя файла в заданном каталоге.
bash$ echo * abs-book.sgml add-drive.sh agram.sh alias.sh
В регулярных выражениях токен * представляет любое количество (в том числе и 0) символов. В арифметических выражениях символ * обозначает операцию умножения. Двойная звёздочка (два символа звёздочки, следующих подряд друг за другом — **),обозначает операцию возведения в степень.
- ? — Вопросительный знак(?)
Оператор проверки условия. В некоторых выражениях символ ? служит для проверки выполнения условия. В конструкциях с двойными скобками, символ ? подобен трёхместному оператору языка C. В выражениях с подстановкой параметра, символ ? проверяет — установлена ли переменная. Символ ? обозначает одиночный символ при подстановке в имена файлов. В регулярных выражениях служит для обозначения одиночного символа.
- $ — Символ доллара($)
Символ $ предшествующий имени переменной, указывает на то, что будет получено значение переменной:
var1=5 var2=23skidoo echo $var1 # 5 echo $var2 # 23skidoo
В регулярных выражениях, символ $ обозначает конец строки. ${ } — подстановка параметра. $*,$@ — параметры командной строки. $? — Переменная "$?" хранит код завершения последней выполненной команды, функции или сценария. $$ — Переменная "$$" хранит id процесса сценария.
- ( ) — Круглые скобки(())
Команды, заключённые в круглые скобки (( )) исполняются в дочернем процессе — subshell.
(a=hello; echo $a)
Переменные, создаваемые в дочернем процессе не видны в "родительском" сценарии. Родительский процесс-сценарий, не может обращаться к переменным, создаваемым в дочернем процессе.
a=123 ( a=321; ) echo "a = $a" # a = 123 #Переменная "a" в скобках подобна локальной переменной.
Круглые скобки (()) также используются для инициализации массивов:
Array=(element1 element2 element3)
(( )) — двойные круглые скобки.Вычисляется целочисленное выражение, заключенное между двойными круглыми скобками (( )).
- {} — Фигурные скобки({})
Команда интерпретируется как список команд, разделённых точкой с запятой, с вариациями, представленными в фигурных скобках. При интерпретации имён файлов (подстановка) используются параметры, заключённые в фигурные скобки.
grep Linux file*.{txt,htm*}# Поиск всех вхождений слова "Linux" # в файлах "fileA.txt", "file2.txt", "fileR.html", "file-87.htm", и пр.
Использование неэкранированных или неокавыченных пробелов внутри фигурных скобок недопустимо!
echo {file1,file2}\ :{\ A," B",' C'} file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C
{} — Известен так же как "вложенный блок", эта конструкция, фактически, создаёт анонимную функцию. Однако, в отличии от обычных функций, переменные, создаваемые во вложенных блоках кода, доступны объемлющему сценарию.
bash$ { local a; a=123; } bash: local: can only be used in a function
a=123 { a=321; } echo "a = $a" # a = 321 (значение, присвоенное во вложенном блоке кода)
Код, заключенный в фигурные скобки, может выполнять перенаправление ввода-вывода.
Пример.: Вложенные блоки и перенаправление ввода-вывода.
#!/bin/bash # Чтение строк из файла /etc/fstab. File=/etc/fstab {read line1 read line2 } < $File echo "Первая строка в $File :" echo "$line1" echo echo "Вторая строка в $File :" echo "$line2" exit 0
Пример.: Сохранение результата исполнения вложенного блока в файл
#!/bin/bash # rpm-check.sh # Запрашивает описание rpm-архива, список файлов, и проверяется возможность установки. # Результат сохраняется в файле. # # Этот сценарий иллюстрирует порядок работы со вложенными блоками кода. SUCCESS=0 E_NOARGS=65 if [ -z "$1" ]; then echo "Порядок использования: `basename $0` rpm-file" exit $E_NOARGS fi { echo echo "Описание архива:" rpm -qpi $1 # Запрос описания. echo echo "Список файлов:" rpm -qpl $1 # Запрос списка. echo rpm -i --test $1 # Проверка возможности установки. if [ "$?" -eq $SUCCESS ]; then echo "$1 может быть установлен." else echo "$1 — установка невозможна!" fi echo }> "$1.test" # Перенаправление вывода в файл. echo "Результаты проверки rpm-архива находятся в файле $1.test" # За дополнительной информацией по ключам команды rpm см. man rpm. exit 0
В отличие от групп команд в (круглых скобках), описаных выше, вложенные блоки кода, заключённые в {фигурные скобки} исполняются в пределах того же процесса, что и сам скрипт (т.е. не вызывают запуск дочернего процесса — subshell)!
{ } \; — pathname, полное имя файла (т.е. путь к файлу и его имя). Чаще всего используется совместно с командой find.
Обратите внимание на то, что символ ; , которым завершается ключ -exec команды find, экранируется обратным слэшем. Это необходимо, чтобы предотвратить его интерпретацию.
- [ ] — Квадратные скобки([ ])
Проверка истинности выражения, заключенного в квадратные скобки [ ]. Примечательно, что [ является частью встроенной команды test (и её синонимом), и не имеет никакого отношения к "внешней" утилите /usr/bin/test.
[[ ]] — Проверка истинности выражения, заключенного между [[ ]] (зарезервированное слово интерпретатора).
[ ] — Элемент массива. При работе с массивами в квадратных скобках указывается порядковый номер того элемента массива, к которому производится обращение.
Array[1]=slot_1 echo ${Array[1]}
[ ] — диапазон символов. В регулярных выражениях, в квадратных скобках задаётся диапазон искомых символов.
- >, &>, >&, >>, < — Перенаправление.
Конструкция scriptname > filename перенаправляет вывод scriptname в файл filename. Если файл filename уже существовал, то его прежнее содержимое будет утеряно.
Конструкция command &> filename перенаправляет вывод команды command, как с stdout, так и с stderr, в файл filename.
Конструкция command >&2 перенаправляет вывод с stdout на stderr.
Конструкция scriptname >> filename добавляет вывод scriptname к файлу filename. Если задано имя несуществующего файла, то он создаётся.
<< — Перенаправление ввода на встроенный документ.
(command)>, <(command) — Подстановка процесса.
В операциях сравнения, символы "<" и ">" обозначают операции сравнения строк. А так же операции сравнения целых чисел.
<, > — Посимвольное ASCII-сравнение.
veg1=carrots veg2=tomatoes if [[ "$veg1" < "$veg2" ]]; then echo "Не смотря на то, что в словаре слово $veg1 предшествует слову $veg2," echo "это никак не отражает мои кулинарные предпочтения." else echo "Интересно. Каким словарем вы пользуетесь?" fi
\<,\> — границы отдельных слов в регулярных выражениях.
bash$ grep '\<the\>' textfile
- | — Конвейер(pipe).
Передает вывод предыдущей команды на ввод следующей или на вход командного интерпретатора shell. Этот метод часто используется для связывания последовательности команд в единую цепочку.
echo ls -l | sh # Передаёт вывод "echo ls -l" командному интерпретатору shell, # тот же результат даёт простая команда "ls -l". cat *.lst | sort | uniq # Объединяет все файлы ".lst", сортирует содержимое и удаляет повторяющиеся строки.
Конвейеры (еще их называют каналами) — это классический способ взаимодействия процессов, с помощью которого stdout одного процесса перенаправляется на stdin другого. Обычно используется совместно с командами вывода, такими как cat или echo, от которых поток данных поступает в "фильтр" (команда, которая на входе получает данные, преобразует их и обрабатывает).
cat $filename | grep $search_word |
В конвейер могут объединяться и сценарии на языке командной оболочки.
#!/bin/bash # uppercase.sh : Преобразование вводимых символов в верхний регистр. tr 'a-z' 'A-Z' # Диапазоны символов должны быть заключены в кавычки # чтобы предотвратить порождение имен файлов от однобуквенных имен файлов. exit 0
А теперь попробуем объединить в конвейер команду ls -l с этим сценарием:
Выход stdout каждого процесса в конвейере должен читаться на входе stdin последующим, в конвейере, процессом. Если этого не делается, то поток данных блокируется, в результате конвейер будет работать не так как ожидается!
cat file1 file2 | ls -l | sort # Вывод команды "cat file1 file2" будет утерян.
Конвейер исполняется в дочернем процессе, а посему — не имеет доступа к переменным сценария.
variable="initial_value" echo "new_value" | read variable echo "variable = $variable" # variable = initial_value
Если одна из команд в конвейере завершается аварийно, то это приводит каварийному завершению работы всего конвейера!
>| — принудительное перенаправление, даже если установлен ключ noclobber option.
- || — логическая операция OR(логическое ИЛИ).
В опрециях проверки условий, оператор || возвращает 0 (success), если один из операндов имеет значение true (ИСТИНА).
- & — Амперсанд
Выполнение задачи в фоне. Команда, за которой стоит &, будет исполняться в фоновом режиме.
bash$ sleep 10 & [1] 850 [1]+ Done sleep 10
В сценариях команды, и даже циклы могут запускаться в фоновом режиме.
Пример.: Запуск цикла в фоновом режиме
#!/bin/bash # background-loop.sh for i in 1 2 3 4 5 6 7 8 9 10 # Первый цикл. do echo -n "$i " done & # Запуск цикла в фоне. # Иногда возможны случаи выполнения этого цикла после второго цикла. echo # Этот 'echo' иногда не отображается на экране. for i in 11 12 13 14 15 16 17 18 19 20 # Второй цикл. do echo -n "$i " done echo # Этот 'echo' иногда не отображается на экране. # ====================================================== # Ожидается, что данный сценарий выведет следующую последовательность: # 1 2 3 4 5 6 7 8 9 10 # 11 12 13 14 15 16 17 18 19 20 # Иногда возможен такой вариант: # 11 12 13 14 15 16 17 18 19 20 # 1 2 3 4 5 6 7 8 9 10 bozo $ # (Второй 'echo' не был выполнен. Почему?) # Изредка возможен такой вариант: # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # (Первый 'echo' не был выполнен. Почему?) # Крайне редко встречается и такое: # 11 12 13 1 2 3 4 5 6 7 8 9 10 14 15 16 17 18 19 20 # Второй цикл начал исполняться раньше первого. exit 0
Команда, исполняемая в пределах сценария в фоне, может подвесить сценарий, ожидая нажатия клавиши. К счастью, это легко "лечится".
- && — логическая операция AND (логическое И).
В операциях проверки условий, оператор && возвращает 0 (success) тогда, и только тогда, когда оба операнда имеют значение true(ИСТИНА).
- - — Знак дефиса (-)
Префикс ключа. С этого символа начинаются опциональные ключи команд:
COMMAND -[Option1][Option2][...] ls -al sort -dfu $filename set -- $variable if [ $file1 -ot $file2 ];then echo "Файл $file1 был создан раньше чем $file2." fi if [ "$a" -eq "$b" ]; then echo "$a равно $b." fi if [ "$c" -eq 24 -a "$d" -eq 47 ]; then echo "$c равно 24, а $d равно 47." fi
- — перенаправление из/в stdin или stdout.
(cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -) # Перемещение полного дерева файлов и подкаталогов из одной директории в другую # спасибо Алану Коксу (Alan Cox) <a.cox@swansea.ac.uk>, за небольшие поправки # 1) cd /source/directory Переход в исходный каталог, содержимое которого будет перемещено # 2) && "И-список": благодаря этому все последующие команды будут выполнены # только тогда, когда 'cd' завершится успешно # 3) tar cf - . ключом 'c' архиватор 'tar' создает новый архив, # ключом 'f' (file) и последующим '-' задается файл архива -- stdout, # в архив помещается текущий каталог ('.') с вложенными подкаталогами. # 4) | конвейер с ... # 5) ( ... ) subshell-ом (дочерним экземпляром командной оболочки) # 6) cd /dest/directory Переход в каталог назначения. # 7) && "И-список", см. выше # 8) tar xpvf - Разархивирование ('x'), с сохранением атрибутов "владельца" и прав доступа ('p') к файлам, # с выдачей более подробных сообщений на stdout ('v'), # файл архива -- stdin ('f' с последующим '-'). # # Примечательно, что 'x' -- это команда, а 'p', 'v' и 'f' -- ключи # Во как! # Более элегантный вариант: # cd source-directory # tar cf - . | (cd ../target-directory; tar xzf -) # #cp -a /source/directory /dest имеет тот же эффект. bunzip2 linux-2.4.3.tar.bz2 | tar xvf - # --разархивирование tar-файла-- | --затем файл передается утилите "tar"-- # Если у вас утилита "tar" не поддерживает работу с "bunzip2", # тогда придется выполнять работу в два этапа, с использованием конвейера. # Целью данного примера является разархивирование тарбола (tar.bz2) с исходными текстами ядра.
Обратите внимание, что в этом контексте - - не самостоятельный оператор Bash, а скорее опция, распознаваемая некоторыми утилитами Unix (такими как tar, cat и т.п.), которые выводят результаты своей работы в stdout.
bash$ echo "whatever" | cat - whatever
В случае, когда ожидается имя файла, тогда - перенаправляет вывод на stdout (вспомните пример с tar cf) или принимает ввод с stdin.
bash$ file Usage: file [-bciknvzL] [-f namefile] [-m magicfiles] file...
Сама по себе команда file без параметров завершается с сообщением об ошибке. Добавим символ - и получим более полезный результат. Это заставит командный интерпретатор ожидать ввода от пользователя.
bash$ file - abc standard input: ASCII text bash$ file - #!/bin/bash standard input: Bourne-Again shell script text executable
Теперь команда принимает ввод пользователя со stdin и анализирует его. Используя передачу stdout по конвейеру другим командам, можно выполнять довольно эффектные трюки, например вставка строк в начало файла. С помощью команды diff — находить различия между одним файлом и частью другого:
grep Linux file1 | diff file2 -
И наконец пример использования служебного символа - с командой tar.
Пример.: Резервное архивирование всех файлов, которые были изменены втечение последних суток
#!/bin/bash # Резервное архивирование (backup) всех файлов в текущем каталоге, # которые были изменены в течение последних 24 часов # в тарболл (tarball) (.tar.gz - файл). BACKUPFILE=backup archive=${1:-$BACKUPFILE} # На случай, если имя архива в командной строке не задано, # т.е. по-умолчанию имя архива -- "backup.tar.gz" tar cvf - `find . -mtime -1 -type f -print` > $archive.tar gzip $archive.tar echo "Каталог $PWD заархивирован в файл \"$archive.tar.gz\"." # Stephane Chazelas заметил, что вышеприведенный код будет "падать" # если будет найдено слишком много файлов # или если имена файлов будут содержать символы пробела. # Им предложен альтернативный код: # ------------------------------------------------------------------- # find . -mtime -1 -type f -print0 | xargs -0 tar rvf "$archive.tar" # используется версия GNU утилиты "find". # find . -mtime -1 -type f -exec tar rvf "$archive.tar" '{}' \; # более универсальный вариант, хотя и более медленный, # зато может использоваться в других версиях UNIX. # ------------------------------------------------------------------- exit 0
Могут возникнуть конфликтные ситуации между опреатором перенаправления - и именами файлов, начинающимися с символа "-". Поэтому сценарий должен проверять имена файлов и предварять их префиксом пути, например, ./-FILENAME, $PWD/-FILENAME или $PATHNAME/-FILENAME.
Если значение переменной начинается с символа "-", то это тоже может быть причиной появления ошибок.
var="-n" echo $var # В данном случае команда приобретет вид "echo -n" и ничего не выведет.
- — предыдущий рабочий каталог. Команда cd - выполнит переход в предыдущий рабочий каталог, путь к которому хранится в переменной окружения $OLDPWD. Не путайте оператор "-" (предыдущего рабочего каталога) с оператором - (переназначения). Ещё раз напомню, что интерпретация символа "-" зависит от контекста, в котором он употребляется. - — Знак минус в арифметических операциях.
- = — Символ равно (=).
Символ равно (=) выступает в качестве оператора присваивания.
a=28 echo $a # 28
В зависимости от контекста применения, символ = может выступать в качестве оператора сравнения.
- + — Символ плюс (+).
Символ плюс (+) выступает в качестве оператора сложения в арифметических операциях. В зависимости от контекста применения, символ + может выступать как оператор регулярного выражения. + - Ключ (опция). Дополнительный флаг для ключей (опций) команд. Отдельные внешние и встроенные команды используют символ "+" для разрешения некоторой опции, а символ "-" — для запрещения.
- % — Символ процента (%).
Модуль (остаток от деления) — арифметическая операция. В зависимости от контекста применения, символ % может выступать в качестве шаблона.
- ~ — Символ тильда (~).
Соответствует содержимому внутренней переменной $HOME. ~bozo — домашний каталог пользователя bozo, а команда ls ~bozo выведет содержимое его домашнего каталога. ~/ — это домашний каталог текущего пользователя, а команда ls ~/ выведет содержимое домашнего каталога текущего пользователя.
bash$ echo ~bozo /home/bozo bash$ echo ~/home/bozo bash$ echo ~/ /home/bozo/ bash$ echo ~: /home/bozo: bash$ echo ~nonexistent-user ~nonexistent-user
~+ — текущий рабочий каталог. Соответствует содержимому внутренней переменной $PWD.
~- — предыдущий рабочий каталог. Соответствует содержимому внутренней переменной $OLDPWD.
- ^ — Символ карет (^).
Начало-строки. В регулярных выражениях символ "^" задает начало строки текста. Управляющий символ изменяет поведение терминала или управляет выводом текста. Управляющий символ набирается с клавиатуры как комбинация CONTROL + <клавиша>.
- Ctrl-B — Курсор — на одну позицию назад (без стирания символа).
- Ctrl-C — Прерывание выполнения процесса.
- Ctrl-D — Выход из командного интерпретатора (log out) (аналог команды exit)."EOF" (признак конца файла). Этот символ может выступать в качестве завершающего привводе с stdin.
- Ctrl-G — "BEL" (звуковой сигнал -- "звонок").
- Ctrl-H — Backspace ("забой") — удаление предыдущего символа.
- Ctrl-I — Горизонтальная табуляция.
- Ctrl-J — Перевод строки.
- Ctrl-K — Вертикальная табуляция.
- Ctrl-L — Перевод формата (очистка экрана (окна) терминала). Аналогична команде clear.
- Ctrl-M — Возврат каретки.
- Ctrl-Q — Resume (XON). Эта комбинация "восстанавливает" работу stdin терминала.
- Ctrl-S — Suspend (XOFF). Эта комбинация "замораживает" stdin терминала. (Для восстановления "работоспособности" — используйте комбинацию Ctrl-Q)
- Ctrl-U — Стирание строки ввода.
- Ctrl-Z — Приостановка процесса.
#!/bin/bash # Вставка символа Ctl-H в строку. a="^H^H" # Два символа Ctl-H (backspace). echo "abcdef" # abcdef echo -n "abcdef$a " # abcd f # Пробел в конце ^ ^ двойной шаг назад. echo -n "abcdef$a" # abcdef # Пробела в конце нет, backspace не работает (почему?). # Результаты могут получиться совсем не те, что вы ожидаете. echo; echo
#!/bin/bash # Спасибо Lee Maschmeyer, за этот пример. read -n 1 -s -p $'Control-M — переводит курсор в начало этой же строки. Нажмите клавишу Enter. \x0d' # Разумеется, что, 'x0d' — это # шестнадцатиричный эквивалент '''''Ctrl-M'''''. echo >&2 # Перевод строки read -n 1 -s -p $'Control-J — переводит курсор в начало другой строки. \x0a' echo >&2 # Control-J — это перевод строки. read -n 1 -s -p $'А Control-K\x0b — вниз.' echo >&2 # Control-K — это вертикальная табуляция. exit 0
- — Символ пробела ( ).
Пробельный символ используется как разделитель команд или переменных. В качестве пробельного символамогут выступать — собственно пробел (space), символ табуляции, символ перевода строки, символ возврата каретки или комбинация из вышеперечисленных символов. В некоторых случаях, таких как присваивание значений переменным, использование пробельных символов недопустимо. Пустые строки никак не обрабатываются командным интерпретатором и могут свободно использоваться для визуального выделения отдельных блоков сценария. $IFS — переменная специального назначения. Содержит символы-разделители полей, используемые некоторыми командами. По-умолчанию — пробельные символы.
Переменные и параметры. Введение.
Переменные — это одна из основ любого языка программирования. Они участвуют в арифметических операциях, в синтаксическом анализе строк и совершенно необходимы для абстрагирования каких-либо величин с помощью символических имен. Физически переменные представляют собой ни что иное как участки памяти, в которые записана некоторая информация.
Подстановка переменных
Когда интерпретатор встречает в тексте сценария имя переменной, то он вместо него подставляет значение этой переменной. Поэтому ссылки на переменные называются подстановкой переменных.
- $
Необходимо всегда помнить о различиях между именем переменной и ее значением. Если variable1 — это имя переменной, то $variable1 — это ссылка на ее значение. "Чистые" имена переменных, без префикса $, могут использоваться только при объявлении переменной, при присваивании переменной некоторого значения, при удалении (сбросе), при экспорте и в особых случаях — когда переменная представляет собой название сигнала. Присваивание может производится с помощью символа = (например: var1=27), инструкцией read и в заголовке цикла (for var2 in 1 2 3).
Заключение ссылки на переменную в двойные кавычки (" ") никак не сказывается на работе механизма подстановки. Этот случай называется "частичные кавычки", иногда можно встретить название "нестрогие кавычки". Одиночные кавычки (' ') заставляют интерпретатор воспринимать ссылку на переменную как простой набор символов, потому в одинарных кавычках операции подстановки не производятся. Этот случай называется "полные", или "строгие" кавычки.
Примечательно, что написание $variable фактически является упрощенной формой написания ${variable}. Более строгая форма записи ${variable} может с успехом использоваться в тех случаях, когда применение упрощенной формы записи порождает сообщения о синтаксических ошибках.
Пример: Присваивание значений переменным и подстановка значений переменных.
#!/bin/bash # Присваивание значений переменным и подстановка значений переменных a=375 hello=$a #------------------------------------------------------------------------- # Использование пробельных символов # с обеих сторон символа "=" присваивания недопустимо. # Если записать "VARIABLE =value", # то интерпретатор попытается выполнить команду "VARIABLE" с параметром "=value". # Если записать "VARIABLE= value", # то интерпретатор попытается установить переменную окружения "VARIABLE" в "" # и выполнить команду "value". #------------------------------------------------------------------------- echo hello # Это не ссылка на переменную, выведет строку "hello". echo $hello echo ${hello} # Идентично предыдущей строке. echo "$hello" echo "${hello}" echo hello="A B C D" echo $hello # A B C D echo "$hello" # A B C D # Здесь вы сможете наблюдать различия в выводе echo $hello и echo "$hello". # Заключение ссылки на переменную в кавычки сохраняет пробельные символы. echo echo '$hello' # $hello # Внутри одинарных кавычек не производится подстановка значений переменных, # т.е. "$" интерпретируется как простой символ. # Обратите внимание на различия, существующие между этими типами кавычек. hello= # Запись пустого значения в переменную. echo "\$hello (пустое значение) = $hello" # Обратите внимание: запись пустого значения — это не то же самое, # что сброс переменной, хотя конечный результат — тот же. # -------------------------------------------------------------- # Допускается присваивание нескольких переменных в одной строке, # если они отделены пробельными символами. # Внимание! Это может снизить читабельность сценария и оказаться непереносимым. var1=variable1 var2=variable2 var3=variable3 echo echo "var1=$var1 var2=$var2 var3=$var3" # Могут возникнуть проблемы с устаревшими версиями "sh". # -------------------------------------------------------------- echo; echo numbers="один два три" other_numbers="1 2 3" # Если в значениях переменных встречаются пробелы, # то использование кавычек обязательно. echo "numbers = $numbers" echo "other_numbers = $other_numbers" # other_numbers = 1 2 3 echo echo "uninitialized_variable = $uninitialized_variable" # Неинициализированная переменная содержит "пустое" значение. uninitialized_variable= # Объявление неинициализированной переменной (то же, что и присваивание пустого значения) echo "uninitialized_variable = $uninitialized_variable" # Переменная содержит "пустое" значение. uninitialized_variable=23 # Присваивание. unset uninitialized_variable # Сброс. echo "uninitialized_variable = $uninitialized_variable" # Переменная содержит "пустое" значение. echo exit 0
Неинициализированная переменная хранит "пустое" значение - не ноль! Использование неинициализированных переменных может приводить к ошибкам разного рода в процессе исполнения. Не смотря на это в арифметических операциях допускается использовать неинициализированные переменные.
echo "$uninitialized" # (пустая строка) let "uninitialized += 5" # Прибавить 5. echo "$uninitialized" # 5 # Заключение: # Неинициализированные переменные не имеют значения, однако # в арифметических операциях за значение таких переменных принимается число 0. # Это недокументированная (и возможно непереносимая) возможность.
Присваивание значений переменным
- =
Оператор присваивания (пробельные символы до и после оператора — недопустимы).
Не путайте с операторами сравнения = и -eq!
Обратите внимание: символ = может использоваться как в качестве оператора присваивания, так и в качестве оператора сравнения, конкретная интерпретация зависит от контекста применения.
Пример: Простое присваивание.
#!/bin/bash # Явные переменные echo # Когда перед именем переменной не употребляется символ '$'? # В операциях присваивания. # Присваивание a=879 echo "Значение переменной \"a\" — $a." # Присваивание с помощью ключевого слова 'let' let a=16+5 echo "Значение переменной \"a\" теперь стало равным: $a." echo # В заголовке цикла 'for' (своего рода неявное присваивание) echo -n "Значения переменной \"a\" в цикле: " for a in 7 8 9 11 do echo -n "$a " done echo echo # При использовании инструкции 'read' (тоже одна из разновидностей присваивания) echo -n "Введите значение переменной \"a\" " read a echo "Значение переменной \"a\" теперь стало равным: $a." echo exit 0
Пример: Присваивание значений переменным простое и замаскированное.
#!/bin/bash a=23 # Простейший случай echo $a b=$a echo $b # Теперь немного более сложный вариант (подстановка команд). a=`echo Hello!` # В переменную 'a' попадает результат работы команды 'echo' echo $a # Обратите внимание на восклицательный знак (!) в подстанавливаемой команде # этот вариант не будет работать при наборе в командной строке, # поскольку здесь используется механизм "истории команд" BASH # Однако, в сценариях, механизм истории команд запрещен. a=`ls -l` # В переменную 'a' записывается результат работы команды 'ls -l' echo $a # Кавычки отсутствуют, удаляются лишние пробелы и пустые строки. echo echo "$a" # Переменная в кавычках, все пробелы и пустые строки сохраняются exit 0
# Взято из /etc/rc.d/rc.local R=$(cat /etc/redhat-release) arch=$(uname -m)
Переменные Bash не имеют типа
В отличие от большинства других языков программирования, Bash не производит разделения переменных по "типам". По сути, переменные Bash являются строковыми переменными, но, в зависимости от контекста, Bash допускает целочисленную арифметику с переменными. Определяющим фактором здесь служит содержимое переменных.
Пример: Целое число или строка?
#!/bin/bash # int-or-string.sh: Целое число или строка? a=2334 # Целое число. let "a += 1" echo "a = $a " # a = 2335 echo # Всё ещё целое число. b=${a/23/BB} # замена "23" на "BB". # Происходит трансформация числа в строку. echo "b = $b" # b = BB35 declare -i b # Явное указание типа здесь не поможет. echo "b = $b" # b = BB35 let "b += 1" # BB35 + 1 = echo "b = $b" # b = 1 echo c=BB34 echo "c = $c" # c = BB34 d=${c/BB/23} # замена "BB" на "23". # Переменная $d становится целочисленной. echo "d = $d" # d = 2334 let "d += 1" # 2334 + 1 = echo "d = $d" # d = 2335echo echo # А что происходит с "пустыми" переменными? e="" echo "e = $e" # e = let "e += 1" # Арифметические операции допускают использование "пустых" переменных? echo "e = $e" # e = 1 echo # "Пустая" переменная становится целочисленной. # А что происходит с необъявленными переменными? echo "f = $f" # f = let "f += 1" # Арифметические операции допустимы? echo "f = $f" # f = 1 echo # Необъявленная переменная трансформируется в целочисленную. # Переменные Bash не имеют типов. exit 0
Отсутствие типов — это и благословение и проклятие. С одной стороны — отсутствие типов делает сценарии более гибкими(чтобы повеситься — достаточно иметь веревку!) и облегчает чтение кода. С другой — является источником потенциальных ошибок и поощряет привычку к "неряшливому" программированию.
Бремя отслеживания типа той или иной переменной полностью лежит на плечах программиста. Bash не будет делать это за вас!
Специальные типы переменных
- локальные переменные
Переменные, область видимости которых ограничена блоком кода или телом функции (см. также локальные переменные в функциях)
- переменные окружения
Переменные, которые затрагивают командную оболочку и порядок взаимодействия с пользователем.
В более общем контексте, каждый процесс имеет некоторое "окружение" (среду исполнения), т.е. набор переменных, к которым процесс может обращаться за получением определенной информации. В этом смысле командная оболочка подобна любому другому процессу.
Каждый раз, когда запускается командный интерпретатор, для него создаются переменные, соответствующие переменным окружения. Изменение переменных или добавление новых переменных окружения заставляет оболочку обновить свои переменные, и все дочерние процессы (и команды, исполняемые ею)наследуют это окружение.
Пространство, выделяемое под переменные окружения, ограничено. Создание слишком большого количества переменных окружения или одной переменной, которая занимает слишком большое пространство, может привести к возникновению определённых проблем.
bash$ eval "`seq 10000 | sed -e 's/.*/export var&=ZZZZZZZZZZZZZZ/'`" bash$ du bash: /usr/bin/du: Argument list too long
Если сценарий изменяет переменные окружения, то они должны "экспортироваться", т.е. передаваться окружению, локальному по отношению к сценарию. Эта функция возложена на команду export.
Сценарий может экспортировать переменные только дочернему процессу, т.е.командам и процессам запускаемым из данного сценария. Сценарий, запускаемый из командной строки не может экспортировать переменные "наверх" командной оболочке. Дочерний процесс не может экспортировать переменные родительскому процессу.
- позиционные параметры
Аргументы, передаваемые скрипту из командной строки — $0, $1, $2, $3..., где $0 — это название файла сценария, $1 — это первый аргумент, $2 — второй, $3 — третий и так далее. Аргументы, следующие за $9, должны заключаться в фигурные скобки, например: ${10}, ${11}, ${12}.
Специальные переменные $* и $@ содержат все позиционные параметры (аргументы командной строки).
Пример: Позиционные параметры.
#!/bin/bash # Команда вызова сценария должна содержать по меньшей мере 10 параметров, например # ./scriptname 1 2 3 4 5 6 7 8 9 10 MINPARAMS=10 echo echo "Имя файла сценария: \"$0\"." # Для текущего каталога добавит ./ echo "Имя файла сценария: \"`basename $0`\"." # Добавит путь к имени файла (см. 'basename') echo if [ -n "$1" ] # Проверяемая переменная заключена в кавычки. then echo "Параметр #1: $1" # необходимы кавычки для экранирования символа # fi if [ -n "$2" ] then echo "Параметр #2: $2" fi if [ -n "$3" ] then echo "Параметр #3: $3" fi if [ -n "${10}" ] # Параметры, следующие за $9 должны заключаться в фигурные скобки then echo "Параметр #10: ${10}" fi echo "-----------------------------------" echo "Все аргументы командной строки: "$*"" if [ $# -lt "$MINPARAMS" ] then echo echo "Количество аргументов командной строки должно быть не менее $MINPARAMS !" fi echo exit 0
Скобочная нотация позиционных параметров даёт довольно простой способ обращения к последнему аргументу, переданному в сценарий из командной строки. Такой способ подразумевает использование косвенной адресации.
args=$# # Количество переданных аргументов. lastarg=${!args} # Обратите внимание: lastarg=${!$#} неприменимо.
В сценарии можно предусмотреть различные варианты развития событий, в зависимости отимени сценария. Для этого сценарий должен проанализировать аргумент $0 -- имя файласценария. Это могут быть и имена символических ссылок на файл сценария.
Если сценарий ожидает передачи аргументов в командной строке, то при их отсутствии он получит "пустые" переменные, что может вызвать нежелательный побочный эффект. Один из способов борьбы с подобными ошибками — добавить дополнительный символ в обеих частях операции присваивания, где используются аргументы командной строки.
variable1_=$1_# Это предотвратит появление ошибок, даже при отсутствии входного аргумента. critical_argument01=$variable1_ # Дополнительные символы всегда можно "убрать" позднее. # Это может быть сделано примерно так: variable1=${variable1_/_/} # Побочный эффект возникает только если имя переменной # $variable1_ будет начинаться с символа "_". # Здесь используется один из вариантов подстановки параметров, обсуждаемых далее. # Отсутствие шаблона замены приводит к удалению. # Более простой способ заключается # в обычной проверке наличия позиционного параметра. if [ -z $1 ]; then exit $POS_PARAMS_MISSING fi
Пример: wh, whois выяснение имени домена.
#!/bin/bash # Команда 'whois domain-name' выясняет имя домена на одном из 3 серверов: # ripe.net, cw.net, radb.net # Разместите этот скрипт под именем 'wh' в каталоге /usr/local/bin # Требуемые символические ссылки: # ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe # ln -s /usr/local/bin/wh /usr/local/bin/wh-cw # ln -s /usr/local/bin/wh /usr/local/bin/wh-radb if [ -z "$1" ] then echo "Порядок использования: `basename $0` [domain-name]" exit 65 fi case `basename $0` in # Проверка имени скрипта и, соответственно, имени сервера "wh" ) whois $1@whois.ripe.net;; "wh-ripe") whois $1@whois.ripe.net;; "wh-radb") whois $1@whois.radb.net;; "wh-cw" ) whois $1@whois.cw.net;; * ) echo "Порядок использования: `basename $0` [domain-name]";; esac exit 0
Команда shift "сдвигает" позиционные параметры, в результате чего парметры "сдвигаются" на одну позицию влево.
$1 <--- $2, $2 <--- $3, $3 <--- $4, и т.д.
Прежний аргумент $1 теряется, но аргумент $0 (имя файла сценария) остаётся без изменений. Если вашему сценарию передаётся большое количество входных аргументов, то команда shift позволит вам получить доступ к аргументам, с порядковым номером больше 9, без использования {фигурных скобок}.
Пример: Использование команды shift.
#!/bin/bash # Использование команды 'shift' с целью перебора всех аргументов командной строки. # Назовите файл с этим сценарием, например "shft", # и вызовите его с набором аргументов, например: # ./shft a b c def 23 skidoo until [ -z "$1" ] # До тех пор пока не будут разобраны все входные аргументы... do echo -n "$1 " shift done echo # Дополнительная пустая строка. exit 0
Команда shift может применяться и к входным аргументам функций.
Кавычки
Кавычки, ограничивающие строки с обеих сторон, служат для предотвращения интерпретации специальных символов, которые могут находиться в строке. Символ называется "специальным", если он несет дополнительную смысловую нагрузку, например символ шаблона — *.
bash$ ls -l [Vv]* -rw-rw-r-- 1 bozo bozo 324 Apr 2 15:05 VIEWDATA.BAT -rw-rw-r-- 1 bozo bozo 507 May 4 14:25 vartrace.sh -rw-rw-r-- 1 bozo bozo 539 Apr 14 17:11 viewdata.sh bash$ ls -l '[Vv]*' ls: [Vv]*: No such file or directory
Некоторые программы и утилиты могут вызываться с дополнительными параметрами,содержащими специальными символы, поэтому очень важно предотвратить интерпретацию передаваемых параметров командной оболочкой, позволяя сделать это вызываемой программой.
bash$ grep '[Пп]ервая' *.txt file1.txt:Это первая строка в file1.txt. file2.txt:Это Первая строка в file2.txt.
Примечательно, что "не окавыченный" вариант команды grep [Пп]ервая *.txt будет правильно исполняться в Bash, но не в tcsh. Вообще, желательно использовать двойные кавычки (" ") при обращении к переменным. Это предотвратит интерпретацию специальных символов, которые могут содержаться в именах переменных, за исключением $, `(обратная кавычка) и \ (escape — обратный слэш). То, что символ $ попал в разряд исключений, позволяет выполнять обращение к переменным внутри строк, ограниченных двойными кавычками ("$variable"), т.е. выполнять подстановку значений переменных. Двойные кавычки могут быть использованы для предотвращения разбиения строки на слова. Заключение строки в кавычки приводит к тому, что она передаётся как один аргумент, даже если она содержит пробельные символы-разделители.
variable1="a variable containing five words" COMMAND This is $variable1 # Исполнение COMMAND с 7 входными аргументами: # "This" "is" "a" "variable" "containing" "five" "words" COMMAND "This is $variable1" # Исполнение COMMAND с одним входным аргументом:# "This is a variable containing five words" variable2="" # Пустая переменная. COMMAND $variable2 $variable2 $variable2 # Исполнение COMMAND без аргументов. COMMAND "$variable2" "$variable2" "$variable2" # Исполнение COMMAND с 3 "пустыми" аргументами. COMMAND "$variable2 $variable2 $variable2" # Исполнение COMMAND с 1 аргументом (и 2 пробелами).
Заключение в кавычки аргументов команды echo необходимо только в том случае, когда разбиение на отдельные слова сопряжено с определенными трудностями.
Пример: Вывод "причудливых" переменных.
#!/bin/bash # weirdvars.sh: Вывод "причудливых" переменных var="'(]\\{}\$\"" echo $var # '(]\{}$" echo "$var" # '(]\{}$" Никаких различий. echo IFS='\' echo $var # '(] {}$" \ символ-разделитель преобразован в пробел. echo "$var" # '(]\{}$" # Примеры выше предоставлены S.C. exit 0
Одиночные кавычки (' ') схожи по своему действию с двойными кавычками, только не допускают обращение к переменным, поскольку специальный символ "$" внутри одинарных кавычек воспринимается как обычный символ. Внутри одиночных кавычек, любой специальный символ, заисключением ', интерпретируется как простой символ. Одиночные кавычки ("строгие, или полные кавычки") следует рассматривать как более строгий вариант, чем двойные кавычки ("нестрогие, или неполные кавычки"). Поскольку внутри одиночных кавычек даже экранирующий (\) символ воспринимается как обычный символ, попытка вывести одиночную кавычку внутри строки, ограниченной одинарными кавычками, не даст желаемого результата.
echo "Why can't I write 's between single quotes" echo # Обходной метод. echo 'Why can'\''t I write '"'"'s between single quotes' # |-------| |----------| |-----------------------| # Три строки, ограниченных одинарными кавычками, # и экранированные одиночные кавычки между ними. # Пример любезно предоставлен Stephane Chazelas.
Экранирование — это способ заключения в кавычки одиночного символа. Экранирующий (escape)символ (\) сообщает интерпретатору, что следующий за ним символ должен восприниматься как обычный символ. С отдельными командами и утилитами, такими как echo и sed, экранирующий символ может применяться для получения обратного эффекта — когда обычные символы при экранировании приобретают специальное значение.
Специальное назначение некоторых экранированных символов, используемых совместно с echo и sed
- \n — перевод строки (новая строка)
- \r — перевод каретки
- \t — табуляция
- \v — вертикальная табуляция
- \b — забой (backspace)
- \a — "звонок" (сигнал)
- \0xx — ASCII-символ с кодом 0xx в восьмеричном виде
Пример: Экранированные символы.
#!/bin/bash # escaped.sh: экранированные символы echo; echo echo "\v\v\v\v" # Вывод последовательности символов \v\v\v\v. # Для вывода экранированных символов следует использовать ключ -e. echo "=============" echo "ВЕРТИКАЛЬНАЯ ТАБУЛЯЦИЯ" echo -e "\v\v\v\v" # Вывод 4-х вертикальных табуляций. echo "==============" echo "КАВЫЧКИ" echo -e "\042" # Выводит символ " (кавычки с восьмеричным кодом ASCII 42). echo "==============" # Конструкция $'\X' делает использование ключа -e необязательным. echo; echo "НОВАЯ СТРОКА И ЗВОНОК" echo $'\n' # Перевод строки. echo $'\a' # Звонок (сигнал). echo "===============" echo "КАВЫЧКИ" # Bash версии 2 и выше допускает использование конструкции $'\nnn'. # Обратите внимание: здесь под '\nnn' подразумевается восьмеричное значение. echo $'\t \042 \t' # Кавычки (") окруженные табуляцией. # В конструкции $'\xhhh' допускается использовать и шестнадцатеричные значения. echo $'\t \x22 \t' # Кавычки (") окруженные табуляцией. # Спасибо Greg Keraunen, за это примечание. # Ранние версии Bash допускали употребление конструкции в виде '\x022'. echo "===============" echo # Запись ASCII-символов в переменную. # ---------------------------------------- quote=$'\042' # запись символа " в переменную. echo "$quote Эта часть строки ограничена кавычками, $quote а эта — нет." echo # Конкатенация ASCII-символов в переменную. triple_underline=$'\137\137\137' # 137 — это восьмеричный код символа '_'. echo "$triple_underline ПОДЧЕРКИВАНИЕ $triple_underline" echo ABC=$'\101\102\103\010' # 101, 102, 103 это A, B и C соответственно. echo $ABC echo; echo escape=$'\033' # 033 — восьмеричный код экранирующего символа. echo "\"escape\" выводится как $escape" # вывод отсутствует. echo; echo exit 0
- \" — кавычки
echo "Привет" # Привет echo "Он сказал: \"Привет\"." # Он сказал: "Привет".
- \$ — символ доллара (если за комбинацией символов \$ следует имя переменной, то она не будет разыменована)
echo "\$variable01" # выведет $variable01
- \\ — обратный слэш
echo "\\" # выведет \ # Тогда как... echo "\" # Приведёт к выводу вторичного приглашения к вводу. # В сценариях — порождает сообщение об ошибке.
Поведение символа \ сильно зависит от того экранирован ли он, ограничен ли кавычками или находится внутри конструкции подстановки команды или во вложенном документе.
# Простое экранирование и кавычки echo \z # z echo \\z # \z echo '\z' # \z echo '\\z' # \\z echo "\z" # \z echo "\\z" # \z # Подстановка команды echo `echo \z` # z echo `echo \\z` # z echo `echo \\\z` # \z echo `echo \\\\z` # \z echo `echo \\\\\\z` # \z echo `echo \\\\\\\z` # \\z echo `echo "\z"` # \z echo `echo "\\z"` # \z # Встроенный документ cat <<EOF \z EOF # \z cat <<EOF \\z EOF # \z # Эти примеры предоставил Stephane Chazelas.
Отдельные символы в строке, которая записывается в переменную, могут быть экранированы, исключение составляет сам экранирующий символ.
variable=\ echo "$variable" # Не работает - дает сообщение об ошибке: # test.sh: : command not found # В "чистом" виде экранирующий (escape) символ не может быть записан в переменную. # Фактически, в данном примере, происходит экранирование символа перевода строки # в результате получается такая команда: variable=echo "$variable" # ошибочное присваивание variable=\ 23skidoo echo "$variable" # 23skidoo # Здесь все в порядке, поскольку вторая строка # является нормальным, с точки зрения присваивания, выражением. variable=\ # \^ За escape-символом следует пробел echo "$variable" # пробел variable=\\ # \ variable=\\\ echo "$variable" # Не работает - сообщение об ошибке: # test.sh: \: command not found # # Первый escape-символ экранирует второй, а третий оказывается неэкранированным, # результат тот же, что и в первом примере. variable=\\\\ echo "$variable" # \\ # Второй и четвертый escape-символы экранированы. # Это нормально.
Экранирование пробелов предотвращает разбиение списка аргументов командной строки на отдельные аргументы.
file_list="/bin/cat /bin/gzip /bin/more /usr/bin/less /usr/bin/emacs-20.7" # Список файлов как аргумент(ы) командной строки. # Добавить два файла в список и вывести список. ls -l /usr/X11R6/bin/xsetroot /sbin/dump $file_listecho "-------------------------------------------------------------------------" # Что произойдет, если экранировать пробелы в списке? ls -l /usr/X11R6/bin/xsetroot\ /sbin/dump\ $file_list # Ошибка: первые три файла будут "слиты" воедино # и переданы команде 'ls -l' как один аргумент # потому что два пробела, разделяющие аргументы (слова) — экранированы.
Кроме того, escape-символ позволяет писать многострочные команды. Обычно, каждая команда занимает одну строку, но escape-символ позволяет экранировать символ перевода строки, в результате чего одна команда может занимать несколько строк.
(cd /source/directory && tar cf - . ) | \ (cd /dest/directory && tar xpvf -) # Команда копирования дерева каталогов. # Разбита на две строки для большей удобочитаемости. # Альтернативный вариант: tar cf - -C /source/directory . | tar xpvf - -C /dest/directory # См. примечание ниже. # (Спасибо Stephane Chazelas.)
Если строка сценария заканчивается символом создания конвейера |, то необходимость в применении символа \, для экранирования перевода строки, отпадает. Тем не менее, считается хорошим тоном, всегда использовать символ \ в конце промежуточных строк многострочных команд.
echo "foo bar" #foo #bar echo echo 'foo bar' # Никаких различий. echo echo foo\ bar # Перевод строки экранирован. #foobar echo echo "foo\ bar" # Внутри "нестрогих" кавычек символ "\" интерпретируется как экранирующий. #foobar echo echo 'foo\ bar' # В "строгих" кавычках обратный слэш воспринимается как обычный символ. #foo\ #bar # Примеры предложены Stephane Chazelas.
Завершение и код завершения
...эта часть Bourne shell покрыта мраком, тем не менее все пользуются ею. ©Chet Ramey
Команда exit может использоваться для завершения работы сценария, точно так же как и в программах на языке C. Кроме того, она может возвращать некоторое значение, которое может быть проанализировано вызывающим процессом. Каждая команда возвращает код завершения (иногда код завершения называют возвращаемым значением). В случае успеха команда должна возвращать 0, а в случае ошибки — ненулевое значение, которое, как правило, интерпретируется как код ошибки. Практически все команды и утилиты Unix возвращают 0 в случае успешного завершения, но имеются и исключения из правил.
Аналогичным образом ведут себя функции, расположенные внутри сценария, и сам сценарий, возвращая код завершения. Код, возвращаемый функцией или сценарием, определяется кодом возврата последней команды. Команде exit можно явно указать код возврата, в виде: exit nnn, где nnn — это код возврата (число в диапазоне 0 - 255).
Когда работа сценария завершается командой exit без параметров, то код возврата сценария определяется кодом завершения последней исполненной команды (не считая саму команду exit).
#!/bin/bash COMMAND_1 ... # Сценарий вернет код завершения последней команды. COMMAND_LAST exit
Эквивалентный вариант — exit $? или можно вообще опустить команду exit.
#!/bin/bash COMMAND_1 ... # Сценарий вернет код завершения последней команды. COMMAND_LAST exit $? #!/bin/bash COMMAND1 ... # Сценарий вернет код завершения последней команды. COMMAND_LAST
Код возврата последней команды хранится в специальной переменной $?. После исполнения кодафункции, переменная $? хранит код завершения последней команды, исполненной в функции. Таким способом в Bash передаётся "значение, возвращаемое" функцией. После завершения работы сценария, код возврата можно получить, обратившись из командной строки к переменной $?, т.е. это будет код возврата последней команды, исполненной в сценарии.
Пример: Завершение / код завершения.
#!/bin/bash echo hello echo $? # код возврата = 0, поскольку команда выполнилась успешно. lskdf # Несуществующая команда. echo $? # Ненулевой код возврата, поскольку команду выполнить не удалось. echo exit 113 # Явное указание кода возврата 113. # Проверить можно, если набрать в командной строке "echo $?" # после выполнения этого примера. # В соответствии с соглашениями, 'exit 0' указывает на успешное завершение, # в то время как ненулевое значение означает ошибку.
Переменная $? особенно полезна, когда необходимо проверить результат исполнения команды. Символ !, может выступать как логическое "НЕ" для инверсии кода возврата.
Пример: Использование символа "!" для логической инверсии кода возврата.
#!/bin/bash true # встроенная команда "true". echo "код возврата команды \"true\" = $?" # 0 ! true echo "код возврата команды \"! true\" = $?" # 1 # Обратите внимание: символ "!" от команды необходимо отделять пробелом. # !true вызовет сообщение об ошибке "command not found" # Спасибо S.C.
В отдельных случаях коды возврата должны иметь предопределённые значения и не должны задаваться пользователем.
Проверка условий
Практически любой язык программирования включает в себя условные операторы, предназначенные для проверки условий, чтобы выбрать тот или иной путь развития событий в зависимости от этих условий. В Bash, для проверки условий, имеется команда test, различного вида скобочные операторы и условный оператор if/then.
Конструкции проверки условий
- Оператор if/then проверяет — является ли код завершения списка команд 0 (поскольку 0 означает "успех"!), и если это так, то выполняет одну, или более, команд, следующие за словом then.
- Существует специальная команда — [ (левая квадратная скобка). Она является синонимом команды test, и является встроенной командой (т.е. более эффективной, в смысле производительности). Эта команда воспринимает свои аргументы как выражение сравнения или как файловую проверку и возвращает код завершения в соответствии с результатами проверки(0 — истина, 1 — ложь).
- Начиная с версии 2.02, Bash предоставляет в распоряжение программиста конструкцию ... , расширенный вариант команды test, которая выполняет сравнение способом более знакомым программистам, пишущим на других языках программирования. Обратите внимание: [[ — это зарезервированное слово, а не команда!
Bash исполняет [[ $a -lt $b ]] как один элемент, который имеет код возврата!
Круглые скобки (( ... )) и предложение let ... так же возвращают код 0, если результатом арифметического выражения является ненулевое значение. Таким образом, арифметические выражения могут участвовать в операциях сравнения.
Предложение let "1<2" возвращает 0 (так как результат сравнения "1<2" — "1", или "истина")(( 0 && 1 )) возвращает 1 (так как результат операции "0 && 1" — "0", или "ложь")
- Условный оператор if проверяет код завершения любой команды, а не только результат выражения, заключенного в квадратные скобки.
if cmp a b &> /dev/null # Подавление вывода. then echo "Файлы a и b идентичны." else echo "Файлы a и b имеют различия." fi if grep -q Bash file then echo "Файл содержит, как минимум, одно слово Bash." fi word=Linux letter_sequence=inu if echo "$word" | grep -q "$letter_sequence" # Подавление вывода ключом "-q" в команде grep. then echo "Последовательность $letter_sequence обнаружена в слове $word" else echo "Последовательность $letter_sequence, в слове $word не найдена" fi if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED then echo "Команда выполнена успешно." else echo "Обнаружена ошибка при выполнении команды." fi
- Оператор if/then допускает наличие вложенных проверок.
if echo "Следующий *if* находится внутри первого *if*." if [[ $comparison = "integer" ]] then (( a < b )) else [[ $a < $b ]] fi then echo '$a меньше $b'fi fi
Это детальное описание конструкции "if-test" любезно предоставлено Stephane Chazelas.
Пример: Что есть "истина"?
#!/bin/bash echo echo "Проверяется \"0\"" if [ 0 ] # ноль then echo "0 — это истина." else echo "0 — это ложь." fi # 0 — это истина. echo echo "Проверяется \"1\"" if [ 1 ] # единица then echo "1 — это истина." else echo "1 — это ложь." fi # 1 — это ложь. echo echo "Testing \"-1\"" if [ -1 ] # минус один then echo "-1 — это истина." else echo "-1 — это ложь." fi # -1 — это истина. echo echo "Проверяется \"NULL\"" if [ ] # NULL (пустое условие) then echo "NULL — это истина." else echo "NULL — это ложь." fi # NULL — это ложь. echo echo "Проверяется \"xyz\"" if [ xyz ] # строка then echo "Случайная строка — это истина." else echo "Случайная строка — это ложь." fi # Случайная строка — это истина. echo echo "Проверяется \"\$xyz\"" if [ $xyz ] # Проверка, если $xyz это null, но... # только для неинициализированных переменных. then echo "Неинициализированная переменная — это истина." else echo "Неинициализированная переменная — это ложь." fi # Неинициализированная переменная — это ложь. echo echo "Проверяется \"-n \$xyz\"" if [ -n "$xyz" ] # Более корректный вариант. then echo "Неинициализированная переменная — это истина." else echo "Неинициализированная переменная — это ложь." fi # Неинициализированная переменная — это ложь. echo xyz= # Инициализирована пустым значением. echo "Проверяется \"-n \$xyz\"" if [ -n "$xyz" ] then echo "Пустая переменная — это истина." else echo "Пустая переменная — это ложь." fi # Пустая переменная — это ложь. echo # Когда "ложь" истинна? echo "Проверяется \"false\"" if [ "false" ] # это обычная строка "false". then echo "\"false\" — это истина." # и она истинна. else echo "\"false\" — это ложь." fi # "false" — это истина. echo echo "Проверяется \"\$false\"" # Опять неинициализированная переменная. if [ "$false" ] then echo "\"\$false\" — это истина." else echo "\"\$false\" — это ложь." fi # "$false" — это ложь. # Теперь мы получили ожидаемый результат. echo exit 0
Упражнение
Объясните результаты, полученные в примере Что есть "истина"?
if [ condition-true ] then command 1 command 2 ... else # Необязательная ветка (можно опустить, если в ней нет необходимости). # Дополнительный блок кода, # исполняемый в случае, когда результат проверки — "ложь". command 3 command 4 ... fi
Когда if и then располагаются в одной строке, то конструкция if должна завершаться точкой с запятой. И if, и then - это зарезервированные слова. Зарезервированные слова начинают инструкцию, которая должна быть завершена прежде, чем в той же строке появится новая инструкция.
if [ -x "$filename" ]; then
- Else if и elif
elif — это краткая форма записи конструкции else if. Применяется для построения многоярусных инструкций if/then.
if [ condition1 ] then command1 command2 command3 elif [ condition2 ] # То же самое, что и else if then command4 command5 else default-command fi
Конструкция if test condition-true является точным эквивалентом конструкции if [ condition-true], где левая квадратная скобка [ выполняет те же действия, что и команда test. Закрывающая правая квадратная скобка ] не является абсолютно необходимой, однако, более новые версии Bash требуют её наличие. Команда test — это встроенная команда Bash, которая выполняет проверки файлов и производит сравнение строк. Таким образом, в Bash-скриптах, команда test не вызывает внешнюю (/usr/bin/test) утилиту, которая является частью пакета sh-utils. Аналогично, [ не производит вызов утилиты /usr/bin/[, которая является символической ссылкой на /usr/bin/test.
bash$ type test test is a shell builtin bash$ type '[' [ is a shell builtin bash$ type '[[' [[ is a shell keyword bash$ type ']]' ]] is a shell keyword bash$ type ']' bash: type: ]: not found
Пример: Эквиваленты команды test — /usr/bin/test, [ ], и /usr/bin/[
#!/bin/bash echo if test -z "$1" then echo "Аргументы командной строки отсутствуют." else echo "Первый аргумент командной строки: $1." fi echo if /usr/bin/test -z "$1" # Даёт тот же результат, что и встроенная команда "test". then echo "Аргументы командной строки отсутствуют." else echo "Первый аргумент командной строки: $1." fi echo if [ -z "$1" ] # Функционально идентично вышеприведенному блоку кода. # if [ -z "$1" эта конструкция должна работать, но... # Bash выдает сообщение об отсутствующей закрывающей скобке. then echo "Аргументы командной строки отсутствуют." else echo "Первый аргумент командной строки: $1." fi echo if /usr/bin/[ -z "$1" # Функционально идентично вышеприведенному блоку кода. # if /usr/bin/[ -z "$1" ] # Работает, но выдает сообщение об ошибке. then echo "Аргументы командной строки отсутствуют." else echo "Первый аргумент командной строки: $1." fi echo exit 0
Конструкция [[ ]] более универсальна, по сравнению с [ ] . Этот расширенный вариант команды test перекочевал в Bash из ksh88. Внутри этой конструкции не производится никакой дополнительной интерпретации имён файлов и не производится разбиение аргументов на отдельные слова, но допускается подстановка параметров и команд.
file=/etc/passwd if [[ -e $file ]] then echo "Файл паролей найден." fi
Конструкция [[...]] более предпочтительна, нежели [...], поскольку поможет избежать некоторых логических ошибок. Например, операторы &&, ||, < и > внутри [[ ]] вполне допустимы, в то время как внутри [ ] порождают сообщения об ошибках! Строго говоря, после оператора if, ни команда test, ни квадратные скобки ( [] или [[ ]] ) не являются обязательными.
dir=/home/bozo if cd "$dir" 2>/dev/null; then # "2>/dev/null" подавление вывода сообщений об ошибках. echo "Переход в каталог $dir выполнен." else echo "Невозможно перейти в каталог $dir." fi
Инструкция if COMMAND возвращает код возврата команды COMMAND. Точно так же, условие, находящееся внутри квадратных скобок может быть проверено без использования оператора if.
var1=20 var2=22 [ "$var1" -ne "$var2" ] && echo "$var1 не равно $var2" home=/home/bozo [ -d "$home" ] || echo "каталог $home не найден."
Внутри (()) производится вычисление арифметического выражения. Если результатом вычислений является ноль, то возвращается 1, или "ложь". Ненулевой результат даёт код возврата 0, или "истина". То есть полная противоположность инструкциям test и [ ], обсуждавшимся выше.
Пример: Арифметические выражения внутри (( ))
#!/bin/bash # Проверка арифметических выражений. # Инструкция (( ... )) вычисляет арифметические выражения. # Код возврата противоположен коду возврата инструкции [ ... ] ! (( 0 )) echo "Код возврата \"(( 0 ))\": $?." # 1(( 1 )) (( 1 )) echo "Код возврата \"(( 1 ))\": $?." # 0 (( 5 > 4 )) # true echo "Код возврата \"(( 5 > 4 ))\": $?." # 0 (( 5 > 9 )) # false echo "Код возврата \"(( 5 > 9 ))\": $?." # 1 (( 5 - 5 )) # 0 echo "Код возврата \"(( 5 - 5 ))\": $?." # 1 (( 5 / 4 )) # Деление, всё в порядке echo "Код возврата \"(( 5 / 4 ))\": $?." # 0 (( 1 / 2 )) # Результат деления < 1. echo "Код возврата \"(( 1 / 2 ))\": $?." # Округляется до 0. #1 (( 1 / 0 )) 2>/dev/null # Деление на 0. echo "Код возврата \"(( 1 / 0 ))\": $?." # 1 # Для чего нужна инструкция "2>/dev/null" ? # Что произойдет, если её убрать? # Попробуйте убрать ее и выполнить сценарий. exit 0
Операции проверки файлов
Возвращает true если...
- -e — файл существует
- -f — обычный файл (не каталог и не файл устройства!)
- -s — ненулевой размер файла
- -d — файл является каталогом
- -b — файл является блочным устройством (floppy, cdrom и т.п.)
- -c — файл является символьным устройством (клавиатура, модем, звуковая карта и т.п.)
- -p — файл является каналом
- -h — файл является символической ссылкой
- -L — файл является символической ссылкой
- -S — файл является сокетом
- -t — файл (дескриптор) связан с терминальным устройством
Этот ключ может использоваться для проверки — является ли файл стандартным устройством ввода stdin ([ -t 0 ]) или стандартным устройством вывода stdout ([ -t 1 ]).
- -r — файл доступен для чтения (пользователю, запустившему сценарий)
- -w — файл доступен для записи (пользователю, запустившему сценарий)
- -x — файл доступен для исполнения (пользователю, запустившему сценарий)
- -g — set-group-id (sgid) флаг для файла или каталога установлен
Если для каталога установлен флаг sgid, то файлы, создаваемые в таком каталоге, наследуют идентификатор группы каталога, который может не совпадать с идентификатором группы, к которой принадлежит пользователь, создавший файл. Это может быть полезно для каталогов, в которых хранятся файлы, общедоступные для группы пользователей.
- -u — set-user-id (suid) флаг для файла установлен
Установленный флаг suid приводит к изменению привилегий запущенного процесса на привилегии владельца исполняемого файла. Исполняемые файлы, владельцем которых является root, с установленным флагом set-user-id запускаются с привилегиями root, даже если их запускает обычный пользователь. Это может оказаться полезным для некоторых программ(таких как pppd и cdrecord), которые осуществляют доступ к аппаратной части компьютера. В случае отсутствия флага suid, программы не смогут быть запущены рядовым пользователем, не обладающим привилегиями root. -rwsr-xr-t 1 root 178236 Oct 2 2000 /usr/sbin/pppd Файл с установленным флагом suid отображается с включенным флагом s в поле прав доступа.
- -k — флаг sticky bit (бит фиксации) установлен
Общеизвестно, что флаг "sticky bit" — это специальный тип прав доступа к файлам. Программы сустановленным флагом "sticky bit" остаются в системном кэше после своего завершения,обеспечивая тем самым более быстрый запуск программы. Если флаг установлен для каталога, то это приводит к ограничению прав на запись. Установленный флаг "sticky bit" отображается в виде символа t в поле прав доступа. drwxrwxrwt 7 root 1024 May 19 21:26 tmp/ Если пользователь не является владельцем каталога , с установленным "sticky bit", но имеет право на запись в каталог, то он может удалять только те файлы в каталоге, владельцем которых он является. Это предотвращает удаление и перезапись "чужих" файлов в общедоступных каталогах, таких как /tmp.
- -O — вы являетесь владельцем файла
- -G — вы принадлежите к той же группе, что и файл
- -N — файл был модифицирован с момента последнего чтения
- f1 -nt f2 — файл f1 более новый, чем f2
- f1 -ot f2 — файл f1 более старый, чем f2
- f1 -ef f2 — файлы f1 и f2 являются "жесткими" ссылками на один и тот же файл
- ! — "НЕ" — логическое отрицание (инверсия) результатов всех вышеприведенных проверок(возвращается true если условие отсутствует).
Пример: Проверка "битых" ссылок.
#!/bin/bash # broken-link.sh # Автор Lee Bigelow <ligelowbee@yahoo.com> # Используется с его разрешения. #Сценарий поиска "битых" ссылок и их вывод в "окавыченном" виде #таким образом они могут передаваться утилите xargs для дальнейшей обработки :) #например. broken-link.sh /somedir /someotherdir|xargs rm # #На всякий случай приведу лучший метод: # #find "somedir" -type l -print0|\ #xargs -r0 file|\ #grep "broken symbolic"| #sed -e 's/^\|: *broken symbolic.*$/"/g' # #но это не чисто BASH-евский метод, а теперь сам сценарий. #Внимание! будьте осторожны с файловой системой /proc и циклическими ссылками! ############################################################## #Если скрипт не получает входных аргументов, #то каталогом поиска является текущая директория #В противном случае, каталог поиска задается из командной строки #################### [ $# -eq 0 ] && directorys=`pwd` || directorys=$@ #Функция linkchk проверяет каталог поиска #на наличие в нём ссылок на несуществующие файлы, и выводит их имена. #Если анализируемый файл является каталогом, #то он передаётся функции linkcheck рекурсивно. ########## linkchk () { for element in $1/*; do [ -h "$element" -a ! -e "$element" ] && echo \"$element\" [ -d "$element" ] && linkchk $element # Само собой, '-h' проверяет символические ссылки, '-d' -- каталоги. done } #Вызов функции linkchk для каждого аргумента командной строки, #если он является каталогом. Иначе выводится сообщение об ошибке #и информация о порядке пользования скриптом. ################ for directory in $directorys; do if [ -d $directory ] then linkchk $directory else echo "$directory не является каталогом" echo "Порядок использования: $0 dir1 dir2 ..." fi done exit 0
Операции сравнения
Сравнение целых чисел:
- -eq — равно
if [ "$a" -eq "$b" ]
- -ne — не равно
if [ "$a" -ne "$b" ]
- -gt — больше
if [ "$a" -gt "$b" ]
- -ge — больше или равно
if [ "$a" -ge "$b" ]
- -lt — меньше
if [ "$a" -lt "$b" ]
- -le — меньше или равно
if [ "$a" -le "$b" ]
- < — меньше (внутри двойных круглых скобок )
(("$a" < "$b"))
- <= — меньше или равно (внутри двойных круглых скобок)
(("$a" <= "$b"))
- > — больше (внутри двойных круглых скобок )
(("$a" > "$b"))
- >= — больше или равно(внутри двойных круглых скобок )
(("$a" >= "$b"))
Cравнение строк:
- = — равно
if [ "$a" = "$b" ]
- == — равно. Синоним оператора =.
if [ "$a" == "$b" ]
[[ $a == z* ]] # истина, если $a начинается с символа "z" (сравнение по шаблону) [[ $a == "z*" ]] # истина, если $a равна z* [ $a == z* ] # имеют место подстановка имён файлов и разбиение на слова [ "$a" == "z*" ] # истина, если $a равна z* # Спасибо S.C.
- != — не равно
if [ "$a" != "$b" ] Этот оператор используется при поиске по шаблону внутри ...
- < — меньше, в смысле величины ASCII-кодов
if [[ "$a" < "$b" ]] if [ "$a" \< "$b" ] Обратите внимание! Символ "<" необходимо экранировать внутри [ ]
- > — больше, в смысле величины ASCII-кодов
if [[ "$a" > "$b" ]] if [ "$a" \> "$b" ] Обратите внимание! Символ ">" необходимо экранировать внутри [ ]
- -z — строка "пустая", т.е. имеет нулевую длину
- -n — строка не "пустая"
Оператор -n требует, чтобы строка была заключена в кавычки внутри квадратных скобок. Как правило, проверка строк, не заключенных в кавычки, оператором ! -z, или просто указание строки без кавычек внутри квадратных скобок, проходит нормально, однако это небезопасная, с точки зрения отказоустойчивости, практика. Всегда заключайте проверяемую строку в кавычки.
Пример: Операции сравнения.
#!/bin/bash a=4 b=5 # Здесь переменные "a" и "b" могут быть как целыми числами, так и строками. # Здесь наблюдается некоторое размывание границ # между целочисленными и строковыми переменными, # поскольку переменные в Bash не имеют типов. # Bash выполняет целочисленные операции над теми переменными, # которые содержат только цифры # Будьте внимательны! echo if [ "$a" -ne "$b" ] then echo "$a не равно $b" echo "(целочисленное сравнение)" fi echo if [ "$a" != "$b" ] then echo "$a не равно $b." echo "(сравнение строк)" # "4" != "5" # ASCII 52 != ASCII 53 fi # Оба варианта, "-ne" и "!=", работают правильно. echo exit 0
Пример: Проверка — является ли строка пустой
#!/bin/bash # str-test.sh: Проверка пустых строк и строк, не заключенных в кавычки, # Используется конструкция if [ ... ] # Если строка не инициализирована, то она не имеет никакого определенного значения. # Такое состояние называется "null" (пустая) (это не то же самое, что ноль). if [ -n $string1 ] # $string1 не была объявлена или инициализирована. then echo "Строка \"string1\" не пустая." else echo "Строка \"string1\" пустая." fi # Неверный результат. # Выводится сообщение о том, что $string1 не пустая, # не смотря на то, что она не была инициализирована. echo # Попробуем ещё раз. if [ -n "$string1" ] # На этот раз, переменная $string1 заключена в кавычки. then echo "Строка \"string1\" не пустая." else echo "Строка \"string1\" пустая." fi # Внутри квадратных скобок заключайте строки в кавычки! echo if [ $string1 ] # Опустим оператор -n. then echo "Строка \"string1\" не пустая." else echo "Строка \"string1\" пустая." fi # Всё работает прекрасно. # Квадратные скобки — [ ], без посторонней помощи определяют, что строка пустая. # Тем не менее, хорошим тоном считается заключать строки в кавычки ("$string1"). # Как указывает Stephane Chazelas, # if [ $string 1 ] один аргумент "]" # if [ "$string 1" ] два аргумента, пустая "$string1" и "]" echo string1=initialized if [ $string1 ] # Опять, попробуем строку без ничего. then echo "Строка \"string1\" не пустая." else echo "Строка \"string1\" пустая." fi # И снова получим верный результат. # И опять-таки, лучше поместить строку в кавычки ("$string1"), поскольку... echo string1="a = b" if [ $string1 ] # И снова, попробуем строку без ничего.. then echo "Строка \"string1\" не пустая." else echo "Строка \"string1\" пустая." fi # Строка без кавычек даёт неверный результат! exit 0 # Спасибо Florian Wisser, за предупреждение.
Пример: zmore
#!/bin/bash #Просмотр gz-файлов с помощью утилиты 'more' NOARGS=65 NOTFOUND=66 NOTGZIP=67 if [ $# -eq 0 ] # то же, что и: if [ -z "$1" ] # $1 должен существовать, но может быть пустым: zmore "" arg2 arg3 then echo "Порядок использования: `basename $0` filename" >&2 # Сообщение об ошибке на stderr. exit $NOARGS # Код возврата 65 (код ошибки). fi filename=$1 if [ ! -f "$filename" ] # Кавычки необходимы на тот случай, если имя файла содержит пробелы. then echo "Файл $filename не найден!" >&2 # Сообщение об ошибке на stderr. exit $NOTFOUND fi if [ ${filename##*.} != "gz" ] # Квадратные скобки нужны для выполнения подстановки значения переменной then echo "Файл $1 не является gz-файлом!" exit $NOTGZIP fi zcat $1 | more # Используется утилита 'more' (очень похожа на 'less'). # Последние версии 'more' могут просматривать сжатые файлы. # Можно вставить 'more' или 'less', если пожелаете. exit $? # Сценарий возвращает код возврата, полученный по конвейеру. # На самом деле команда "exit $?" не является обязательной, # так как работа скрипта завершится здесь в любом случае.
Построение сложных условий проверки:
- -a — логическое И (and)
exp1 -a exp2 возвращает true, если оба выражения, и exp1, и exp2 истинны.
- -o — логическое ИЛИ (or)
exp1 -o exp2 возвращает true, если хотябы одно из выражений, exp1 или exp2 истинно. Они похожи на операторы Bash && и ||, употребляемые в двойных квадратных скобках. [[ condition1 && condition2 ]] Операторы -o и -a употребляются совместно с командой test или внутри одинарных квадратных скобок. if [ "$exp1" -a "$exp2" ]
Вложенные условные операторы if/then
Операторы проверки условий if/then могут быть вложенными друг в друга. Конечный результат будет таким же как если бы результаты всех проверок были объединены оператором &&.
if [ condition1 ] then if [ condition2 ] then do-something # Только если оба условия "condition1" и "condition2" истинны. fi fi
Проверка степени усвоения материала
Для запуска X-сервера может быть использован файл xinitrc. Этот файл содержит некоторое число операторов if/then. Ниже приводится отрывок из этого файла.
if [ -f $HOME/.Xclients ]; then exec $HOME/.Xclients elif [ -f /etc/X11/xinit/Xclients ]; then exec /etc/X11/xinit/Xclients else # failsafe settings. Although we should never get here # (we provide fallbacks in Xclients as well) it can't hurt. xclock -geometry 100x100-5+5 & xterm -geometry 80x50-50+150 & if [ -f /usr/bin/netscape -a -f /usr/share/doc/HTML/index.html ]; then netscape /usr/share/doc/HTML/index.html & fi fi
Объясните действия условных операторов в вышеприведённом отрывке, затем просмотрите файл /etc/X11/xinit/xinitrc и проанализируйте его. Возможно вам придется обратиться к разделам, посвящённым grep, sed и регулярным выражениям.
Операции и смежные темы
Операторы
- Присваивание
variable assignment
Инициализация переменной или изменение её значения.
= — Универсальный оператор присваивания, пригоден как для сравнения целых чисел, так и для сравнения строк.
Пусть вас не смущает, что оператор присваивания ("="), по своему внешнему виду, совпадает с оператором сравнения (=).
# Здесь знак "=" выступает в качестве оператора сравнения if [ "$string1" = "$string2" ] # if [ "X$string1" = "X$string2" ] более отказоустойчивый вариант, # предохраняет от "сваливания" по ошибке в случае, когда одна из переменных пуста. # (добавленные символы "X" компенсируют друг друга.) then command fi
- Арифметические операторы
+ — Сложение - — Вычитание * — Умножение / — Деление ** — Возведение в степень
# В Bash, начиная с версии 2.02, был введен оператор возведения в степень -- "**". let "z=5**3" echo "z = $z" # z = 125
% — модуль (деление по модулю), возвращает остаток от деления
bash$ echo `expr 5 % 3` 2
Этот оператор может применяться в алгоритмах генерации псевдослучайных чисел в заданном диапазоне, для форматирования вывода на экран и даже для генерации простых чисел. На удивление часто операцию деления по модулю можно встретить в различных численных алгоритмах.
Пример: Наибольший общий делитель.
#!/bin/bash # gcd.sh: поиск наибольшего общего делителя # по алгоритму Эвклида # Под "наибольшим общим делителем" (нод) двух целых чисел # понимается наибольшее целое число, которое делит оба делимых без остатка. # Алгоритм Эвклида выполняет последовательное деление. # В каждом цикле, # делимое <--- делитель # делитель <--- остаток # до тех пор, пока остаток не станет равным нулю (остаток = 0). # The gcd = dividend, on the final pass. # Замечательное описание алгоритма Эвклида можно найти # на сайте Jim Loy, http://www.jimloy.com/number/euclids.htm. # ------------------------------------------------------ # Проверка входных параметров ARGS=2E_B ADARGS=65 if [ $# -ne "$ARGS" ] then echo "Порядок использования: `basename $0` первое-число второе-число" exit $E_BADARGS fi # ------------------------------------------------------ gcd () { # Начальное присваивание. dividend=$1 # В сущности, не имеет значения divisor=$2 # какой из них больше. # Почему? remainder=1 # Если переменные не инициализировать, # то работа сценария будет прервана по ошибке # в первом же цикле. until [ "$remainder" -eq 0 ] do let "remainder = $dividend % $divisor" dividend=$divisor # Повторить цикл с новыми исходными данными divisor=$remainder done # алгоритм Эвклида } # последнее $dividend и есть нод. gcd $1 $2echo; echo "НОД чисел $1 и $2 = $dividend"; echo # Упражнение : # -------- # Вставьте дополнительную проверку входных аргументов, # и предусмотрите завершение работы сценария с сообщением об ошибке, если # входные аргументы не являются целыми числами. exit 0
let "var += 5"
-= — "минус-равно" (уменьшение значения переменной на заданное число).
*= — "умножить-равно" (умножить значение переменной на заданное число, результат записать в переменную).let "var *= 4"
/= — "слэш-равно" (уменьшение значения переменной в заданное число раз). %= — "процент-равно" (найти остаток от деления значения переменной на заданное число, результат записать в переменную). Арифметические операторы очень часто используются совместно с командами expr и let.
Пример: Арифметические операции.
#!/bin/bash # От 1 до 6 пятью различными способами. n=1; echo -n "$n " let "n = $n + 1" # let "n = n + 1" тоже допустимо echo -n "$n " : $((n = $n + 1)) # оператор ":" обязателен, поскольку в противном случае, Bash будет # интерпретировать выражение "$((n = $n + 1))" как команду. echo -n "$n " n=$(($n + 1)) echo -n "$n " : $[ n = $n + 1 ] # оператор ":" обязателен, поскольку в противном случае, Bash будет # интерпретировать выражение "$[ n = $n + 1 ]" как команду. # Не вызывает ошибки даже если "n" содержит строку. echo -n "$n " n=$[ $n + 1 ] # Не вызывает ошибки даже если "n" содержит строку. # Старайтесь избегать употребления такой конструкции, # поскольку она уже давно устарела и не переносима. echo -n "$n "; echo # Спасибо Stephane Chazelas. exit 0
Целые числа в Bash фактически являются длинными целыми (32-бит) со знаком, с диапазоном изменений от -2147483648 до 2147483647. Если в результате какой либо операции эти пределы будут превышены, то результат получится ошибочным.
#НЕАКТУАЛЕН для версии Bash 4.2! a=2147483646 echo "a = $a" # a = 2147483646 let "a+=1" # Увеличить "a" на 1. echo "a = $a" # a = 2147483647 let "a+=1" # увеличить "a" ещё раз, с выходом за границы диапазона. echo "a = $a" # a = -2147483648 # ОШИБКА! (выход за границы диапазона)
Версия Bash 2.05b, поддерживает 64-битные целые числа. Bash ничего не знает о существовании чисел с плавающей запятой. Такие числа, из-за наличия символа десятичной точки, он воспринимает как строки.
a=1.5 let "b = $a + 1.3" # Ошибка. # -bash: let: b = 1.5 + 1.3: syntax error: invalid arithmetic operator (error token is ".5 + 1.3") echo "b = $b" # b=1 b=
Для работы с числами с плавающей запятой в сценариях можно использовать утилиту-калькулятор bc.
- Битовые операции
Битовые операции очень редко используются в сценариях командного интерпретатора. Их главное назначение, на мой взгляд, установка и проверка некоторых значений,читаемых из портов ввода-вывода и сокетов. "Битовые операции" гораздо более уместны в компилирующих языках программирования, таких как C и C++.
<< — сдвигает на 1 бит влево (умножение на 2)
let "var <<= 2"
>> — сдвиг вправо на 1 бит (деление на 2)
>>= — "сдвиг-вправо-равно" (имеет смысл обратный <<=)
& — по-битовое И (AND)
&= — по-битовое И-равно
| — по-битовое ИЛИ (OR)
|= — по-битовое ИЛИ-равно
~ — по-битовая инверсия
! — по-битовое отрицание
^ — по-битовое ИСКЛЮЧАЮЩЕЕ ИЛИ (XOR)
^= — по-битовое ИСКЛЮЧАЮЩЕЕ-ИЛИ-равно
- Логические операции
&& — логическое И (and)
if [ $condition1 ] && [ $condition2 ] # То же самое, что: if [ $condition1 -a $condition2 ] # Возвращает true, если оба операнда condition1 и condition2 истинны...\ if [[ $condition1 && $condition2 ]] # То же верно # Обратите внимание: оператор && не должен использоваться внутри [ ... ]!
Оператор &&, в зависимости от контекста, может так же использоваться в "И"-списках для построения составных команд.
|| — логическое ИЛИ (or)
if [ $condition1 ] || [ $condition2 ] # То же самое, что: if [ $condition1 -o $condition2 ] # Возвращает true, если хотя бы один из операндов истинен... if [[ $condition1 || $condition2 ]] # Also works. # Обратите внимание: оператор || не должен использоваться внутри [ ... ].
Bash производит проверку кода возврата КАЖДОГО из операндов в логических выражениях.
Пример: Построение сложных условий, использующих && и ||.
#!/bin/bash a=24 b=47 if [ "$a" -eq 24 ] && [ "$b" -eq 47 ] then echo "Первая проверка прошла успешно." else echo "Первая проверка не прошла." fi # ОКА: if [ "$a" -eq 24 && "$b" -eq 47 ] # пытается выполнить ' [ "$a" -eq 24 ' # и терпит неудачу наткнувшись на ']'. # if [[ $a -eq 24 && $b -eq 24 ]] это правильный вариант # (в строке 17 оператор "&&" имеет иной смысл, нежели в строке 6.) # Спасибо Stephane Chazelas. if [ "$a" -eq 98 ] || [ "$b" -eq 47 ] then echo "Вторая проверка прошла успешно." else echo "Вторая проверка не прошла." fi # Опции -a и -o предоставляют # альтернативный механизм проверки условий. # Спасибо Patrick Callahan. if [ "$a" -eq 24 -a "$b" -eq 47 ] then echo "Третья проверка прошла успешно." else echo "Третья проверка не прошла." fi if [ "$a" -eq 98 -o "$b" -eq 47 ] then echo "Четвертая проверка прошла успешно." else echo "Четвертая проверка не прошла." fi a=rhino b=crocodile if [ "$a" = rhino ] && [ "$b" = crocodile ] then echo "Пятая проверка прошла успешно." else echo "Пятая проверка не прошла." fi exit 0
Операторы && и || могут использоваться и в арифметических вычислениях.
bash$ echo $(( 1 && 2 )) $((3 && 0)) $((4 || 0)) $((0 || 0)) 1 0 1 0
- Прочие операции
, — запятая
С помощью оператора запятая можно связать несколько арифметических в одну последовательность. При разборе таких последовательностей, командный интерпретатор вычисляет все выражения (которые могут иметь побочные эффекты) в последовательности и возвращает результат последнего.
let "t1 = ((5 + 3, 7 - 1, 15 - 4))" echo "t1 = $t1" # t1 = 11 let "t2 = ((a = 9, 15 / 3))" # Выполняется присваивание "a" = 9, # а затем вычисляется "t2". echo "t2 = $t2 a = $a" # t2 = 5 a = 9
Оператор запятая чаще всего находит применение в циклах for.
Числовые константы
Интерпретатор командной оболочки воспринимает числа как десятичные, в противном случае числу должен предшествовать специальный префикс, либо число должно быть записано в особой нотации. Числа, начинающиеся с символа 0, считаются восьмеричными. если числу предшествует префикс 0x, то число считается шестнадцатеричным. Число, в записи которого присутствует символ #, расценивается как запись числа с указанием основы счисления в виде ОСНОВА#ЧИСЛО.
Пример: Различные представления числовых констант.
#!/bin/bash # numbers.sh: Различные представления числовых констант. # Десятичное: по-умолчанию let "dec = 32" echo "десятичное число = $dec" # 32 # В общем-то ничего необычного. # Восьмеричное: числа начинаются с '0' (нуля) let "oct = 032" echo "восьмеричное число = $oct" # 26 # Результат печатается в десятичном виде. # --------- ------ -- ------- # Шестнадцатиричное: числа начинаются с '0x' или '0X' let "hex = 0x32" echo "шестнадцатиричное число = $hex" # 50 # Результат печатается в десятичном виде. # Другие основы счисления: ОСНОВА#ЧИСЛО # ОСНОВА должна быть между 2 и 64. # для записи ЧИСЛА должен использоваться соответствующий ОСНОВЕ диапазон символов, # см. ниже. let "bin = 2#111100111001101" echo "двоичное число = $bin" # 31181 let "b32 = 32#77" echo "32-ричное число = $b32" # 231 let "b64 = 64#@_" echo "64-ричное число = $b64" # 4094 # # Нотация ОСНОВА#ЧИСЛО может использоваться на ограниченном # диапазоне основ счисления (от 2 до 64) # 10 цифр + 26 символов в нижнем регистре + 26 символов в верхнем регистре + @ + _ echo echo $((36#zz)) $((2#10101010)) $((16#AF16)) $((53#1aA)) # 1295 170 44822 3375 # Важное замечание: # -------------- # Использование символов, для записи числа, выходящих за диапазон, # соответствующий ОСНОВЕ счисления # будет приводить к появлению сообщений об ошибках. let "bad_oct = 081" # numbers.sh: let: oct = 081: value too great for base (error token is "081") # Для записи восьмеричных чисел допускается использовать #+ только цифры в диапазоне 0 - 7. exit 0 # Спасибо Rich Bartell и Stephane Chazelas, за разъяснения.
Часть третья. Углублённый материал
К вопросу о переменных
Правильное использование переменных может придать сценариям дополнительную мощь и гибкость, а для этого необходимо изучить все тонкости и нюансы.
Внутренние переменные
- Встроенные переменные
$BASH — путь к исполняемому файлу Bash
bash$ echo $BASH /bin/bash
$BASH_VERSINFO[n] — это массив, состоящий из 6 элементов, и содержащий информацию о версии Bash. Очень похожа на переменную $BASH_VERSION, описываемую ниже.
#!/bin/bash # Информация о версии Bash: for n in 0 1 2 3 4 5 do echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}" done # BASH_VERSINFO[0] = 2 # Major version no. # BASH_VERSINFO[1] = 05 # Minor version no. # BASH_VERSINFO[2] = 8 # Patch level. # BASH_VERSINFO[3] = 1 # Build version. # BASH_VERSINFO[4] = release # Release status. # BASH_VERSINFO[5] = i386-redhat-linux-gnu # Architecture # (same as $MACHTYPE).
$BASH_VERSINFO — версия Bash, установленного в системе
bash$ echo $BASH_VERSION 2.04.12(1)-release tcsh% echo $BASH_VERSION BASH_VERSION: Undefined variable.
Проверка переменной $BASH_VERSION — неплохой метод проверки типа командной оболочки, под которой исполняется скрипт. Переменная $SHELL не всегда даёт правильный ответ.
$DIRSTACK — содержимое вершины стека каталогов (который управляется командами pushd и popd)
Эта переменная соответствует команде dirs, за исключением того, что dirs показывает полное содержимое всего стека каталогов.
$EDITOR — заданный по-умолчанию редактор, вызываемый скриптом, обычно vi или emacs
$EUID — "эффективный" идентификационный номер пользователя (Effective User ID)
Идентификационный номер пользователя, права которого были получены, возможно с помощью команды su. Значение переменной $EUID необязательно должно совпадать с содержимым переменной $UID.
$FUNCNAME — имя текущей функции
xyz23 () { echo "Исполняется функция $FUNCNAME." # Исполняется функция xyz23. } xyz23 echo "FUNCNAME = $FUNCNAME" # FUNCNAME = # Пустое (Null) значение за пределами функций.
$GLOBIGNORE — Перечень шаблонных символов, которые будут проигнорированы при выполнении подстановки имён файлов (globbing)
$GROUPS — группы, к которым принадлежит текущий пользователь
Это список групп (массив) идентификационных номеров групп для текущего пользователя, какэо записано в /etc/passwd.
root# echo $GROUPS 0 root# echo ${GROUPS[1]} 1 root# echo ${GROUPS[5]} 6
$HOME — домашний каталог пользователя, как правило это /home/username
$HOSTNAME — Сетевое имя хоста устанавливается командой hostname во время исполнения инициализирующих сценариев при загрузке системы. Внутренняя переменная $HOSTNAME Bash получает своё значение посредством вызова функции gethostname().
$HOSTTYPE — тип машины
Подобно $MACHTYPE, идентифицирует аппаратную архитектуру.
bash$ echo $HOSTTYPE i686
$IFS — разделитель полей во вводимой строке (IFS — Internal Field Separator) Эта переменная управляет порядком выделения полей (задаёт символы-разделители) приразборе строки символов. По-умолчанию — пробельный символ (пробел, табуляция и перевод строки), но может быть изменён, например, для разбора строк, в которых отдельные поля разделены запятыми. Обратите внимание: при составлении содержимого переменной $*, Bash использует первый символ из $IFS для разделения аргументов.
bash$ echo $IFS | cat -vte $ bash$ bash -c 'set w x y z; IFS=":-;"; echo "$*"' w:x:y:z
При всём при том следует помнить, что при использовании $IFS пробельные символы обрабатываются несколько иначе, чем все остальные.
Пример: $IFS и пробельные символы.
#!/bin/bash # При использовании $IFS, пробельные символы обрабатываются иначе, чем все остальные. output_args_one_per_line() { for arg do echo "[$arg]" done } echo; echo "IFS=\" \"" echo "-------" IFS=" " var=" a b c " output_args_one_per_line $var # output_args_one_per_line `echo " a b c "` # [a] # [b] # [c] echo; echo "IFS=:" echo "-----" IFS=: var=":a::b:c:::" # То же самое, только пробелы заменены символом ":". output_args_one_per_line $var # # [] # [a] # [] # [b] # [c] # [] # [] # [] # То же самое происходит и с разделителем полей "FS" в awk. # Спасибо Stephane Chazelas. echo exit 0 #(Спасибо S. C., за разъяснения и примеры.)
$LC_COLLATE — Чаще всего устанавливается в .bashrc или /etc/profile', эта переменная задаёт порядок сортировки символов, в операциях подстановки имён файлов и в поиске по шаблону. При неверной настройке переменной LC_COLLATE можно получить весьма неожиданные результаты. Начиная с версии 2.05, Bash, в операциях подстановки имён файлов, не делает различий между символами верхнего и нижнего регистров, в диапазонах символов в квадратных скобках. Например, ls [A-M]* выведет как File1.txt, так и file1.txt. Возврат к общепринятому стандарту поведения шаблонов вквадратных скобках выполняется установкой переменной LC_COLLATE в значение C командой export LC_COLLATE=C в файле /etc/profile и/или ~/.bashrc.
$LC_CTYPE — Эта внутренняя переменная определяет кодировку символов. Используется в операциях подстановки и поиске по шаблону.
$LINENO — Номер строки исполняемого сценария. Эта переменная имеет смысл только внутри исполняемого сценария и чаще всего применяется в отладочных целях.
# *** BEGIN DEBUG BLOCK *** last_cmd_arg=$_ # Запомнить. echo "Строка $LINENO: переменная \"v1\" = $v1" echo "Последний аргумент командной строки = $last_cmd_arg" # *** END DEBUG BLOCK ***
$MACHTYPE — аппаратная архитектура. Идентификатор аппаратной архитектуры.
bash$ echo $MACHTYPE i686
$OLDPWD — прежний рабочий каталог ("OLD-Print-Working-Directory") $OSTYPE — тип операционной системы
bash$ echo $OSTYPE linux
$PATH — путь поиска, как правило включает в себя каталоги /usr/bin/, /usr/X11R6/bin/, /usr/local/bin,и т.д. Когда командный интерпретатор получает команду, то он автоматически пытается отыскать соответствующий исполняемый файл в указанном списке каталогов (в переменной $PATH). Каталоги, в указанном списке, должны отделяться друг от друга двоеточиями. Обычно,переменная $PATH инициализируется в /etc/profile и/или в ~/.bashrc.
bash$ echo $PATH /bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin
Инструкция PATH=${PATH}:/opt/bin добавляет каталог /opt/bin в конец текущего пути поиска.
Иногда может оказаться целесообразным, внутри сценария, временно добавить какой-либо каталог к пути поиска. По завершении работы скрипта, эти изменения будут утеряны(вспомните о том, что невозможно изменить переменные окружения вызывающего процесса). Текущий "рабочий каталог", ./, обычно не включается в $PATH из соображений безопасности.
$PIPESTATUS — Код возврата канала (конвейера). Интересно, что это не то же самое, что код возврата последней исполненной команды.
bash$ echo $PIPESTATUS 0 bash$ ls -al | bogus_command bash: bogus_command: command not found bash$ echo $PIPESTATUS 141 bash$ ls -al | bogus_command bash: bogus_command: command not found bash$ echo $? 127
Переменная $PIPESTATUS может давать неверные значения при вызове из командной строки.
tcsh% bash bash$ who | grep nobody | sort bash$ echo ${PIPESTATUS[*]} 0
Если поместить эти строки в сценарий и исполнить его, то будут выведены верные значения 0 1 0. Спасибо Wayne Pollock за замечания и предоставленный пример.
$PPID — Переменная $PPID хранит PID (идентификатор) родительского процесса. Сравните с командой pidof.
$PROMPT_COMMAND — Переменная хранит команду, которая используется непосредственно для вывода первичного приглашения к вводу — $PS1.
$PS1 — prompt, приглашение командной строки.
$PS2 — Вторичное приглашение командной строки, выводится тогда, когда от пользователя ожидается дополнительный ввод. Отображается как ">".
$PS3 — Третичное приглашение (prompt), выводится тогда, когда пользователь должен сделать выбор в операторе select.
$PS4 — Приглашение (prompt) четвёртого уровня, выводится в начале каждой строки вывода тогда, когда сценарий вызывается с ключом -x. Отображается как "+".
$PWD — рабочий (текущий) каталог
Пример: Аналог встроенной команды pwd.
#!/bin/bash E_WRONG_DIRECTORY=73 clear # Очистка экрана. TargetDirectory=/home/bozo/projects/GreatAmericanNovel cd $TargetDirectory echo "Удаление файлов в каталоге $TargetDirectory." if [ "$PWD" != "$TargetDirectory" ] then # Защита от случайного удаления файлов не в том каталоге. echo "Неверный каталог!" echo "Переменная $PWD указывает на другой каталог!" exit $E_WRONG_DIRECTORY fi rm -rf * rm .[A-Za-z0-9]* # удалить "скрытые" файлы (начинающиеся с ".") # rm -f .[^.]* ..?* удалить файлы, чьи имена начинаются с нескольких точек. # (shopt -s dotglob; rm -f *) тоже работает верно. # Спасибо S.C. за замечание. # Имена файлов могут содержать любые символы из диапазона 0-255, за исключением "/". # Оставляю вопрос удаления файлов с "необычными" символами для самостоятельного изучения. # Здесь можно вставить дополнительные действия, по мере необходимости. echo echo "Конец." echo "Файлы, из каталога $TargetDirectory, удалены." echo exit 0
$REPLY — переменная по-умолчанию, куда записывается ввод пользователя, выполненный с помощью команды read, если явно не задана другая переменная. Так же может использоваться в операторе select, для построения меню выбора.
#!/bin/bash echo echo -n "Ваше любимое растение? " read echo "Ваше любимое растение: $REPLY." # REPLY хранит последнее значение, прочитанное командой "read" тогда, и только тогда # когда команде "read" не передается имя переменной. echo echo -n "Ваш любимый фрукт? " read fruit echo "Ваш любимый фрукт $fruit." echo "но..." echo "Значение переменной \$REPLY осталось равным $REPLY." # Переменная $REPLY не была перезаписана потому, что # следующей команде "read", в качестве аргумента была передана переменная $fruit echo exit 0
$SECONDS — Время паботы сценария в секундах
#!/bin/bash # Автор: Mendel Cooper # Дополнен переводчиком. TIME_LIMIT=10 INTERVAL=1 echo echo "Для прерывания работы сценария, ранее чем через $TIME_LIMIT секунд, нажмите Control-C." echo while [ "$SECONDS" -le "$TIME_LIMIT" ] do # Оригинальный вариант сценария содержал следующие строки # if [ "$SECONDS" -eq 1 ] # then # units=second # else # units=seconds # fi # Однако, из-за того, что в русском языке для описания множественного числа # существует большее число вариантов, чем в английском, # переводчик позволил себе смелость несколько подправить сценарий # (прошу ногами не бить! ;-) ) # === НАЧАЛО БЛОКА ИЗМЕНЕНИЙ, ВНЕСЕННЫХ ПЕРЕВОДЧИКОМ === let "last_two_sym = $SECONDS - $SECONDS / 100 * 100" # десятки и единицы if [ "$last_two_sym" -ge 11 -a "$last_two_sym" -le 19 ] then units="секунд" # для чисел, которые заканчиваются на "...надцать" else let "last_sym = $last_two_sym - $last_two_sym / 10 * 10" # единицы case "$last_sym" in "1" ) units="секунду" # для чисел, заканчивающихся на 1 ;; "2" | "3" | "4" ) units="секунды" # для чисел, заканчивающихся на 2, 3 и 4 ;; * ) units="секунд" # для всех остальных (0, 5, 6, 7, 8, 9) ;; esac fi # === КОНЕЦ БЛОКА ИЗМЕНЕНИЙ, ВНЕСЕННЫХ ПЕРЕВОДЧИКОМ === echo "Сценарий отработал $SECONDS $units." # В случае перегруженности системы, скрипт может перескакивать через отдельные # значения счетчика sleep $INTERVAL done echo -e "\a" # Сигнал! exit 0
$SHELLOPTS — список допустимых опций интерпретатора shell. Переменная доступна только для чтения.
bash$ echo $SHELLOPTS braceexpand:hashall:histexpand:monitor:history:interactive-comments:emacs
$SHLVL — Уровень вложенности shell. Если в командной строке echo $SHLVL даёт 1, то в сценарии значение этой переменной будет больше на 1, т.е. 2.
$TMOUT — Если переменная окружения $TMOUT содержит ненулевое значение, то интерпретатор будет ожидать ввод не более чем заданное число секунд, что, в первичном приглашении (см.описание PS1 выше), может привести к автоматическому завершению сеанса работы. Начиная с версии 2.05b Bash, стало возможным использование $TMOUT в сценариях в комбинации с read.
# Работает только в сценариях Bash, начиная с версии 2.05b. TMOUT=3 # Ожидание ввода не более трех секунд. echo "Ваша любимая песня?" echo "Вводите быстрее, у Вас только $TMOUT секунды на обдумывание!" read song if [ -z "$song" ] then song="(ответ не получен)" # По-умолчанию. fi echo "Вам нравится $song."
Возможны и более сложные варианты организации организации ограничения времени ожидания ввода. Как один из вариантов, можно предложить организовать прерывание цикла ожидания по сигналу. Но это потребует написание функции обработки сигналов командой trap.
Пример: Ограничения времени ожидания ввода.
#!/bin/bash # timed-input.sh # TMOUT=3 бесполезно в сценариях TIMELIMIT=3 # Три секунды в данном случае, но может быть установлено и другое значение PrintAnswer() { if [ "$answer" = TIMEOUT ] then echo $answer else # Чтобы не спутать разные варианты вывода. echo "Ваше любимое растение $answer" kill $! # "Прибить" ненужную больше функцию TimerOn, запущенную в фоновом процессе. # $! — PID последнего процесса, запущенного в фоне. fi } TimerOn() { sleep $TIMELIMIT && kill -s 14 $$ & # Ждать 3 секунды, после чего выдать sigalarm сценарию. } Int14Vector() { answer="TIMEOUT" PrintAnswer exit 14 } trap Int14Vector 14 # переназначить процедуру обработки прерывания от таймера (14) echo "Ваше любимое растение? " TimerOn read answer PrintAnswer# По общему признанию, это не очень хороший способ ограничения времени ожидания, # По общему признанию, это не очень хороший способ ограничения времени ожидания, # однако опция "-t" команды "read" упрощает задачу. # См. "t-out.sh", ниже. # Если вам нужно что-то более элегантное... # подумайте о написании программы на C или C++, # с использованием соответствующих библиотечных функций, таких как 'alarm' и 'setitimer'. exit 0
В качестве альтернативы можно использовать stty.
Пример: Ещё один пример ограничения времени ожидания ввода от пользователя
#!/bin/bash #timeout.sh # Автор: Stephane Chazelas, # дополнен автором документа. INTERVAL=5 # предел времени ожидания timedout_read() { timeout=$1 varname=$2 old_tty_settings=`stty -g` stty -icanon min 0 time ${timeout}0 eval read $varname # или просто read $varname stty "$old_tty_settings" # См. man stty. } echo; echo -n "Как Вас зовут? Отвечайте быстрее! " timedout_read $INTERVAL your_name # Такой прием может не работать на некоторых типах терминалов. # Максимальное время ожидания зависит от терминала. # (чаще всего это 25.5 секунд). echo if [ ! -z "$your_name" ] # Если имя было введено... then echo "Вас зовут $your_name." else echo "Вы не успели ответить." fi echo # Алгоритм работы этого сценария отличается от "timed-input.sh". # Каждое нажатие на клавишу вызывает сброс счетчика в начальное состояние. exit 0
Возможно самый простой способ — использовать опцию -t команды read.
Пример: Ограничение времени ожидания команды read
#!/bin/bash # t-out.sh TIMELIMIT=4 # 4 секунды read -t $TIMELIMIT variable <&1 echo if [ -z "$variable" ] then echo "Время ожидания истекло." else echo "variable = $variable" fi exit 0
$UID — user id number, UID (идентификатор) текущего пользователя, в соответствии с /etc/passwd Это реальный UID текущего пользователя, даже если он временно приобрёл права другого пользователя с помощью su. Переменная $UID доступна только для чтения.
Пример: Я — root?
#!/bin/bash # am-i-root.sh: Root я, или не root? ROOT_UID=0 # $UID root-а всегда равен 0. if [ "$UID" -eq "$ROOT_UID" ] # Настоящий "root"? then echo "- root!" else echo "простой пользователь (но мамочка вас тоже любит)!" fi exit 0 # ============================================================= # # Код, приведённый ниже, никогда не отработает, # поскольку работа сценария уже завершилась выше # Еще один способ отличить root-а от не root-а: ROOTUSER_NAME=root username=`id -nu` # Или... username=`whoami` if [ "$username" = "$ROOTUSER_NAME" ] then echo "Рутти-тутти. - root!" else echo "Вы - лишь обычный юзер." fi
Переменные $ENV, $LOGNAME, $MAIL, $TERM, $USER и $USERNAME, не являются встроенными переменными Bash. Тем не менее, они часто инициализируются как переменные окружения в одном из стартовых файлов Bash. Переменная $SHELL, командная оболочка пользователя, может задаваться в /etc/passwd или в сценарии "init" и она тоже не является встроенной переменной Bash!
tcsh% echo $LOGNAME bozo tcsh% echo $SHELL /bin/tcsh tcsh% echo $TERM rxvt bash$ echo $LOGNAME bozo bash$ echo $SHELL /bin/tcsh bash$ echo $TERM rxvt
- Позиционные параметры (аргументы)
$0, $1, $2 и т.д. — аргументы передаются... из командной строки в сценарий, функциям или команде set
$# — Количество аргументов командной строки, или позиционных параметров
$* — Все аргументы (позиционные параметры), в виде одной строки (слова)
"$*" — необходимо заключать в кавычки.
$@ — То же самое, что и "$*", но при этом каждый параметр представлен как отдельная строка(слово), т.е. параметры не подвергаются какой-либо интерпретации.
"$@" -- необходимо заключать в кавычки.
Пример: Я — root?
#!/bin/bash # Вызовите сценарий с несколькими аргументами, например: "один два три". E_BADARGS=65 if [ ! -n "$1" ] then echo "Порядок использования: `basename $0` argument1 argument2 и т.д." exit $E_BADARGS fi echo index=1 echo "Список аргументов в переменной \"\$*\":" for arg in "$*" # Работает некорректно, если "$*" не ограничена кавычками. do echo "Аргумент #$index = $arg" let "index+=1" done # $* воспринимает все аргументы как одну строку. echo "Полный список аргументов выглядит как одна строка." echo index=1 echo "Список аргументов в переменной \"\$@\":" for arg in "$@" do echo "Аргумент #$index = $arg" let "index+=1" done # $@ воспринимает аргументы как отдельные строки (слова). echo "Список аргументов выглядит как набор различных строк (слов)." echo exit 0
После команды shift (сдвиг), первый аргумент, в переменной "$@", теряется, а остальные сдвигаются на одну позицию "вниз" (или "влево", если хотите).
#!/bin/bash # Вызовите сценарий в таком виде: ./scriptname 1 2 3 4 5 echo "$@" # 1 2 3 4 5 shift echo "$@" # 2 3 4 5 shift echo "$@" # 3 4 5 # Каждая из команд "shift" приводит к потере аргумента $1, # но остальные аргументы остаются в "$@".
Специальная переменная $@ может быть использована для выбора типа ввода в сценария. Команда cat "$@" позволяет выполнять ввод как со стандартного устройства ввода stdin, так ииз файла, имя которого передаётся сценарию из командной строки. Переменные $* и $@, в отдельных случаях, могут содержать противоречивую информацию! Это зависит от содержимого переменной $IFS.
Пример: Противоречия в переменных $* и $@
#!/bin/bash # Демонстрация противоречивости содержимого внутренних переменных "$*" и "$@", # которая проявляется при изменении порядка заключения параметров в кавычки. # Демонстрация противоречивости, проявляющейся при изменении # содержимого переменной IFS. set -- "Первый один" "второй" "третий:один" "" "Пятый: :один" # Установка аргументов $1, $2, и т.д. echo echo 'IFS по-умолчанию, переменная "$*"' c=0 for i in "$*" # в кавычках do echo "$((c+=1)): [$i]" # Эта строка остается без изменений во всех циклах. # Вывод аргументов. done echo --- echo 'IFS по-умолчанию, переменная $*' c=0 for i in $* # без кавычек do echo "$((c+=1)): [$i]" done echo --- echo 'IFS по-умолчанию, переменная "$@"' c=0 for i in "$@" do echo "$((c+=1)): [$i]" done echo --- echo 'IFS по-умолчанию, переменная $@' c=0 for i in $@ do echo "$((c+=1)): [$i]" done echo --- IFS=: echo 'IFS=":", переменная "$*"' c=0 for i in "$*" do echo "$((c+=1)): [$i]" done echo --- echo 'IFS=":", переменная $*' c=0 for i in $* do echo "$((c+=1)): [$i]" done echo --- var=$* echo 'IFS=":", переменная "$var" (var=$*)' c=0 for i in "$var" do echo "$((c+=1)): [$i]" done echo --- echo 'IFS=":", переменная $var (var=$*)' c=0 for i in $var do echo "$((c+=1)): [$i]" done echo --- var="$*" echo 'IFS=":", переменная $var (var="$*")' c=0 for i in $var do echo "$((c+=1)): [$i]" done echo --- echo 'IFS=":", переменная "$var" (var="$*")' c=0 for i in "$var" do echo "$((c+=1)): [$i]" done echo --- echo 'IFS=":", переменная "$@"' c=0 for i in "$@" do echo "$((c+=1)): [$i]" done echo --- echo 'IFS=":", переменная $@' c=0 for i in $@ do echo "$((c+=1)): [$i]" done echo --- var=$@ echo 'IFS=":", переменная $var (var=$@)' c=0 for i in $var do echo "$((c+=1)): [$i]" done echo --- echo 'IFS=":", переменная "$var" (var=$@)' c=0 for i in "$var" do echo "$((c+=1)): [$i]" done echo --- var="$@" echo 'IFS=":", переменная "$var" (var="$@")' c=0 for i in "$var" do echo "$((c+=1)): [$i]" done echo --- echo 'IFS=":", переменная $var (var="$@")' c=0 for i in $var do echo "$((c+=1)): [$i]" done echo# Попробуйте запустить этот сценарий под ksh или zsh -y. exit 0 # Это сценарий написан Stephane Chazelas, # Незначительные изменения внесены автором документа.
Различия между $@ и $* наблюдаются только тогда, когда они помещаются в двойные кавычки!
Пример: Содержимое $* и $@, когда переменная $IFS — пуста
#!/bin/bash # Если переменная $IFS инициализирована "пустым" значением, # то "$*" и "$@" содержат аргументы не в том виде, в каком ожидается. mecho () # Вывод аргументов. { echo "$1,$2,$3"; } IFS="" # Инициализация "пустым" значением. set a b c # Установка аргументов. mecho "$*" # abc,, mecho $* # a,b,c mecho $@ # a,b,c mecho "$@" # a,b,c # Поведение переменных $* и $@, при "пустой" $IFS, зависит # от версии командной оболочки, Bash или sh. # Поэтому, было бы неразумным пользоваться этой "фичей" в своих сценариях. # Спасибо S.C. exit 0
- Прочие специальные переменные
$- — Список флагов, переданных сценарию (командой set)
Эта конструкция изначально была введена в ksh, откуда перекочевала в Bash и, похоже, работает в Bash не совсем надежно. Единственное возможное применение — проверка запущен ли сценарий в интерактивном режиме.
$! — PID последнего, запущенного в фоне, процесса
LOG=$0.log COMMAND1="sleep 100" echo "Запись в лог всех PID фоновых процессов, запущенных из сценария: $0" >> "$LOG" # Таким образом возможен мониторинг и удаление процессов по мере необходимости. echo >> "$LOG" # Команды записи в лог. echo -n "PID of \"$COMMAND1\": " >> "$LOG" ${COMMAND1} & echo $! >> "$LOG" # PID процесса "sleep 100": 1506 # Спасибо Jacques Lederer за предложенный пример.
$_ — Специальная переменная, содержит последний аргумент предыдущей команды.
Пример: Переменная "подчеркивание"
#!/bin/bash echo $_ # /bin/bash # Для запуска сценария был вызван /bin/bash. du >/dev/null # Подавление вывода. echo $_ # du ls -al >/dev/null # Подавление вывода. echo $_ # -al (последний аргумент) : echo $_ # :
$? — Код возврата команды, функции или скрипта
$$ — PID самого процесса-сценария. Переменная $$ часто используется при генерации "уникальных" имён для временных файлов. Обычно это проще, чем вызов mktemp.
Работа со строками
Bash поддерживает на удивление большое количество операций над строками. К сожалению, этот раздел Bash испытывает недостаток унификации. Одни операции являются подмножеством операций подстановки параметров, а другие — совпадают с функциональностью команды Unix — expr. Это приводит к противоречиям в синтаксисе команд и перекрытию функциональных возможностей, не говоря уже о возникающей путанице.
Длина строки:
${#string} expr length $string expr "$string" : '.*' stringZ=abcABC123ABCabcecho ${#stringZ} # 15 echo `expr length $stringZ` # 15 echo `expr "$stringZ" : '.*'`
Пример: Вставка пустых строк между параграфами в текстовом файле.
#!/bin/bash # paragraph-space.sh # Вставка пустых строк между параграфами в текстовом файле. MINLEN=45 # Возможно потребуется изменить это значение. # Строки, содержащие количество символов меньшее, чем $MINLEN # принимаются за последнюю строку параграфа. while read line # Построчное чтение файла от начала до конца... do echo "$line" # Вывод строки. len=${#line} if [ "$len" -lt "$MINLEN" ] then echo # Добавление пустой строки после последней строки параграфа. fi done exit 0
Длина подстроки в строке (подсчёт совпадающих символов ведётся с начала строки):
expr match "$string" '$substring' , где $substring — регулярное выражение.
expr "$string" : '$substring' , где $substring — регулярное выражение.
stringZ=abcABC123ABCabc # |------| echo `expr match "$stringZ" 'abc[A-Z]*.2'` # 8 echo `expr "$stringZ" : 'abc[A-Z]*.2'` # 8
Index:
Номер позиции первого совпадения в $string c первым символом в $substring:
expr index $string $substring
stringZ=abcABC123ABCabc echo `expr index "$stringZ" C12` # 6 # позиция символа C. echo `expr index "$stringZ" 1c` # 3 # символ 'c' (в #3 позиции) совпал раньше, чем '1'.
Эта функция довольно близка к функции strchr() в языке C.
Извлечение подстроки:
${string:position}
Извлекает подстроку из $string, начиная с позиции $position.
Если строка $string — "*" или "@", то извлекается позиционный параметр (аргумент), с номером $position.
${string:position:length}
Извлекает $length символов из $string, начиная с позиции $position.
stringZ=abcABC123ABCabc # 0123456789..... # Индексация начинается с 0. echo ${stringZ:0} # abcABC123ABCabc echo ${stringZ:1} # bcABC123ABCabc echo ${stringZ:7} # 23ABCabc echo ${stringZ:7:3} # 23A # Извлекает 3 символа. # Возможна ли индексация с "правой" стороны строки? echo ${stringZ:-4} # abcABC123ABCabc # По-умолчанию выводится полная строка. # Однако ... echo ${stringZ:(-4)} # Cabc echo ${stringZ: -4} # Cabc # Теперь выводится правильно. # Круглые скобки или дополнительный пробел "экранируют" параметр позиции. # Спасибо Dan Jacobson, за разъяснения.
Если $string — "*" или "@", то извлекается до $length позиционных параметров (аргументов), начиная с $position.
echo ${*:2} # Вывод 2-го и последующих аргументов. echo ${@:2} # То же самое. echo ${*:2:3} # Вывод 3-х аргументов, начиная со 2-го.
expr substr $string $position $length
Извлекает $length символов из $string, начиная с позиции $position.
stringZ=abcABC123ABCabc # 123456789......# Индексация начинается с 1. echo `expr substr $stringZ 1 2` # ab echo `expr substr $stringZ 4 3` # ABC
expr match "$string" '\($substring\)'
Находит и извлекает первое совпадение $substring в $string, где $substring — это регулярное выражение.
expr "$string" : '\($substring\)'
Находит и извлекает первое совпадение $substring в $string, где $substring — это регулярное выражение.
stringZ=abcABC123ABCabc # ======= echo `expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)'` # abcABC1 echo `expr "$stringZ" : '\(.[b-c]*[A-Z]..[0-9]\)'` # abcABC1 echo `expr "$stringZ" : '\(.......\)'` # abcABC1 # Все вышеприведённые операции дают один и тот же результат.
expr match "$string" '.*\($substring\)'
Находит и извлекает первое совпадение $substring в $string, где $substring — это регулярное выражение. Поиск начинается с конца $string.
expr "$string" : '.*\($substring\)'
Находит и извлекает первое совпадение $substring в $string, где $substring — это регулярное выражение. Поиск начинается с конца $string.
stringZ=abcABC123ABCabc# ====== echo `expr match "$stringZ" '.*\([A-C][A-C][A-C][a-c]*\)'` # ABCabc echo `expr "$stringZ" : '.*\(......\)'` # ABCabc
Удаление части строки:
${string#substring}
Удаление самой короткой, из найденных, подстроки $substring в строке $string. Поиск ведётся с начала строки.
${string##substring}
Удаление самой длинной, из найденных, подстроки $substring в строке $string. Поиск ведётся сначала строки.
stringZ=abcABC123ABCabc # |----| # |----------| echo ${stringZ#a*C} # 123ABCabc # Удаление самой короткой подстроки. echo ${stringZ##a*C} # abc # Удаление самой длинной подстроки.
${string%substring}
Удаление самой короткой, из найденных, подстроки $substring в строке $string. Поиск ведётся с конца строки.
${string%%substring}
Удаление самой длинной, из найденных, подстроки $substring в строке $string. Поиск ведётся с конца строки.
stringZ=abcABC123ABCabc #|| # |------------| echo ${stringZ%b*c} # abcABC123ABCa # Удаляется самое короткое совпадение. Поиск ведётся с конца $stringZ. echo ${stringZ%%b*c} # a # Удаляется самое длинное совпадение. Поиск ведётся с конца $stringZ.
Пример: Преобразование графических файлов из одного формата в другой, с изменением имени файла.
#!/bin/bash # cvt.sh: # Преобразование всех файлов в заданном каталоге, # из графического формата MacPaint, в формат "pbm". # Используется утилита "macptopbm", входящая в состав пакета "netpbm", # который сопровождается Brian Henderson (bryanh@giraffe-data.com). # Netpbm — стандартный пакет для большинства дистрибутивов Linux. OPERATION=macptopbm SUFFIX=pbm # Новое расширение файла. if [ -n "$1" ] then directory=$1 # Если каталог задан в командной строке при вызове сценария else directory=$PWD # Иначе просматривается текущий каталог. fi # Все файлы в каталоге, имеющие расширение ".mac", считаются файлами # формата MacPaint. for file in $directory/* # Подстановка имен файлов. do filename=${file%.*c} # Удалить расширение ".mac" из имени файла # ( с шаблоном '.*c' совпадают все подстроки # начинающиеся с '.' и заканчивающиеся 'c', $OPERATION $file > "$filename.$SUFFIX" # Преобразование с перенаправлением в файл с новым именем rm -f $file # Удаление оригинального файла после преобразования. echo "$filename.$SUFFIX" # Вывод на stdout. done exit 0 # Упражнение: # -------- # Сейчас этот сценарий конвертирует *все* файлы в каталоге # Измените его так, чтобы он конвертировал *только* те файлы, # которые имеют расширение ".mac".
Замена подстроки:
${string/substring/replacement}
Замещает первое вхождение $substring строкой $replacement.
${string//substring/replacement}
Замещает все вхождения $substring строкой $replacement.
stringZ=abcABC123ABCabc echo ${stringZ/abc/xyz} # xyzABC123ABCabc # Замена первой подстроки 'abc' строкой 'xyz'. echo ${stringZ//abc/xyz} # xyzABC123ABCxyz # Замена всех подстрок 'abc' строкой 'xyz'.
${string/#substring/replacement}
Подстановка строки $replacement вместо $substring, если строка $string начинается найденным соответствием. Поиск ведётся с начала строки $string.
${string/%substring/replacement}
Подстановка строки $replacement вместо $substring, если строка $string заканчивается найденным соответствием. Поиск ведётся с конца строки $string.
stringZ=abcABC123ABCabc echo ${stringZ/#abc/XYZ} # XYZABC123ABCabc # Поиск ведется с начала строки echo ${stringZ/%abc/XYZ} # abcABC123ABCXYZ # Поиск ведется с конца строки
Использование awk при работе со строками:
В качестве альтернативы, Bash-скрипты могут использовать средства awk при работе со строками.
Пример: Альтернативный способ извлечения подстрок.
#!/bin/bash # substring-extraction.sh String=23skidoo1 # 012345678 Bash # 123456789 awk # Обратите внимание на различия в индексации: # Bash начинает индексацию с '0'. # Awk начинает индексацию с '1'. echo ${String:2:4} # с 3 позиции (0-1-2), 4 символа # skid # В эквивалент в awk: substr(string,pos,length). echo | awk ' { print substr("'"${String}"'",3,4) }' # skid # Передача пустого "echo" по каналу в awk, означает фиктивный ввод, # делая, тем самым, ненужным предоставление имени файла. exit 0
Подстановка параметров
Работа с переменными и/или подстановка их значений
${parameter}
То же самое, что и $parameter, т.е. значение переменной parameter. В отдельных случаях, при возникновении неоднозначности интерпретации, корректно будет работать только такая форма записи: ${parameter}. Может использоваться для конкатенации (слияния) строковых переменных.
your_id=${USER}-on-${HOSTNAME} echo "$your_id" # echo "Старый \$PATH = $PATH" PATH=${PATH}:/opt/bin #Добавление /opt/bin в $PATH. echo "Новый \$PATH = $PATH"
${parameter-default}, ${parameter:-default}
Если параметр отсутствует, то используется значение по-умолчанию.
echo ${username-`whoami`} # Вывод результата работы команды `whoami`, если переменная $username не установлена.
Формы записи ${parameter-default} и ${parameter:-default} в большинстве случаев можно считать эквивалентными. Дополнительный символ : имеет значение только тогда, когда parameter определён, но имеет "пустое" (null)значение.
#!/bin/bash username0= # переменная username0 объявлена, но инициализирована "пустым" значением. echo "username0 = ${username0-`whoami`}" # Вывод после символа "=" отсутствует. echo "username1 = ${username1-`whoami`}" # Переменная username1 не была объявлена. # Выводится имя пользователя, выданное командой `whoami`. username2= # переменная username2 объявлена, но инициализирована "пустым" значением. echo "username2 = ${username2:-`whoami`}" # Выводится имя пользователя, выданное командой `whoami`, поскольку # здесь употребляется конструкция ":-" , а не "-". exit 0
Параметры по-умолчанию очень часто находят применение в случаях, когда сценарию необходимы какие-либо входные аргументы, передаваемые из командной строки, но такие аргументы не были переданы.
DEFAULT_FILENAME=generic.data filename=${1:-$DEFAULT_FILENAME} # Если имя файла не задано явно, то последующие операторы будут работать # с файлом "generic.data".
${parameter=default},${parameter:=default}
Если значения параметров не заданы явно, то они принимают значения по-умолчанию. Оба метода задания значений по-умолчанию до определенной степени идентичны. Символ : имеет значение только когда $parameter был инициализирован "пустым" (null) значением, как показано выше.
echo ${username=`whoami`} # Переменная "username" принимает значение, возвращаемое командой `whoami`.
${parameter+alt_value},${parameter:+alt_value}
Если параметр имеет какое-либо значение, то используется alt_value, иначе — null ("пустая" строка).
Оба варианта до определенной степени идентичны. Символ : имеет значение только если parameter объявлен и "пустой".
echo "###### \${parameter+alt_value} ########" echo a=${param1+xyz} echo "a = $a" # a = param2= a=${param2+xyz} echo "a = $a" # a = xyz param3=123 a=${param3+xyz} echo "a = $a" # a = xyz echo echo "###### \${parameter:+alt_value} ########" echo a=${param4:+xyz} echo "a = $a" # a = param5= a=${param5:+xyz} echo "a = $a" # a = # Вывод отличается от a=${param5+xyz} param6=123 a=${param6+xyz} echo "a = $a" # a = xyz
${parameter?err_msg},${parameter:?err_msg}
Если parameter инициализирован, то используется его значение, в противном случае — выводится err_msg.
Обе формы записи можно, до определенной степени, считать идентичными. Символ : имеет значение только когда parameter инициализирован "пустым" значением.
Пример: Подстановка параметров и сообщения об ошибках.
#!/bin/bash # Проверка отдельных переменных окружения. # Если переменная, к примеру $USER, не установлена, # то выводится сообщение об ошибке. : ${HOSTNAME?} ${USER?} ${HOME?} ${MAIL?} echo echo "Имя машины: $HOSTNAME." echo "Ваше имя: $USER." echo "Ваш домашний каталог: $HOME." echo "Ваш почтовый ящик: $MAIL." echo echo "Если перед Вами появилось это сообщение," echo "то это значит, что все критические переменные окружения установлены." echo echo # ------------------------------------------------------ # Конструкция ${variablename?} так же выполняет проверку # наличия переменной в сценарии. ThisVariable=Value-of-ThisVariable # Обратите внимание, в строковые переменные могут быть записаны # символы, которые запрещено использовать в именах переменных. : ${ThisVariable?} echo "Value of ThisVariable is $ThisVariable". echo echo : ${ZZXy23AB?"Переменная ZZXy23AB не инициализирована."} # Если ZZXy23AB не инициализирована, # то сценарий завершается с сообщением об ошибке. # Текст сообщения об ошибке можно задать свой. # : ${ZZXy23AB?"Переменная ZZXy23AB не инициализирована."} # То же самое: dummy_variable=${ZZXy23AB?} # dummy_variable=${ZZXy23AB?"Переменная ZXy23AB не инициализирована."} # echo ${ZZXy23AB?} >/dev/null echo "Это сообщение не будет напечатано, поскольку сценарий завершится раньше." HERE=0 exit $HERE # Сценарий завершит работу не здесь.
Пример: Подстановка параметров и сообщение о "порядке использования".
#!/bin/bash # usage-message.sh : ${1?"Порядок использования: $0 ARGUMENT"} # Сценарий завершит свою работу здесь, если входные аргументы отсутствуют, # со следующим сообщением. # usage-message.sh: 1: Порядок использования: usage-message.sh ARGUMENT echo "Эти две строки появятся, только когда задан аргумент в командной строке." echo "Входной аргумент командной строки = \"$1\"" exit 0 # Точка выхода находится здесь, только когда задан аргумент командной строки. # Проверьте код возврата в обеих случаях, с и без аргумента командной строки. # Если аргумент задан, то код возврата будет равен 0. # Иначе — 1.
Подстановка параметров и/или экспансия.
Следующие выражения могут служить дополнениями оператора match команды expr, применяемой к строкам. Как правило, они используются при разборе имён файлов и каталогов.
Длина переменной/Удаление подстроки.
${#var}
String length (число символов в переменной $var). В случае массивов, команда ${#array} возвращает длину первого элемента массива.
Исключения:
- ${#*} и ${#@} возвращает количество аргументов (позиционных параметров).
- Для массивов, ${#array[*]} и ${#array[@]} возвращает количество элементов в массиве.
Пример: Длина переменной.
#!/bin/bash # length.sh E_NO_ARGS=65if [ $# -eq 0 ] # Для работы скрипта необходим хотя бы один входной параметр. then echo "Вызовите сценарий с одним или более параметром командной строки." exit $E_NO_ARGS fi var01=abcdEFGH28ij echo "var01 = ${var01}" echo "Length of var01 = ${#var01}" echo "Количество входных параметров = ${#@}" echo "Количество входных параметров = ${#*}" exit 0
${var#Pattern},${var##Pattern}
Удаляет из переменной $var наименьшую/наибольшую подстроку, совпадающую с шаблоном $Pattern. Поиск ведётся с начала строки $var.
# Функция из сценария "days-between.sh". 95 # Удаляет нули, стоящие в начале аргумента-строки. strip_leading_zero () # Ведущие нули, которые могут находиться в номере дня/месяца, лучше удалить { # В противном случае Bash будет интерпретировать числа как восьмеричные return=${1#0} # "1" — это аргумент "$1". } # "0" — это то, что удаляется из "$1" — ведущие нули.
Manfred Schwarb предложил более сложный вариант вышеприведенного кода:
strip_leading_zero2 () # Удалить ведущие нули. { shopt -s extglob # Переход в режим расширенной подстановки. local val=${1##+(0)} # Поиск самой длинной последовательности нулей. shopt -u extglob # выход из режима расширенной подстановки. _strip_leading_zero2=${val:-0} # Если был введен 0, то возвращается 0, а не "". }
Другой пример:
echo `basename $PWD` # Имя текущего рабочего каталога. echo "${PWD##*/}" # Имя текущего рабочего каталога. echo echo `basename $0` # Имя файла-сценария. echo $0 # Имя файла-сценария. echo "${0##*/}" # Имя файла-сценария. echo filename=test.data echo "${filename##*.}" # data # Расширение файла.
${var%Pattern},${var%%Pattern}
Удаляет из переменной $var наименьшую/наибольшую подстроку, совпадающую с шаблоном $Pattern. Поиск ведётся с конца строки $var.Bash версии 2 имеет ряд дополнительных возможностей.
Пример: Поиск по шаблону в подстановке параметров.
#!/bin/bash # Поиск по шаблону в операциях подстановки параметров # ## % %%. var1=abcd12345abc6789 pattern1=a*c # * (символ шаблона), означает любые символы между a и c. echo echo "var1 = $var1" # abcd12345abc6789 echo "var1 = ${var1}" # abcd12345abc6789 (альтернативный вариант) echo "Число символов в ${var1} = ${#var1}" echo "pattern1 = $pattern1" # a*c (между 'a' и 'c' могут быть любые символы) echo echo '${var1#$pattern1} =' "${var1#$pattern1}" # d12345abc6789 # Наименьшая подстрока, удаляются первые 3 символа abcd12345abc6789 ^^^^^^ |-| echo '${var1##$pattern1} =' "${var1##$pattern1}" # 6789 # Наибольшая подстрока, удаляются первые 12 символов abcd12345abc6789 # ^^^^^^ |----------| echo; echo pattern2=b*9 # все, что между 'b' и '9' echo "var1 = $var1" # abcd12345abc6789 echo "pattern2 = $pattern2" echo echo '${var1%pattern2} =' "${var1%$pattern2}" # abcd12345a # Наименьшая подстрока, удаляются последние 6 символов abcd12345abc6789 # ^^^^^^^^^ |----| echo '${var1%%pattern2} =' "${var1%%$pattern2}" # a # Наибольшая подстрока, удаляются последние 12 символов abcd12345abc6789 # ^^^^^^^^^ |-------------| # Запомните, # и ## используются для поиска с начала строки, # % и %% используются для поиска с конца строки. echo exit 0
Пример: Изменение расширений в именах файлов.
#!/bin/bash # rfe # --- # Изменение расширений в именах файлов. # # rfe old_extension new_extension # Пример: # Изменить все расширения *.gif в именах файлов на *.jpg, в текущем каталоге # rfe gif jpg ARGS=2 E_BADARGS=65 if [ $# -ne "$ARGS" ] then echo "Порядок использования: `basename $0` old_file_suffix new_file_suffix" exit $E_BADARGS fi for filename in *.$1 # Цикл прохода по списку имен файлов, имеющих расширение равное первому аргументу. do mv $filename ${filename%$1}$2 # Удалить первое расширение и добавить второе, done exit 0
Подстановка значений переменных / Замена подстроки
Эти конструкции перекочевали в Bash из ksh.
${var:pos}
Подставляется значение переменной var, начиная с позиции pos.
${var:pos:len}
Подставляется значение переменной var, начиная с позиции pos, не более len символов.
${var/Pattern/Replacement}
Первое совпадение с шаблоном Pattern, в переменной var замещается подстрокой Replacement. Если подстрока Replacement отсутствует, то найденное совпадение будет удалено.
${var//Pattern/Replacement}
Глобальная замена. Все найденные совпадения с шаблоном Pattern, в переменной var, будут замещены подстрокой Replacement. Как и в первом случае, если подстрока Replacement отсутствует, то все найденные совпадения будут удалены.
Пример: Поиск по шаблону при анализе произвольных строк.
#!/bin/bash var1=abcd-1234-defg echo "var1 = $var1" t=${var1#*-*} echo "var1 (все, от начала строки по первый символ \"-\", включительно, удаляется) = $t" # t=${var1#*-} то же самое, # поскольку оператор # ищет кратчайшее совпадение, # а * соответствует любым предшествующим символам, включая пустую строку. # (Спасибо S. C. за разъяснения.) t=${var1##*-*} echo "Если var1 содержит \"-\", то возвращается пустая строка... var1 = $t" t=${var1%*-*} echo "var1 (все, начиная с последнего \"-\" удаляется) = $t" echo # ------------------------------------------- path_name=/home/bozo/ideas/thoughts.for.today # ------------------------------------------- echo "path_name = $path_name" t=${path_name##/*/} echo "Из path_name удален путь к файлу = $t" # В данном случае, тот эе эффект можно получить так: t=`basename $path_name` # t=${path_name%/}; t=${t##*/} более общее решение, # но имеет некоторые ограничения. # Если $path_name заканчивается символом перевода строки, то `basename $path_name` не будет работать, # но для данного случая вполне применимо. # (Спасибо S.C.) t=${path_name%/*.*} # Тот же эффект дает t=`dirname $path_name` echo "Из path_name удалено имя файла = $t" # Этот вариант будет терпеть неудачу в случаях: "../", "/foo////", # "foo/", "/". # Удаление имени файла, особенно когда его нет, # использование dirname имеет свои особенности. # (Спасибо S.C.) echo t=${path_name:11} echo "Из $path_name удалены первые 11 символов = $t" t=${path_name:11:5} echo "Из $path_name удалены первые 11 символов, выводится 5 символов = $t" echo t=${path_name/bozo/clown} echo "В $path_name подстрока \"bozo\" заменена на \"clown\" = $t" t=${path_name/today/} echo "В $path_name подстрока \"today\" удалена = $t" t=${path_name//o/O} echo "В $path_name все символы \"o\" переведены в верхний регистр, = $t" t=${path_name//o/} echo "Из $path_name удалены все символы \"o\" = $t" exit 0
${var/#Pattern/Replacement}
Если в переменной var найдено совпадение с Pattern, причём совпадающая подстрока расположена в начале строки (префикс), то оно заменяется на Replacement. Поиск ведётся сначала строки.
${var/%Pattern/Replacement}
Если в переменной var найдено совпадение с Pattern, причём совпадающая подстрока расположена в конце строки (суффикс), то оно заменяется на Replacement. Поиск ведётся с конца строки.
Пример: Поиск префиксов и суффиксов с заменой по шаблону.
#!/bin/bash # Поиск с заменой по шаблону. v0=abc1234zip1234abc # Начальное значение переменной. echo "v0 = $v0" # abc1234zip1234abc echo # Поиск совпадения с начала строки. v1=${v0/#abc/ABCDEF} # abc1234zip1234abc # |-| echo "v1 = $v1" # ABCDE1234zip1234abc # |---| # Поиск совпадения с конца строки. v2=${v0/%abc/ABCDEF} # abc1234zip123abc # |-| echo "v2 = $v2" # abc1234zip1234ABCDEF # |----| echo # ---------------------------------------------------- # Если совпадение находится не с начала/конца строки, # то замена не производится. # ---------------------------------------------------- v3=${v0/#123/000} # Совпадение есть, но не в начале строки. echo "v3 = $v3" # abc1234zip1234abc # ЗАМЕНА НЕ ПРОИЗВОДИТСЯ! v4=${v0/%123/000} # Совпадение есть, но не в конце строки. echo "v4 = $v4" # abc1234zip1234abc # ЗАМЕНА НЕ ПРОИЗВОДИТСЯ! exit 0
${!varprefix*},${!varprefix@}
Поиск по шаблону всех, ранее объявленных переменных, имена которых начинаются с varprefix.
xyz23=whatever xyz24= a=${!xyz*} # Подстановка имен объявленных переменных, которые начинаются с "xyz". echo "a = $a" # a = xyz23 xyz24 a=${!xyz@} # То же самое. echo "a = $a" # a = xyz23 xyz24 # Эта возможность была добавлена в Bash, в версии 2.04.
Объявление переменных: declare и typeset
Инструкции declare и typeset являются встроенными инструкциями (они абсолютно идентичны друг другу и являются синонимами) и предназначена для наложения ограничений на переменные. Это очень слабая попытка контроля над типами, которая имеется во многих языках программирования. Инструкция declare появилась в Bash, начиная с версии 2. Кроме того, инструкция typeset может использоваться и в ksh-сценариях.
Ключи инструкций declare/typeset
-r readonly (только для чтения)
declare -r var1
(declare -r var1 аналогично объявлению readonly var1).
Это грубый эквивалент констант (const) в языке C. Попытка изменения таких переменных завершается сообщением об ошибке.
-i integer
declare -i number
# Сценарий интерпретирует переменную "number" как целое число. number=3 echo "number = $number" # number = 3 number=three echo "number = $number" # number = 0 # Строка "three" интерпретируется как целое число.
Допускается выполнение некоторых арифметических операций над переменными, объявленными как integer, не прибегая к инструкциям expr или let.
n=6/3 echo "n = $n" # n = 6/3 declare -i n n=6/3 echo "n = $n" # n = 2
-a array
declare -a indices
Переменная indices объявляется массивом.
-f functions
declare -f
Инструкция declare -f, без аргументов, приводит к выводу списка ранее объявленных функций в сценарии.
declare -f function_name
Инструкция declare -f function_name выводит имя функции function_name, если она была объявлена ранее.
-x export
declare -x var3
Эта инструкция объявляет переменную, как доступную для экспорта.
-x var=$value
declare -x var3=373
Инструкция declare допускает совмещение объявления и присваивания значения переменной одновременно.
Пример: Объявление переменных с помощью инструкции declare.
#!/bin/bash func1 () { echo Это функция. } declare -f # Список функций, объявленных выше. echo declare -i var1 # var1 — целочисленная переменная. var1=2367 echo "переменная var1 объявлена как $var1" var1=var1+1 # Допустимая арифметическая операция над целочисленными переменными. echo "переменная var1 увеличена на 1 = $var1." # Допустимая операция для целочисленных переменных echo "Возможно ли записать дробное число 2367.1 в var1?" var1=2367.1 # Сообщение об ошибке, переменная не изменяется. echo "значение переменной var1 осталось прежним = $var1" echo declare -r var2=13.36 # инструкция 'declare' допускает установку свойств переменной # и одновременно присваивать значение. echo "var2 declared as $var2" # Допускается ли изменять значение readonly переменных? var2=13.37 # Сообщение об ошибке и завершение работы сценария. echo "значение переменной var2 осталось прежним $var2" # Эта строка никогда не будет выполнена. exit 0 # Сценарий завершит работу выше.
Косвенные ссылки на переменные
Предположим, что значение одной переменной — есть имя второй переменной. Возможно ли получить значение второй переменной через обращение к первой?
Например, пусть a=letter_of_alphabet и letter_of_alphabet=z, тогда вопрос будет звучать так:
"Возможно ли получить значение z, обратившись к переменной a?".
В действительности это возможно и это называется косвенной ссылкой. Для этого необходимо прибегнуть к несколько необычной нотации eval var1=\$$var2.
Пример: Косвенные ссылки.
#!/bin/bash # Косвенные ссылки на переменные. a=letter_of_alphabet letter_of_alphabet=z echo # Прямое обращение к переменной. echo "a = $a" # Косвенное обращение к переменной. eval a=\$$a echo "А теперь a = $a" echo # Теперь попробуем изменить переменную, на которую делается ссылка. t=table_cell_3 table_cell_3=24 echo "\"table_cell_3\" = $table_cell_3" echo -n "разыменование (получение ссылки) \"t\" = "; eval echo \$$t # В данном, простом, случае, # eval t=\$$t; echo "\"t\" = $t" # даёт тот же результат (почему?). echo t=table_cell_3 NEW_VAL=387 table_cell_3=$NEW_VAL echo "Значение переменной \"table_cell_3\" изменено на $NEW_VAL." echo "Теперь \"table_cell_3\" = $table_cell_3" echo -n "разыменование (получение ссылки) \"t\" = "; eval echo \$$t # инструкция "eval" принимает два аргумента "echo" и "\$$t" (назначает равным $table_cell_3) echo # (Спасибо S.C. за разъяснения.) # Ещё один способ — нотация ${!t}, будет обсуждаться в разделе "Bash, версия 2". exit 0
Пример: Передача косвенных ссылок в awk.
#!/bin/bash # Другая версия сценария "column totaler" # который суммирует заданную колонку (чисел) в заданном файле. # Здесь используются косвенные ссылки. ARGS=2 E_WRONGARGS=65 if [ $# -ne "$ARGS" ] # Проверка количества входных аргументов. then echo "Порядок использования: `basename $0` filename column-number" exit $E_WRONGARGS fi filename=$1 column_number=$2 #===== До этой строки идентично первоначальному варианту сценария =====# # Многострочные скрипты awk вызываются конструкцией awk ' ..... ' # Начало awk-сценария. # ------------------------------------------------ awk " { total += \$${column_number} # косвенная ссылка } END { print total } " "$filename" # ------------------------------------------------ # Конец awk-сценария. # Косвенные ссылки делают возможным бесконфликтное # обращение к переменным shell внутри вложенных сценариев awk. # Спасибо Stephane Chazelas. exit 0
Такой метод обращения к переменным имеет свои особенности. Если переменная, на которую делается ссылка, меняет свое значение, то переменная которая ссылается, должна быть должным образом разыменована, т.е. должна быть выполнена операция получения ссылки, как это делается в примере выше. К счастью, нотация ${!variable}, введённая в Bash, начиная с версии 2 позволяет выполнять косвенные ссылки более интуитивно понятным образом.
$RANDOM: генерация псевдослучайных целых чисел
$RANDOM — внутренняя функция Bash (не константа), которая возвращает псевдослучайные целые числа в диапазоне 0—32767. Функция $RANDOM не должна использоваться для генерации ключей шифрования.
Пример: Генерация случайных чисел.
#!/bin/bash # $RANDOM возвращает различные случайные числа при каждом обращении к ней. # Диапазон изменения: 0—32767 (16-битовое целое со знаком). MAXCOUNT=10 count=1 echo echo "$MAXCOUNT случайных чисел:" echo "-----------------" while [ "$count" -le $MAXCOUNT ] # Генерация 10 ($MAXCOUNT) случайных чисел. do number=$RANDOM echo $number let "count += 1" # Нарастить счетчик. done echo "-----------------" # Если вам нужны случайные числа не превышающие определенного числа, # воспользуйтесь оператором деления по модулю (остаток от деления). RANGE=500 echo number=$RANDOM let "number %= $RANGE" echo "Случайное число меньше $RANGE —— $number" echo # Если вы желаете ограничить диапазон "снизу", # то просто производите генерацию псевдослучайных чисел в цикле до тех пор, # пока не получите число большее нижней границы. FLOOR=200 number=0 # инициализация while [ "$number" -le $FLOOR ] do number=$RANDOM done echo "Случайное число, большее $FLOOR —— $number" echo # Эти два способа могут быть скомбинированы. number=0 #initialize while [ "$number" -le $FLOOR ] do number=$RANDOM let "number %= $RANGE" # Ограничение "сверху" числом $RANGE. done echo "Случайное число в диапазоне от $FLOOR до $RANGE —— $number" echo # Генерация случайных "true" и "false" значений. BINARY=2 number=$RANDOMT=1 let "number %= $BINARY" # let "number >>= 14" даёт более равномерное распределение # (сдвиг вправо смещает старший бит на нулевую позицию, остальные биты обнуляются). if [ "$number" -eq $T ] then echo "TRUE" else echo "FALSE" fi echo # Можно имитировать бросание 2-х игровых кубиков. SPOTS=7 # остаток от деления на 7 дает диапазон 0 - 6. ZERO=0 die1=0 die2=0 # Кубики "выбрасываются" раздельно. while [ "$die1" -eq $ZERO ] # Пока на "кубике" ноль. do let "die1 = $RANDOM % $SPOTS" # Имитировать бросок первого кубика. done while [ "$die2" -eq $ZERO ] do let "die2 = $RANDOM % $SPOTS" # Имитировать бросок второго кубика. done let "throw = $die1 + $die2" echo "Результат броска кубиков = $throw" echo exit 0
Пример: Выбор случайной карты из колоды.
#!/bin/bash # pick-card.sh # Пример выбора случайного элемента массива. # Выбор случайной карты из колоды. Suites="Треф Бубей Червей Пик" Denominations="2 3 4 5 6 7 8 9 10 Валет Дама Король Туз" # Инициализация массивов. suite=($Suites) denomination=($Denominations) num_suites=${#suite[*]} # Количество элементов массивов. num_denominations=${#denomination[*]} echo -n "${denomination[$((RANDOM%num_denominations))]} " echo ${suite[$((RANDOM%num_suites))]} # $bozo sh pick-cards.sh # Валет Треф # Спасибо "jipe," за пояснения по работе с $RANDOM. exit 0
Jipe подсказал ещё один способ генерации случайных чисел из заданного диапазона.
# Генерация случайных чисел в диапазоне 6—30. rnumber=$((RANDOM%25+6)) # Генерируется случайное число из диапазона 6—30, # но при этом число должно делиться на 3 без остатка. rnumber=$(((RANDOM%30/3+1)*3)) # Примечательно, если $RANDOM возвращает 0 # то это приводит к возникновению ошибки. # Упражнение: Попробуйте разобраться с выражением самостоятельно.
Bill Gradwohl предложил усовершенствованную формулу генерации положительных псевдослучайных чисел в заданном диапазоне.
rnumber=$(((RANDOM%(max-min+divisibleBy))/divisibleBy*divisibleBy+min))
В сценарии ниже, Bill представил универсальную функцию, которая возвращает псевдослучайное число из заданного диапазона.
Пример: Псевдослучайное число из заданного диапазона.
#!/bin/bash # random-between.sh # Псевдослучайное число из заданного диапазона. # Автор: Bill Gradwohl, # незначительные изменения внесены автором документа. # Используется с разрешения автора сценария. randomBetween() { # Генерация положительных и отрицательных псевдослучайных чисел, # в диапазоне от $min до $max, # которые кратны числу $divisibleBy. # # Bill Gradwohl — Oct 1, 2003 syntax() { # Вложенная функция. echo echo "Порядок вызова: randomBetween [min] [max] [multiple]" echo echo "Функция ожидает до 3-х входных аргументов, но они не являются обязательными." echo "min — нижняя граница диапазона" echo "max — верхняя граница диапазона" echo "multiple — делитель, на который должен делиться результат без остатка." echo echo "Если какой либо из параметров отсутствует, по-умолчанию принимаются значения: 0 32767 1" echo "В случае успеха функция возвращает 0, иначе — 1" echo "и это сообщение о порядке вызова." echo "Результат возвращается в глобальной переменной randomBetweenAnswer" echo "Входные параметры, имеющие отрицательные значения, обрабатываются корректно." } local min=${1:-0} local max=${2:-32767} local divisibleBy=${3:-1} # При отсутствии какого-либо из входных параметров, они принимают значения по-умолчанию. local x local spread # Делитель должен быть положительным числом. [ ${divisibleBy} -lt 0 ] && divisibleBy=$((0-divisibleBy)) # Проверка корректности входных параметров. if [ $# -gt 3 -o ${divisibleBy} -eq 0 -o ${min} -eq ${max} ]; then syntax return 1 fi # Если min больше чем max, то поменять их местами. if [ ${min} -gt ${max} ]; then # Поменять местами. x=${min} min=${max} max=${x} fi # Если min не делится без остатка на $divisibleBy, # то привести его к ближайшему подходящему числу из заданного диапазона. if [ $((min/divisibleBy*divisibleBy)) -ne ${min} ]; then if [ ${min} -lt 0 ]; then min=$((min/divisibleBy*divisibleBy)) else min=$((((min/divisibleBy)+1)*divisibleBy)) fi fi # Если max не делится без остатка на $divisibleBy, # то привести его к ближайшему подходящему числу из заданного диапазона. if [ $((max/divisibleBy*divisibleBy)) -ne ${max} ]; then if [ ${max} -lt 0 ]; then max=$((((max/divisibleBy)-1)*divisibleBy)) else max=$((max/divisibleBy*divisibleBy)) fi fi # --------------------------------------------------------------------- # А теперь собственно нахождение псевдослучайного числа. # Обратите внимание: чтобы получить псевдослучайное число в конце диапазона # необходимо рассматривать диапазон от 0 до # abs(max-min)+divisibleBy, а не abs(max-min)+1. # Этим превышением верхней границы диапазона можно пренебречь # поскольку эта новая граница никогда не будет достигнута. # Если использовать при вычислении формулу abs(max-min)+1, # то будут получаться вполне корректные значения, но при этом, # возвращаемые значения будут значительно ниже # верхней границы диапазона. # --------------------------------------------------------------------- spread=$((max-min)) [ ${spread} -lt 0 ] && spread=$((0-spread)) let spread+=divisibleBy randomBetweenAnswer=$(((RANDOM%spread)/divisibleBy*divisibleBy+min)) return 0 } # Проверка функции. min=-14 max=20 divisibleBy=3 # Создадим массив, который будет содержать счетчики встречаемости каждого из чисел # в заданном диапазоне. declare -a answer minimum=${min} maximum=${max} if [ $((minimum/divisibleBy*divisibleBy)) -ne ${minimum} ]; then if [ ${minimum} -lt 0 ]; then minimum=$((minimum/divisibleBy*divisibleBy)) else minimum=$((((minimum/divisibleBy)+1)*divisibleBy)) fi fi # Если max не делится без остатка на $divisibleBy, # то привести его к ближайшему подходящему числу из заданного диапазона. if [ $((maximum/divisibleBy*divisibleBy)) -ne ${maximum} ]; then if [ ${maximum} -lt 0 ]; then maximum=$((((maximum/divisibleBy)-1)*divisibleBy)) else maximum=$((maximum/divisibleBy*divisibleBy)) fi fi # Необходимое условие при работе с массивами — # индекс массива должен быть положительным числом, # поэтому введена дополнительная переменная displacement, которая # гарантирует положительность индексов. displacement=$((0-minimum)) for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do answer[i+displacement]=0 done # Цикл с большим количеством итераций, чтобы посмотреть - что мы получаем. loopIt=1000 # Автор сценария предложил 100000 итераций, # но в этом случае цикл работает чересчур долго. for ((i=0; i<${loopIt}; ++i)); do # Обратите внимание: числа min и max передаются функции в обратном порядке, # чтобы продемонстрировать, что функция обрабатывает их корректно. randomBetween ${max} ${min} ${divisibleBy} # Вывод сообщения об ошибке, если функция вернула некорректное значение. [ ${randomBetweenAnswer} -lt ${min} -o ${randomBetweenAnswer} -gt ${max} ] && echo Выход за границы диапазона MIN .. MAX - ${randomBetweenAnswer}! [ $((randomBetweenAnswer%${divisibleBy})) -ne 0 ] && echo Число не делится на заданный делитель без остатка - ${randomBetweenAnswer}! # Записать полученное число в массив. answer[randomBetweenAnswer+displacement]=$((answer[randomBetweenAnswer+displacement]+1)) done # Проверим полученные результаты for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do [ ${answer[i+displacement]} -eq 0 ] && echo "Число $i не было получено ни разу." || echo "Число ${i} встречено ${answer[i+displacement]} раз." done exit 0
Насколько случайны числа, возвращаемые функцией $RANDOM? Лучший способ оценить"случайность" генерируемых чисел — это написать сценарий, который будет имитировать бросание игрального кубика достаточно большое число раз, а затем выведет количество выпадений каждой из граней...
Пример: Имитация бросания кубика с помощью RANDOM.
#!/bin/bash # Случайные ли числа возвращает RANDOM? RANDOM=$$ # Инициализация генератора случайных чисел числом PID процесса-сценария. PIPS=6 # Кубик имеет 6 граней. MAXTHROWS=600 # Можете увеличить, если не знаете куда девать свое время. throw=0 # Счётчик бросков. zeroes=0 # Обнулить счётчики выпадения отдельных граней. ones=0 # т.к. неинициализированные переменные - "пустые", и не равны нулю!. twos=0 threes=0 fours=0 fives=0 sixes=0 print_result () { echo echo "единиц = $ones" echo "двоек = $twos" echo "троек = $threes" echo "четвёрок = $fours" echo "пятёрок = $fives" echo "шестёрок = $sixes" echo } update_count() { case "$1" in 0) let "ones += 1";; # 0 соответствует грани "1". 1) let "twos += 1";; # 1 соответствует грани "2", и так далее 2) let "threes += 1";; 3) let "fours += 1";; 4) let "fives += 1";; 5) let "sixes += 1";; esac } echo while [ "$throw" -lt "$MAXTHROWS" ] do let "die1 = RANDOM % $PIPS" update_count $die1 let "throw += 1" done print_result # Количество выпадений каждой из граней должно быть примерно одинаковым, если считать RANDOM достаточно случайным. # Для $MAXTHROWS = 600, каждая грань должна выпасть примерно 100 раз (плюс-минус 20). # # Имейте ввиду, что RANDOM - это генератор ПСЕВДОСЛУЧАЙНЫХ чисел, # Упражнение: # --------------- # Перепишите этот сценарий так, чтобы он имитировал 1000 бросков монеты. # Перепишите этот сценарий так, чтобы он имитировал 1000 бросков монеты. # На каждом броске возможен один из двух вариантов выпадения — "ОРЁЛ" или "РЕШКА". exit 0
Как видно из последнего примера, неплохо было бы производить переустановку начального числа генератора случайных чисел RANDOM перед тем, как начать работу с ним. Если используется одно и тоже начальное число, то генератор RANDOM будет выдавать одну и ту же последовательность чисел.(Это совпадает с поведением функции random() в языке C.)
Пример: Переустановка RANDOM.
#!/bin/bash # seeding-random.sh: Переустановка переменной RANDOM. MAXCOUNT=25 # Длина генерируемой последовательности чисел. random_numbers () { count=0 while [ "$count" -lt "$MAXCOUNT" ] do number=$RANDOM echo -n "$number " let "count += 1" done } echo; echo RANDOM=1 # Переустановка начального числа генератора случайных чисел RANDOM. random_numbers echo; echo RANDOM=1 # То же самое начальное число... random_numbers # ...в результате получается та же последовательность чисел. # В каких случаях может оказаться полезной генерация совпадающих серий? echo; echo RANDOM=2 # Ещё одна попытка, но с другим начальным числом... random_numbers # получим другую последовательность. echo; echo # RANDOM=$$ в качестве начального числа выбирается PID процесса-сценария. # Вполне допустимо взять в качестве начального числа результат работы команд 'time' или 'date'. # Немного воображения... SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }') # Псевдослучайное число забирается # из системного генератора псевдослучайных чисел /dev/urandom, # затем конвертируется в восьмеричное число командой "od", # и наконец "awk" возвращает единственное число для переменной SEED. RANDOM=$SEED random_numbers echo; echo exit 0
Системный генератор /dev/urandom даёт последовательность псевдослучайных чисел с более равномерным распределением, чем $RANDOM. Команда dd if=/dev/urandom of=targetfile bs=1count=XX создаёт файл, содержащий последовательность псевдослучайных чисел. Однако, эти числа требуют дополнительной обработки, например с помощью команды od (этот приём используется в примере выше) или dd. Есть и другие способы генерации псевдослучайных последовательностей в сценариях. Awk имеет для этого достаточно удобные средства.
Пример: Получение псевдослучайных чисел с помощью awk.
#!/bin/bash # random2.sh: Генерация псевдослучайных чисел в диапазоне 0 - 1. # Используется функция rand() из awk. AWKSCRIPT=' { srand(); print rand() } ' # Команды/параметры, передаваемые awk # Обратите внимание, функция srand() переустанавливает начальное число генератора случайных чисел echo -n "Случайное число в диапазоне от 0 до 1 = " echo | awk "$AWKSCRIPT" exit 0 # Упражнения: # --------- # 1) С помощью оператора цикла выведите 10 различных случайных чисел. # (Подсказка: вам потребуется вызвать функцию "srand()" # в каждом цикле с разными начальными числами. # Что произойдет, если этого не сделать?) # 2) Заставьте сценарий генерировать случайные числа в диапазоне 10 - 100 # используя целочисленный множитель, как коэффициент масштабирования # 3) То же самое, что и во втором упражнении, # но на этот раз случайные числа должны быть целыми.
Кроме того, команда date так же может использоваться для генерации псевдослучайных целых чисел.
Двойные круглые скобки
Эта конструкция во многом похожа на инструкцию let, внутри ((...)) вычисляются арифметические выражения и возвращается их результат. В простейшем случае, конструкция a=$(( 5 + 3 )) присвоит переменной "a" значение выражения "5 + 3", или 8. Но, кроме того, двойные круглые скобки позволяют работать с переменными в стиле языка C.
Пример: Работа с переменными в стиле языка C.
#!/bin/bash echo (( a = 23 )) # Присвоение переменной в стиле C, с обоих строн от "=" стоят пробелы. echo "a (начальное значение) = $a" (( a++ )) # Пост-инкремент 'a', в стиле C. echo "a (после a++) = $a" (( a-- )) # Пост-декремент 'a', в стиле C. echo "a (после a--) = $a" (( ++a )) # Пред-инкремент 'a', в стиле C. echo "a (после ++a) = $a" (( --a )) # Пред-декремент 'a', в стиле C. echo "a (после --a) = $a" echo (( t = a<45?7:11 )) # Трехместный оператор в стиле языка C. echo "If a < 45, then t = 7, else t = 11." echo "t = $t " # Да! echo# См. так же описание ((...)) в циклах "for" и "while". # Эта конструкция доступна в Bash, начиная с версии 2.04. exit 0
Циклы и ветвления
Управление ходом исполнения — один из ключевых моментов структурной организации сценариев на языке командной оболочки. Циклы и переходы являются теми инструментальными средствами, которые обеспечивают управление порядком исполнения команд.
Циклы
Цикл — это блок команд, который исполняется многократно до тех пор, пока не будет выполнено условие выхода из цикла.
Циклы 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
Пример: Простой цикл for.
#!/bin/bash # Список планет. for planet in Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон do echo $planet done echo # Если 'список аргументов' заключить в кавычки, то он будет восприниматься как единственный аргумент. for planet in "Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон" do echo $planet done exit 0
Каждый из элементов [списка] может содержать несколько аргументов. Это бывает полезным при обработке групп параметров. В этом случае, для принудительного разбора каждого из аргументов в списке, необходимо использовать инструкцию set.
Пример: Цикл 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, можно использовать переменную.
Пример: 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 могут быть использованы имена файлов, которые в свою очередь могут содержать символы-шаблоны.
Пример: Обработка списка файлов в цикле 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 не задан, то в качестве оного используется переменная $@ — список аргументов командной строки.
Пример: Цикл for без списка аргументов.
#!/bin/bash # Попробуйте вызвать этот сценарий с аргументами и без них и посмотреть на результаты. for a do echo -n "$a " done # Список аргументов не задан, поэтому цикл работает с переменной '$@' # (список аргументов командной строки, включая пробельные символы). echo exit 0
При создании списка аргументов, в цикле for допускается пользоваться подстановкой команд.
Пример: Создание списка аргументов в цикле 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
Более сложный пример использования подстановки команд при создании списка аргументов цикла.
Пример: grep для бинарных файлов.
#!/bin/bash # bin-grep.sh: Поиск строк в двоичных файлах. # замена "grep" для бинарных файлов. # Аналогично команде "grep -a" E_BADARGS=65 E_NOFILE=66if [ $# -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
Ещё один пример.
Пример: Список всех пользователей системы.
#!/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
И заключительный пример использования подстановки команд при создании [списка].
Пример: Проверка авторства всех бинарных файлов в текущем каталоге.
#!/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 может передаваться другим командам по конвейеру.
Пример: Список символических ссылок в каталоге.
#!/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 в файл, ниже приводится немного модифицированный вариант предыдущего примера, демонстрирующий эту возможность.
Пример: Список символических ссылок в каталоге, сохраняемый в файле.
#!/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. Для этого используются двойные круглые скобки.
Пример: 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
А сейчас пример сценария, который может найти "реальное" применение.
Пример: Работа с командой 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
Оператор while проверяет условие перед началом каждой итерации и если условие истинно(если код возврата равен 0), то управление передаётся в тело цикла. В отличие от циклов for, циклы while используются в тех случаях, когда количество итераций заранее не известно.
while [condition] do command... done
Как и в случае с циклами for/in, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ ";" перед do.
while [condition] ; do
Обратите внимание: в отдельных случаях, таких как использование конструкции getopts совместно с оператором while, синтаксис несколько отличается от приводимого здесь.
Пример: Простой цикл 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
Пример: Другой пример цикла 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 может иметь несколько условий. Но только последнее из них определяет возможность продолжения цикла. В этом случае синтаксис оператора цикла должен быть несколько иным.
Пример: Цикл 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-подобной нотации, с использованием двойных круглых скобок.
Пример: 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
Оператор цикла until проверяет условие в начале каждой итерации, но в отличие от while итерация возможна только в том случае, если условие ложно.
until [condition-is-true] do command... done
Обратите внимание: оператор until проверяет условие завершения цикла ПЕРЕД очередной итерацией, а не после, как это принято в некоторых языках программирования. Как и в случае с циклами for/in, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ ";" перед do.
until [condition-is-true] ; do
Пример: Цикл until.
#!/bin/bash until [ "$var1" = end ] # Проверка условия производится в начале итерации. do echo "Введите значение переменной #1 " echo "(end - выход)" read var1 echo "значение переменной #1 = $var1" done exit 0
Вложенные циклы
Цикл называется вложенным, если он размещается внутри другого цикла. На первом проходе, внешний цикл вызывает внутренний, который исполняется до своего завершения, после чего управление передаётся в тело внешнего цикла. На втором проходе внешний цикл опять вызывает внутренний. И так до тех пор, пока не завершится внешний цикл. Само собой, как внешний, так и внутренний циклы могут быть прерваны командой break.
Пример: Вложенный цикл
#!/bin/bash # Вложенные циклы "for". outer=1 # Счетчик внешнего цикла. # Начало внешнего цикла. for a in 1 2 3 4 5 do echo "Итерация #$outer внешнего цикла." echo "---------------------" inner=1 # Сброс счетчика вложенного цикла. # Начало вложенного цикла. for b in 1 2 3 4 5 do echo "Итерация #$inner вложенного цикла." let "inner+=1" # Увеличить счетчик итераций вложенного цикла. done # Конец вложенного цикла. let "outer+=1" # Увеличить счетчик итераций внешнего цикла. echo # Пустая строка для отделения итераций внешнего цикла. done # Конец внешнего цикла. exit 0
Управление ходом выполнения цикла
break, continue
Для управления ходом выполнения цикла служат команды break и continueи точно соответствуют своим аналогам в других языках программирования. Команда break прерывает
исполнение цикла, в то время как continue передает управление в начало цикло, минуя все последующие команды в теле цикла.
Пример: Команды break и continue в цикле.
#!/bin/bash LIMIT=19 # Верхний предел echo echo "Печать чисел от 1 до 20 (исключая 3 и 11)." a=0 while [ $a -le "$LIMIT" ] do a=$(($a+1)) if [ "$a" -eq 3 ] || [ "$a" -eq 11 ] # Исключить 3 и 11 then continue # Переход в начало цикла. fi echo -n "$a " done # Упражнение: # Почему число 20 тоже выводится? echo; echo echo Печать чисел от 1 до 20, но взгляните, что происходит после вывода числа 2 ################################################################## # Тот же цикл, только 'continue' заменено на 'break'. a=0 while [ "$a" -le "$LIMIT" ] do a=$(($a+1)) if [ "$a" -gt 2 ] then break # Завершение работы цикла. fi echo -n "$a " done echo; echo; echo exit 0
Команде break может быть передан необязательный параметр. Команда break без параметра прерывает тот цикл, в который она вставлена, а break N прерывает цикл, стоящий на N уровней выше (причем 1-й уровень — это уровень текущего цикла, прим. перев.).
Пример: Прерывание многоуровневых циклов.
#!/bin/bash # break-levels.sh: Прерывание циклов. # "break N" прерывает исполнение цикла, стоящего на N уровней выше текущего. for outerloop in 1 2 3 4 5 do echo -n "Группа $outerloop: " for innerloop in 1 2 3 4 5 do echo -n "$innerloop " if [ "$innerloop" -eq 3 ] then break # Попробуйте "break 2", # тогда будут прерываться как вложенный, так и внешний циклы fi done echo done echo exit 0
Команда continue, как и команда break, может иметь необязательный параметр. В простейшем случае, команда continue передаёт управление в начало текущего цикла, а команда continue N прерывает исполнение текущего цикла и передаёт управление в начало внешнего цикла, отстоящего от текущего на N уровней (причем 1-й уровень — это уровень текущего цикла,прим. перев.).
Пример: Передача управления в начало внешнего цикла.
#!/bin/bash # Команда "continue N" передаёт управление в начало внешнего цикла, отстоящего от текущего на N уровней. for outer in I II III IV V # внешний цикл do echo; echo -n "Группа $outer: " for inner in 1 2 3 4 5 6 7 8 9 10 # вложенный цикл do if [ "$inner" -eq 7 ] then continue 2 # Передача управления в начало цикла 2-го уровня. # попробуйте убрать параметр 2 команды "continue" fi echo -n "$inner " # 8 9 10 никогда не будут напечатаны. done done echo; echo # Упражнение: # Подумайте, где реально можно использовать "continue N" в сценариях. exit 0
Пример: Живой пример использования "continue N".
# Albert Reiner привёл пример использования "continue N": # --------------------------------------------------------- # Допустим, у меня есть большое количество задач, обрабатывающие некоторые данные, # которые хранятся в некоторых файлах, с именами, задаваемыми по шаблону, # в заданном каталоге. # Есть несколько машин, которым открыт доступ к этому каталогу # и я хочу распределить обработку информации между машинами. # тогда я обычно для каждой машины пишу нечто подобное: while true do for n in .iso.* do [ "$n" = ".iso.opts" ] && continue beta=${n#.iso.} [ -r .Iso.$beta ] && continue [ -r .lock.$beta ] && sleep 10 && continue lockfile -r0 .lock.$beta || continue echo -n "$beta: " `date` run-isotherm $beta date ls -alF .Iso.$beta [ -r .Iso.$beta ] && rm -f .lock.$beta continue 2 done break done # Конкретная реализация цикла, особенно sleep N, зависит от конкретных применений, # но в общем случае он строится по такой схеме: while true do for job in {шаблон} do {файл уже обработан или обрабатывается} && continue {пометить файл как обрабатываемый, обработать, пометить как обработанный} continue 2 done break # Или что нибудь подобное `sleep 600', чтобы избежать завершения. done # Этот сценарий завершит работу после того как все данные будут обработаны # (включая данные, которые поступили во время обработки). Использование # соответствующих lock-файлов позволяет вести обработку на нескольких машинах # одновременно, не производя дублирующих вычислений [которые, в моем случае, # выполняются в течении нескольких часов, так что для меня это очень важно]. # Кроме того, поскольку поиск необработанных файлов всегда начинается с # самого начала, можно задавать приоритеты в именах файлов. Конечно, можно # обойтись и без `continue 2', но тогда придется ввести дополнительную # проверку — действительно ли был обработан тот или иной файл # (чтобы перейти к поиску следующего необработанного файла).
Конструкция continue N довольно сложна в понимании и применении, поэтому, вероятно лучше будет постараться избегать её использования.
Операторы выбора
Инструкции case и select технически не являются циклами, поскольку не предусматривают многократное исполнение блока кода. Однако, они, как и циклы, управляют ходом исполнения программы, в зависимости от начальных или конечных условий.
case (in) / esac
Конструкция case эквивалентна конструкции switch в языке C/C++. Она позволяет выполнять тот или иной участок кода, в зависимости от результатов проверки условий. Она является, своего рода, краткой формой записи большого количества операторов if/then/else и может быть неплохим инструментом при создании разного рода меню.
case "$variable" in "$condition1" ) command... ;; "$condition2" ) command... ;; esac
- Заключать переменные в кавычки необязательно, поскольку здесь не производится разбиения на отдельные слова.
- Каждая строка с условием должна завершаться правой (закрывающей)круглой скобкой — ).
- Каждый блок команд, отрабатывающих по заданному условию, должен завершаться двумя символами точка-с-запятой — ;;.
- Блок case должен завершаться ключевым словом esac (case записанное в обратном порядке).
Пример: Использование case.
#!/bin/bash echo; echo "Нажмите клавишу и затем клавишу Return." read Keypress case "$Keypress" in [a-z] ) echo "буква в нижнем регистре";; [A-Z] ) echo "Буква в верхнем регистре";; [0-9] ) echo "Цифра";; * ) echo "Знак пунктуации, пробел или что-то другое";; esac # Допускается указывать диапазоны символов в [квадратных скобках]. # Упражнение: # -------- # Сейчас сценарий считывает нажатую клавишу и завершается. # Измените его так, чтобы сценарий продолжал отвечать на нажатия клавиш, # но завершался бы только после ввода символа "X". # Подсказка: заключите всё в цикл "while". exit 0
Пример: Создание меню с помощью case.
#!/bin/bash # Грубый пример базы данных clear # Очистка экрана echo " Список" echo " ------" echo "Выберите интересующую Вас персону:" echo echo "[E]vans, Roland" echo "[J]ones, Mildred" echo "[S]mith, Julie" echo "[Z]ane, Morris" echo read person case "$person" in # Обратите внимание: переменная взята в кавычки. "E" | "e" ) # Пользователь может ввести как заглавную, так и строчную букву. echo echo "Roland Evans" echo "4321 Floppy Dr." echo "Hardscrabble, CO 80753" echo "(303) 734-9874" echo "(303) 734-9892 fax" echo "revans@zzy.net" echo "Старый друг и партнер по бизнесу" ;; # Обратите внимание: блок кода, анализирующий конкретный выбор, завершается # двумя символами "точка-с-запятой". "J" | "j" ) echo echo "Mildred Jones" echo "249 E. 7th St., Apt. 19" echo "New York, NY 10009" echo "(212) 533-2814" echo "(212) 533-9972 fax" echo "milliej@loisaida.com" echo "Подружка" echo "День рождения: 11 февраля" ;; # Информация о Smith и Zane будет добавлена позднее. * ) # Выбор по-умолчанию. # "Пустой" ввод тоже обрабатывается здесь. echo echo "Нет данных." ;; esac echo # Упражнение: # -------- # Измените этот сценарий таким образом, чтобы он не завершал работу # после вывода информации о персоне, а переходил на ожидание нового # ввода от пользователя. exit 0
Очень хороший пример использования case для анализа аргументов, переданных из командной строки.
#! /bin/bash case "$1" in "") echo "Порядок использования: ${0##*/} <filename>"; exit 65;; # Параметры командной строки отсутствуют, # или первый параметр — "пустой". # Обратите внимание на ${0##*/} это подстановка параметра ${var##pattern}. В результате получается $0. -*) FILENAME=./$1;; # Если имя файла (аргумент $1) начинается с "-", # то заменить его на ./$1 # тогда параметр не будет восприниматься как ключ команды. * ) FILENAME=$1;; # В противном случае — $1. esac
Пример: Оператор case допускает использовать подстановку команд вместо анализируемой переменной.
#!/bin/bash # Подстановка команд в "case". case $( arch ) in # команда "arch" возвращает строку, описывающую аппаратную апхитектуру. i386 ) echo "Машина на базе процессора 80386";; i486 ) echo "Машина на базе процессора 80486";; i586 ) echo "Машина на базе процессора Pentium";; i686 ) echo "Машина на базе процессора Pentium2 или выше";; * ) echo "Машина на другом типе процессора";; esac exit 0
Оператор case допускает использование шаблонных конструкций.
Пример: Простой пример сравнения строк.
#!/bin/bash # match-string.sh: простое сравнение строк match_string () { MATCH=0 NOMATCH=90 PARAMS=2 # Функция требует два входных аргумента. BAD_PARAMS=91 [ $# -eq $PARAMS ] || return $BAD_PARAMS case "$1" in "$2") return $MATCH;; * ) return $NOMATCH;; esac } a=one b=two c=three d=two match_string $a # неверное число аргументов echo $? # 91 match_string $a $b # не равны echo $? # 90 match_string $b $d # равны echo $? # 0 exit 0
Пример: Проверка ввода.
#!/bin/bash # isalpha.sh: Использование "case" для анализа строк. SUCCESS=0 FAILURE=-1 isalpha () # Проверка - является ли первый символ строки символом алфавита. { if [ -z "$1" ] # Вызов функции без входного аргумента? then return $FAILURE fi case "$1" in [a-zA-Z] *) return $SUCCESS;; # Первый символ - буква? * ) return $FAILURE;; esac } # Сравните с функцией "isalpha ()" в языке C. isalpha2 () # Проверка - состоит ли вся строка только из символов алфавита. { [ $# -eq 1 ] || return $FAILURE case $1 in *[!a-zA-Z]*|"") return $FAILURE;; *) return $SUCCESS;; esac } isdigit () # Проверка - состоит ли вся строка только из цифр. { # Другими словами - является ли строка целым числом. [ $# -eq 1 ] || return $FAILURE case $1 in *[!0-9]*|"") return $FAILURE;; *) return $SUCCESS;; esac } check_var () # Интерфейс к isalpha{ { if isalpha "$@" then echo "\"$*\" начинается с алфавитного символа." if isalpha2 "$@" then # Дальнейшая проверка не имеет смысла, если первй символ не буква. echo "\"$*\" содержит только алфавитные символы." else echo "\"$*\" содержит по меньшей мере один не алфавитный символ." fi else echo "\"$*\" начинается с не алфавитного символа ." # Если функция вызвана без входного параметра, # то считается, что строка содержит "не алфавитной" символ. fi echo } digit_check () # Интерфейс к isdigit (). { if isdigit "$@" then echo "\"$*\" содержит только цифры [0 - 9]." else echo "\"$*\" содержит по меньшей мере один не цифровой символ." fi echo } a=23skidoo b=H3llo c=-What? d=What? e=`echo $b` # Подстановка команды. f=AbcDef g=27234 h=27a34 i=27.34 check_var $a check_var $b check_var $c check_var $d check_var $e check_var $f check_var # Вызов без параметра, что произойдет? # digit_check $g digit_check $h digit_check $i exit 0 # Сценарий дополнен S.C. # Упражнение: # -------- # Напишите функцию 'isfloat ()', которая проверяла бы вещественные числа. # Подсказка: Эта функция подобна функции 'isdigit ()', # надо лишь добавить анализ наличия десятичной точки.
select
Оператор select был заимствован из Korn Shell, и является еще одним инструментом,используемым при создании меню.
select variable [in list] do command... break done
Этот оператор предлагает пользователю выбрать один из представленных вариантов.Примечательно, что select по-умолчанию использует в качестве приглашения к вводу (prompt)— PS3 (#?), который легко изменить.
Пример: Создание меню с помощью select.
#!/bin/bash PS3='Выберите ваш любимый овощ: ' # строка приглашения к вводу (prompt) echo select vegetable in "бобы" "морковь" "картофель" "лук" "брюква" do echo echo "Вы предпочитаете $vegetable." echo ";-))" echo break # если 'break' убрать, то получится бесконечный цикл. done exit 0
Если в операторе select список in list не задан, то в качестве списка будет использоваться список аргументов ($@), передаваемый сценарию или функции.
Сравните это с поведением оператора цикла
for variable [in list]
в котором не задан список аргументов.
Пример: Создание меню с помощью select в функции.
#!/bin/bash PS3='Выберите ваш любимый овощ: ' echo choice_of() { select vegetable # список выбора [in list] отсутствует, поэтому 'select' использует входные аргументы функции. do echo echo "Вы предпочитаете $vegetable." echo ";-))" echo break done } choice_of бобы рис морковь редис томат шпинат # $1 $2 $3 $4 $5 $6 # передача списка выбора в функцию choice_of() exit 0
Внутренние команды
Внутренняя команда — это команда, которая встроена непосредственно в Bash. Команды делаются встроенными либо из соображений производительности — встроенные команды исполняются быстрее,чем внешние, которые, как правило, запускаются в дочернем процессе, либо из-за необходимости прямого доступа к внутренним структурам командного интерпретатора.
Действие, когда какая либо команда или сама командная оболочка инициирует (порождает) новый подпроцесс, что бы выполнить какую-либо работу, называется ветвлением (forking) процесса. Новый процесс называется "дочерним" (или "потомком"), а породивший его процесс — "родительским" (или "предком"). В результате и потомок и предок продолжают исполняться одновременно — параллельно друг другу. В общем случае, встроенные команды Bash, при исполнении внутри сценария, не порождают новый подпроцесс, в то время как вызов внешних команд, как правило, приводит к созданию нового подпроцесса. |
Внутренние команды могут иметь внешние аналоги. Например, внутренняя команда Bash — echo имеет внешний аналог /bin/echo и их поведение практически идентично.
#!/bin/bash echo "Эта строка выводится внутренней командой \"echo\"." /bin/echo "А эта строка выводится внешней командой /bin/echo."
Ключевое слово (keyword) — это зарезервированное слово, синтаксический элемент (token) или оператор. Ключевые слова имеют специальное назначение для командного интерпретатора, и фактически являются элементами синтаксиса языка командной оболочки. В качестве примера можно привести "for", "while", "do", "!", которые являются ключевыми (или зарезервированными) словами. Подобно встроенным командам, ключевые слова жёстко зашиты в Bash, но в отличие от встроенных команд, ключевые слова не являются командами как таковыми, хотя при этом могут являться их составной частью.
Ввод/вывод
echo
Выводит (на stdout) выражение или содержимое переменной.
echo Hello echo $a
Для вывода экранированных символов, echo требует наличие ключа -e.
Обычно, команда echo выводит в конце символ перевода строки. Подавить вывод это символа можно ключом -n.
Команда echo может использоваться для передачи информации по конвейеру другим командам:
if echo "$VAR" | grep -q txt # if [[ $VAR = *txt* ]] then echo "$VAR содержит подстроку \"txt\"" fi
Кроме того, команда echo, в комбинации с подстановкой команд может участвовать в операции присвоения значения переменной.
a=`echo "HELLO" | tr A-Z a-z`
Следует запомнить, что команда echo `command` удалит все символы перевода строки, которые будут выведены командой `command`.
Переменная $IFS обычно содержит символ перевода строки \n, как один из вариантов пробельного символа. Bash разобьёт вывод команды `command`, по пробельным символам, на аргументы и передаст их команде echo, которая выведет эти аргументы, разделённые пробелами.
bash$ ls -l /usr/share/apps/kjezz/sounds -rw-r--r-- 1 root root 1407 Nov 7 2000 reflect.au -rw-r--r-- 1 root root 362 Nov 7 2000 seconds.au bash$ echo `ls -l /usr/share/apps/kjezz/sounds` total 40 -rw-r--r-- 1 root root 716 Nov 7 2000 reflect.au -rw-r--r-- 1 root root 362 Nov 7 2000 seconds.au
Это встроенная команда Bash и имеет внешний аналог /bin/echo.
bash$ type -a echo echo is a shell builtin echo is /bin/echo
printf
printf — команда форматированного вывода, расширенный вариант команды echo и ограниченный вариант библиотечной функции printf() в языке C, к тому же синтаксис их несколько отличается друг от друга.
printf format-string... parameter...
Это встроенная команда Bash. Имеет внешний аналог /bin/printf или /usr/bin/printf. За более подробной информацией обращайтесь к страницам справочного руководства man 1 printf по системным командам.
!Старые версии Bash могут не поддерживать команду printf.
Пример: printf в действии.
#!/bin/bash # printf demo # От переводчика: # Считаю своим долгом напомнить, что в качестве разделителя дробной и целой # частей в вещественных числах, может использоваться символ "запятая" # (в русских локалях), поэтому данный сценарий может выдавать сообщение # об ошибке (у меня так и произошло) при выводе числа PI. # Тогда попробуйте заменить в определении числа PI десятичную точку # на запятую — это должно помочь. ;-) PI=3,14159265358979 DecimalConstant=31373 Message1="Поздравляю," Message2="Землянин." echo printf "Число пи с точностью до 2 знака после запятой = %1.2f" $PI echo printf "Число пи с точностью до 9 знака после запятой = %1.9f" $PI # Даже округляет правильно. printf "\n" # Перевод строки, printf "Константа = \t%d\n" $DecimalConstant # Вставлен символ табуляции (\t) printf "%s %s \n" $Message1 $Message2 echo # ==========================================# # Эмуляция функции 'sprintf' в языке C. # Запись форматированной строки в переменную. echo Pi12=$(printf "%1.12f" $PI) echo "Число пи с точностью до 12 знака после запятой = $Pi12" Msg=`printf "%s %s \n" $Message1 $Message2` echo $Msg; echo $Msg exit 0
Одно из полезных применений команды printf — форматированный вывод сообщений об ошибках.
E_BADDIR=65 var=nonexistent_directory error() { printf "$@" >&2 # Форматированный вывод аргументов на stderr. echo exit $E_BADDIR } cd $var || error $"Невозможно перейти в каталог %s." "$var" # Спасибо S.C.
read
"Читает" значение переменной с устройства стандартного ввода — stdin, в интерактивном режиме это означает клавиатуру. Ключ -a позволяет записывать значения в массивы.
Пример: Ввод значений переменных с помощью read.
#!/bin/bash echo -n "Введите значение переменной 'var1': " # Ключ -n подавляет вывод символа перевода строки. read var1 # Обратите внимание — перед именем переменной отсутствует символ '$'. echo "var1 = $var1" echo # Одной командой 'read' можно вводить несколько переменных. echo -n "Введите значения для переменных 'var2' и 'var3' (через пробел или табуляцию): " read var2 var3 echo "var2 = $var2 var3 = $var3" # Если было введено значение только одной переменной, то вторая останется "пустой". exit 0
Если команде read не была передано ни одной переменной, то ввод будет осуществлён в переменную $REPLY.
Пример: Пример использования команды read без указания переменной для ввода.
#!/bin/bash echo # -------------------------- # # Первый блок кода. echo -n "Введите значение: " read var echo "\"var\" = "$var"" # Здесь нет ничего неожиданного. # -------------------------- # echo echo -n "Введите другое значение: " read # Команда 'read' употребляется без указания переменной для ввода, # тем не менее... # По-умолчанию ввод осуществляется в переменную $REPLY. var="$REPLY" echo "\"var\" = "$var"" # Эта часть сценария эквивалентна первому блоку, выделенному выше. echo exit 0
Обычно, при вводе в окне терминала с помощью команды read, символ \ служит для экранирования символа перевода строки. Ключ -r заставляет интерпретировать символ \ как обычный символ.
Пример: Ввод многострочного текста с помощью read.
#!/bin/bash echo echo "Введите строку, завершающуюся символом \\, и нажмите ENTER." echo "Затем введите вторую строку, и снова нажмите ENTER." read var1 # При чтении, символ "\" экранирует перевод строки. # первая строка \ # вторая строка echo "var1 = $var1" # var1 = первая строка вторая строка # После ввода каждой строки, завершающейся символом "\", # вы можете продолжать ввод на другой строке. echo; echo echo "Введите другую строку, завершающуюся символом \\, и нажмите ENTER." read -r var2 # Ключ -r заставляет команду "read" воспринимать "\" # как обычный символ. # первая строка \ echo "var2 = $var2" # var2 = первая строка \ # Ввод данных прекращается сразу же после первого нажатия на клавишу ENTER. echo exit 0
Команда read имеет ряд очень любопытных опций, которые позволяют выводить подсказку — приглашение ко вводу (prompt), и даже читать данные не дожидаясь нажатия на клавишу ENTER.
# Чтение данных, не дожидаясь нажатия на клавишу ENTER. read -s -n1 -p "Нажмите клавишу " keypress echo; echo "Была нажата клавиша "\"$keypress\""." # -s — подавляет эхо-вывод, т.е. ввод с клавиатуры не отображается на экране. # -n N — ввод завершается автоматически, сразу же после ввода N-го символа. # -p — задает вид строки подсказки - приглашения к вводу (prompt). # Использование этих ключей немного осложняется тем, что они должны следовать в определенном порядке.
Ключ -n, кроме всего прочего, позволяет команде read обнаруживать нажатие курсорных и некоторых других служебных клавиш.
Пример: Обнаружение нажатия на курсорные клавиши.
#!/bin/bash # arrow-detect.sh: Обнаружение нажатия на курсорные клавиши, и не только... # Спасибо Sandro Magi за то что показал мне — как. # -------------------------------------------- # Коды клавиш. arrowup='\[A' arrowdown='\[B' arrowrt='\[C' arrowleft='\[D' insert='\[2' delete='\[3' # -------------------------------------------- SUCCESS=0 OTHER=65 echo -n "Нажмите на клавишу... " # Может потребоваться нажать на ENTER, если была нажата клавиша # не входящая в список выше. read -n3 key # Прочитать 3 символа. echo -n "$key" | grep "$arrowup" #Определение нажатой клавиши. if [ "$?" -eq $SUCCESS ] then echo "Нажата клавиша \"." exit $SUCCESS fi echo -n "$key" | grep "$arrowdown" if [ "$?" -eq $SUCCESS ] then echo "Нажата клавиша \" exit $SUCCESS fi echo -n "$key" | grep "$arrowrt" if [ "$?" -eq $SUCCESS ] then echo "Нажата клавиша \"О\"." exit $SUCCESS fi echo -n "$key" | grep "$arrowleft" if [ "$?" -eq $SUCCESS ] then echo "Нажата клавиша \"." exit $SUCCESS fi echo -n "$key" | grep "$insert" if [ "$?" -eq $SUCCESS ] then echo "Нажата клавиша \"Insert\"." exit $SUCCESS fi echo -n "$key" | grep "$delete" if [ "$?" -eq $SUCCESS ] then echo "Нажата клавиша \"Delete\"." exit $SUCCESS fi echo " Нажата какая-то другая клавиша." exit $OTHER # Упражнения: # --------- # 1) Упростите сценарий, заменив множество if-ов # одной конструкцией 'case'. # 2) Добавьте определение нажатий на клавиши "Home", "End", "PgUp" и "PgDn".
Ключ -t позволяет ограничивать время ожидания ввода командой read
Команда read может считывать значения для переменных из файла, перенаправленного на stdin. Если файл содержит не одну строку, то переменной будет присвоена только первая строка. Если команде read будет передано несколько переменных, то первая строка файла будет разбита, по пробелам, на несколько подстрок, каждая из которых будет записана в свою переменную. Будьте осторожны!
Пример: Чтение командой read из файла через перенаправление.
read var1 <data-file echo "var1 = $var1" # Первая строка из "data-file" целиком записывается в переменную var1 read var2 var3 < data-file echo "var2 = $var2 var3 = $var3" # Обратите внимание! # Поведение команды "read" далеко от ожидаемого! # 1) Произошел возврат к началу файла. # 2) Вместо того, чтобы последовательно читать строки из файла, # по числу переменных, первая строка файла была разбита на подстроки, # разделенные пробелами, которые и были записаны в переменные. # 3) В последнюю переменную была записана вся оставшаяся часть строки. # 4) Если команде "read" будет передано большее число переменных, чем подстрок # в первой строке файла, то последние переменные останутся "пустыми". echo "------------------------------------------------" # Эта проблема легко разрешается с помощью цикла: while read line do echo "$line" done < data-file# Спасибо Heiner Steven за разъяснения. echo "------------------------------------------------" # Разбор строки, разделенной на поля # Для задания разделителя полей, используется переменная $IFS, echo "Список всех пользователей:" OIFS=$IFS; IFS=: # В файле /etc/passwd, в качестве разделителя полей # используется символ ":". while read name passwd uid gid fullname ignore do echo "$name ($fullname)" done < /etc/passwd # перенаправление ввода. IFS=$OIFS # Восстановление предыдущего состояния переменной $ # Эту часть кода написал Heiner Steven. # Если переменная $IFS устанавливается внутри цикла, # то отпадает необходимость сохранения ее первоначального значения # во временной переменной. # Спасибо Dim Segebart за разъяснения. echo "------------------------------------------------" echo "Список всех пользователей:" while IFS=: read name passwd uid gid fullname ignore do echo "$name ($fullname)" done < /etc/passwd # перенаправление ввода. echo echo "Значение переменной \$IFS осталось прежним: $IFS" exit 0
Передача информации, выводимой командой echo, по конвейеру команде read, будет вызывать ошибку. Тем не менее, передача данных по конвейеру от cat, кажется срабатывает.
cat file1 file2 | while read line do echo $line done
Не смотря на это, как указывает Bjon Eriksson:
Пример: Проблемы чтения данных из канала (конвейера).
#!/bin/sh # readpipe.sh # Этот сценарий предоставил Bjon Eriksson. last="(null)" cat $0 | while read line do echo "{$line}" last=$line done printf "\nКонец, последняя строка:$last\n" exit 0 # Конец сценария.
Далее следует результат (частично) работы сценария.
./readpipe.sh {#!/bin/sh} {last="(null)"} {cat $0 |} {while read line} {do} {echo "{$line}"} {last=$line} {done} {printf "nКонец, последняя строка:$last\n"} Конец, последняя строка:(null) Переменная (last) инициализируется в подоболочке, поэтому она оказывается не инициализированной за его пределами.
Файловая система
cd
Уже знакомая нам команда cd, изменяющая текущий каталог, может быть использована в случаях, когда некоторую команду необходимо запустить только находясь в определённом каталоге.
(cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)
Команда cd с ключом -P\ (physical) игнорирует символические ссылки. Команда "cd -" выполняет переход в каталог $OLDPWD</blue> — предыдущий рабочий каталог. Неожиданным образом выполняется команда cd, если ей передать, в качестве каталога назначения, два слэша.
bash$ cd // bash$ pwd //
Само собой разумеется, это должен был бы быть каталог /. Эта проблема наблюдается как в командной строке, так и в сценариях.
pwd
Выводит название текущего рабочего каталога (Print Working Directory). Кроме того, имя текущего каталога хранится во внутренней переменной $PWD.
pushd,popd,dirs
Этот набор команд является составной частью механизма "закладок" на каталоги и позволяет перемещаться по каталогам вперед и назад в заданном порядке. Для хранения имён каталогов используется стек (LIFO — "последний вошёл, первый вышел").
pushd dir-name — помещает имя текущего каталога в стек и осуществляет переход в каталог dir-name.
popd — выталкивает, находящееся на вершине стека, имя каталога и одновременно осуществляет переход в каталог, оказавшийся на вершине стека.
dirs — выводит содержимое стека каталогов (сравните с переменной $DIRSTACK). В случае успеха, обе команды — pushd и popd автоматически вызывают dirs.
Эти команды могут оказаться весьма полезными, когда в сценарии нужно производить частую смену каталогов, но при этом не хочется жестко "зашивать" имена каталогов. Обратите внимание: содержимое стека каталогов постоянно хранится в переменной-массиве - $DIRSTACK.
Пример: Смена текущего каталога.
#!/bin/bash dir1=/usr/local dir2=/var/spool pushd $dir1 # Команда 'dirs' будет вызвана автоматически (на stdout будет выведено содержимое стека). echo "Выполнен переход в каталог `pwd`." # Обратные одиночные кавычки. # Теперь можно выполнить какие либо действия в каталоге 'dir1'. pushd $dir2 echo "Выполнен переход в каталог `pwd`." # Теперь можно выполнить какие либо действия в каталоге 'dir2'. echo "На вершине стека находится: $DIRSTACK." popd echo "Возврат в каталог `pwd`." # Теперь можно выполнить какие либо действия в каталоге 'dir1'. popd echo "Возврат в первоначальный рабочий каталог `pwd`." exit 0
Переменные
let
Команда let производит арифметические операции над переменными. В большинстве случаев, её можно считать упрощенным вариантом команды expr.
Пример: Команда let, арифметические операции.
#!/bin/bash echo let a=11 # То же, что и 'a=11' let a=a+5 # Эквивалентно "a = a + 5" # (Двойные кавычки и дополнительные пробелы делают код более удобочитаемым) echo "11 + 5 = $a" let "a <<= 3" # Эквивалентно let "a = a << 3" echo "\"\$a\" (=16) после сдвига влево на 3 разряда = $a" let "a /= 4" # Эквивалентно let "a = a / 4" echo "128 / 4 = $a" let "a -= 5" # Эквивалентно let "a = a - 5" echo "32 - 5 = $a" let "a = a * 10" # Эквивалентно let "a = a * 10" echo "27 * 10 = $a" let "a %= 8" # Эквивалентно let "a = a % 8" echo "270 mod 8 = $a (270 / 8 = 33, остаток = $a)" echo exit 0
eval
eval arg1 [arg2] ... [argN]
Транслирует список аргументов, из списка, в команды.
Пример: Демонстрация команды eval.
#!/bin/bash y=`eval ls -l` # Подобно y=`ls -l` echo $y # но символы перевода строки не выводятся, поскольку имя переменной не в кавычках. echo echo "$y" # Если имя переменной записать в кавычках — символы перевода строки сохраняются. echo; echo y=`eval df` # Аналогично y=`df` # Когда производится подавление вывода символов LF (перевод строки), то анализ # результатов различными утилитами, такими как awk, можно сделать проще. exit 0
Пример: Принудительное завершение сеанса.
#!/bin/bash y=`eval ps ax | sed -n '/ppp/p' | awk '{ print $1 }'` # Выяснить PID процесса 'ppp'. kill -9 $y # "Прихлопнуть" его # Предыдущие строки можно заменить одной строкой # kill -9 `ps ax | awk '/ppp/ { print $1 }' chmod 666 /dev/ttyS3 # Завершённый, по сигналу SIGKILL, ppp изменяет права доступа # к последовательному порту. Вернуть их в первоначальное состояние. rm /var/lock/LCK..ttyS3 # Удалить lock-файл последовательного порта. exit 0
Пример: Шифрование по алгоритму "rot13".
#!/bin/bash # Реализация алгоритма шифрования "rot13" с помощью 'eval'. # Сравните со сценарием "rot13.sh". setvar_rot_13() # Криптование по алгоритму "rot13" { local varname=$1 varvalue=$2 eval $varname='$(echo "$varvalue" | tr a-z n-za-m)' } setvar_rot_13 var "foobar" # Пропустить слово "foobar" через rot13. echo $var # sbbone echo $var | tr a-z n-za-m # foobar # Расшифровывание. # Пример предоставил Stephane Chazelas. exit 0
Rory Winston представил следующий пример, как образец практического использования команды eval.
Пример: Замена имени переменной на её значение, в исходном тексте программы на языке Perl, с помощью eval.
В программе "test.pl", на языке Perl: ... my $WEBROOT = <WEBROOT_PATH>; ... Эта попытка подстановки значения переменной вместо ее имени: $export WEBROOT_PATH=/usr/local/webroot $sed 's/<WEBROOT_PATH>/$WEBROOT_PATH/' < test.pl > out даст такой результат: my $WEBROOT = $WEBROOT_PATH; Тем не менее: $export WEBROOT_PATH=/usr/local/webroot $eval sed 's/<WEBROOT_PATH>/$WEBROOT_PATH/' < test.pl > out # ==== Этот вариант дал желаемый результат — имя переменной, в тексте программы, благополучно было заменено на ее значение: my $WEBROOT = /usr/local/webroot
Команда eval может быть небезопасна. Если существует приемлемая альтернатива, то желательно воздерживаться от использования eval. Так, eval $COMMANDS исполняет код, который записан в переменную COMMANDS, которая, в свою очередь, может содержать весьма неприятные сюрпризы, например rm -rf *. Использование команды eval, для исполнения кода неизвестного происхождения, крайне опасно!
set
Команда set изменяет значения внутренних переменных сценария. Она может использоваться для переключения опций (ключей, флагов), определяющих поведение скрипта. Ещё одно применение — сброс/установка позиционных параметров (аргументов), значения которых будут восприняты как результат работы команды (set `command`).
Пример: Установка значений аргументов с помощью команды set.
#!/bin/bash # script "set-test" # Вызовите сценарий с тремя аргументами командной строки, # например: "./set-test one two three". echo echo "Аргументы перед вызовом set \`uname -a\` :" echo "Аргумент #1 = $1" echo "Аргумент #2 = $2" echo "Аргумент #3 = $3" set `uname -a` # Изменение аргументов # значения которых берутся из результата работы `uname -a` echo $_ echo "Аргументы после вызова set \`uname -a\` :" # $1, $2, $3 и т.д. будут переустановлены в соответствии с выводом # команды `uname -a` echo "Поле #1 'uname -a' = $1" echo "Поле #2 'uname -a' = $2" echo "Поле #3 'uname -a' = $3" echo --- echo $_ # --- echo exit 0
Вызов set без параметров просто выводит список инициализированных переменных окружения.
bash$ set AUTHORCOPY=/home/bozo/posts BASH=/bin/bash BASH_VERSION=$'2.05.8(1)-release' ... XAUTHORITY=/home/bozo/.Xauthority _=/etc/bashrc variable22=abc variable23=xzy
Если команда set используется с ключом "--", после которого следует переменная, то значение переменной переносится в позиционные параметры (аргументы). Если имя переменной отсутствует, то эта команда приводит к сбросу позиционных параметров.
Пример: Изменение значений позиционных параметров (аргументов).
#!/bin/bash variable="one two three four five" set -- $variable # Значения позиционных параметров берутся из "$variable". first_param=$1 second_param=$2 shift; shift # сдвиг двух первых параметров. remaining_params="$*" echo echo "первый параметр = $first_param" # one echo "второй параметр = $second_param" # two echo "остальные параметры = $remaining_params" # three four five echo; echo # Снова. set -- $variable first_param=$1 second_param=$2 echo "первый параметр = $first_param" # one echo "второй параметр = $second_param" # two # ====================================================== set -- # Позиционные параметры сбрасываются, если не задано имя переменной. first_param=$1 second_param=$2 echo "первый параметр = $first_param" # (пустое значение) echo "второй параметр = $second_param" # (пустое значение) exit 0
unset
Команда unset удаляет переменную, фактически — устанавливает её значение в null. Обратите внимание: эта команда не может сбрасывать позиционные параметры (аргументы)!
bash$ unset PATH bash$ echo $PATH bash$
Пример: "Сброс" переменной.
#!/bin/bash # unset.sh: Сброс переменной. variable=hello # Инициализация. echo "variable = $variable" unset variable # Сброс. # Тот же эффект даёт variable= echo "(unset) variable = $variable" # $variable = null. exit 0
export
Команды управления заданиями
Внешние команды, программы и утилиты
Базовые команды
Более сложные команды
Команды для работы с датой и временем
Команды обработки текста
Команды для работы с файлами и архивами
Команды для работы с сетью
Команды управления терминалом
Команды выполнения математических операций
Прочие команды
Команды системного администрирования
Подстановка команд
Арифметические подстановки
Перенаправление ввода/вывода
С помощью команды exec
Перенаправление для блоков кода
Область применения
Встроенные документы
Часть четвёртая. Материал повышенной сложности
Регулярные выражения
Краткое введение в регулярные выражения
Globbing -- Подстановка имен файлов
Подоболочки или Subshells
Ограниченный режим командной оболочки
Подстановка процессов
Функции
Сложные функции и сложности с функциями
Локальные переменные
Рекурсия без локальных переменных
Псевдонимы
Списки команд
Массивы
Файлы
/dev и /proc
/dev
/proc
/dev/zero и /dev/null
Отладка сценариев
Необязательные параметры(ключи)
Широко распространенные ошибки
Стиль программирования
Неофициальные рекомендации по оформлению сценариев
Разное
Интерактивный и неинтерактивный режим работы
Сценарии-обертки
Операции сравнения: Альтернативные решения
Рекурсия
"Цветные" сценарии
Оптимизация
Разные советы
Проблемы безопасности
Проблемы переносимости
Сценарии командной оболочки под Windows
Bash, версия 2
Замечания и дополнения
От автора
Об авторе
Куда обращаться за помощью
Инструменты, использовавшиеся при создании книги
Аппаратура
Программное обеспечение
Благодарности
Библиография
Дополнительные примеры сценариев
Справочная информация
Маленький учебник по Sed и Awk
Sed
Awk
Коды завершения, имеющие предопределённый смысл
Подробное введение в операции ввода-вывода и перенаправление ввода-вывода
Системные каталоги
Локализация
История команд
Пример файла .bashrc
Преобразование пакетных (*.bat) файлов DOS в сценарии командной оболочки
Упражнения
Анализ сценариев
Создание сценариев
Хронология
== Авторские права ==