Спец курс (Автоматизация процесса проектирования)/Лекция 2 (BASH)
Создание массивов
- Массив - это переменная, в которой хранится несколько значений.
- Любая переменная может использоваться как массив.
- На максимальный размер массива ограничений нет и нет никаких других требований к элементам массива, за исключением лишь того, что элементы массива имеют индексы и значения индексов идут подряд.
- Массивы начинаются с нулевого элемента: индекс первого элемента равен 0.
ARRAY[INDEXNR]=value #INDEXNR рассматривается как арифметическое выражение, результат вычисления которого должен быть положительным числом.
- Явное объявление массива выполняется с помощью встроенной команды declare:
declare -a ARRAYNAME
- Также допускается объявление с указанием индекса, но индекс будет игнорироваться.
- С помощью встроенных команд declare и readonly для массива можно определять атрибуты.
- Атрибуты действуют для всех переменных в массиве, у вас не может быть смешанных массивов.
# Массив переменных можно также объявить с помощью инструкции присваивания следующего формата: ARRAY=(value1 value2 ... valueN)
- Каждое значение value в этом формате имеет вид [номериндекса =]строка.
- Индекс не является обязательным.
- Если он указан, то он используется в инструкции присваивания;
- В противном случае в качестве индекса используется значение индекса, которое уже было назначено, плюс один.
- Этот формат можно также использовать в команде declare.
- Если индексы не указываются, индексация начинается с нуля.
- Добавление отсутствующих или дополнительных элементов массива осуществляется следующим образом:
ARRAYNAME[indexnumber]=value
- Помните, что во встроенной команде read есть параметр -a, который позволяет читать и назначать значения элементам массива.
Получение доступа к содержимому массива
- Чтобы получить доступ к содержимому элемента в массиве, используйте фигурные скобки.
Как показано в следующем примере, они необходимы для того, чтобы отключить интерпретацию, выполняемую командной оболочкой, с помощью которой происходит подстановка операторов.
- Если в качестве номера индекса указывается "@" или "*", то выдаются все элементы массива.
#!/bin/bash ARRAY=(one two three) echo ${ARRAY[*]} # one two three echo $ARRAY[*] one[*] echo ${ARRAY[2]} #three ARRAY[3]=four echo ${ARRAY[*]} #one two three four
- Если переменная массива указывается без указания номера индекса, то в результате будет получено содержимое первого элемента массива, т. е. элемента с нулевым индексом.
Удаление переменных массивов
- Для уничтожения массивов или элементов массива используется встроенная команда unset:
#!/bin/bash unset ARRAY[1] echo ${ARRAY[*]} #one three four unset ARRAY echo ${ARRAY[*]} #<--no output-->
Примеры массивов
#!/bin/bash # сможет ли пользователь, имеющий надлежащие права доступа, запускать скрипт с правильными аргументами. if [ $(whoami) != 'root' ]; then echo "Must be root to run $0" exit 1; fi if [ -z $1 ]; then echo "Usage: $0 <path/to/httpd.conf>" exit 1 fi httpd_conf_new=$1 httpd_conf_path="/usr/local/apache/conf" login=htuser #Имена хостов, которые необходимо сконфигурировать, перечислены в массиве farm_hosts farm_hosts=(web03 web04 web05 web06 web07) for i in ${farm_hosts[@]}; do su $login -c "scp $httpd_conf_new ${i}:${httpd_conf_path}" su $login -c "ssh $i sudo /usr/local/apache/bin/apachectl graceful" done exit 0
- Следующий пример представил Дэн Рихтер (Dan Richter). Он столкнулся со следующей проблемой:
"... На сайте моей компании есть демонстрационные примеры, и каждую неделю кто-нибудь должен все их проверять. Поэтому у меня есть задание cron, которое заполняет массив возможными кандидатами; в задании cron для определения недели в году используется команда date +%W и для того, чтобы получить правильный индекс, выполняется операция по модулю. Счастливчик получает уведомление по электронной почте". И ниже приводится способ ее решения:
#!/bin/bash # This is get-tester-address.sh # # First, we test whether bash supports arrays. # (Support for arrays was only added recently.) # whotest[0]='test' || (echo 'Failure: arrays not supported in this version of bash.' && exit 2) # # Our list of candidates. (Feel free to add or # remove candidates.) # wholist=( 'Bob Smith <bob@example.com>' 'Jane L. Williams <jane@example.com>' 'Eric S. Raymond <esr@example.com>' 'Larry Wall <wall@example.com>' 'Linus Torvalds <linus@example.com>' ) # # Count the number of possible testers. # (Loop until we find an empty string.) # count=0 while [ "x${wholist[count]}" != "x" ] do count=$(( $count + 1 )) done # # Now we calculate whose turn it is. # week=`date '+%W'` # The week of the year (0..53). week=${week#0} # Remove possible leading zero. let "index = $week % $count" # week modulo count = the lucky person email=${wholist[index]} # Get the lucky person's e-mail address. echo $email # Output the person's e-mail address. Затем этот скрипт будет использован в других скриптах, таких как следующий, в котором используется встраиваемый документ (here document): email=`get-tester-address.sh` # Find who to e-mail. hostname=`hostname` # This machine's name. # # Send e-mail to the right person. # mail $email -s '[Demo Testing]' <EOF The lucky tester this week is: $email Reminder: the list of demos is here: http://web.example.com:8080/DemoSites (This e-mail was generated by $0 on ${hostname}.) EOF
Подмножества и части строк
- Подмножества и части строк
- Обычно расширение имеет такую форму: ${PARAMETER:OFFSET:LENGTH}, где аргумент LENGTH необязателен. Итак, если вы хотите выбрать только определенное подмножество аргументов скрипта, вы можете использовать полную версию, чтобы показать, сколько аргументов следует выбрать. Например, ${@:4:3} обращается к трем аргументам, начиная с аргумента 4, а именно, к аргументам 4, 5 и 6. Вы можете использовать это расширение для выбора конкретных параметров помимо тех, которые доступны сразу, используя от $1 до $9 включительно. ${@:15:1} — способ вызова сразу 15 параметра.
- Вы можете использовать расширение с конкретными параметрами, а также весь набор параметров, представленный при помощи $* или $@. В этом случае параметр обрабатывается как строка и число, представляющее собой сдвиг или длину. Например, если переменная x имеет значение «some value», то
${x:3:5}
будет иметь значение «e val».
Подстроки значений параметров в shell
#!/bin/bash x=»some value» echo «${x:3:5}» e val
Размеры переменных
- Вы уже видели, что $# указывает число параметров и что расширение ${PARAMETER:OFFSET:LENGTH} применяется и к конкретным параметрам, и к $* и $@, поэтому вас не должно удивить, что аналогичная конструкция, ${#PARAMETER}, может использоваться для определения размера конкретного параметра. Простая функция testlength, показанная в Листинге 10, иллюстрирует это. Попробуйте сделать это сами.
#!/bin/bash testlength () { for p in «$@»; do echo ${#p};done } testlength 1 abc «def ghi» #1 #3 #7
Работа с шаблонами
Расширение параметра также включает возможности поиска по шаблону при помощи тех же знаков подстановки, которые используются в расширениях имен файлов или подстановках. Обратите внимание, что это не соответствует регулярным выражениям, используемым grep.
Расширение шаблонов в shell Расширение Назначение
- ${ПАРАМЕТР#СЛОВО} Shell расширяет СЛОВО как расширение имени файла и удаляет самое короткое соответствие шаблону, если оно имеется, с начала расширенного значения ПАРАМЕТРА. Использование ‘@’ или ‘$’ приводит к удалению по образцу для каждого параметра в списке.
- ${ПАРАМЕТР##СЛОВО} Приводит к удалению самого длинного соответствия шаблону с начала вместо самого короткого.
- ${ПАРАМЕТР%СЛОВО} Shell расширяет СЛОВО как расширение имени файла и удаляет самое короткое соответствие шаблону, если оно имеется, с конца расширенного значения ПАРАМЕТРА. Использование ‘@’ или ‘$’ приводит к удалению по образцу для каждого параметра в списке.
- ${ПАРАМЕТР%%СЛОВО} Приводит к удалению самого длинного соответствия шаблону с конца вместо самого короткого.
- ${ПАРАМЕТР/ОБРАЗЕЦ/ПОСЛЕДОВАТЕЛЬНОСТЬ} Shell расширяет ОБРАЗЕЦ как расширение имени файла и заменяет самое длинное соответствие шаблону, если оно имеется, расширенным значением ПАРАМЕТРА. Для соответствия образцам в начале расширенного значения ПАРАМЕТРА поставьте в начале ОБРАЗЦА # или %, если соответствие должно проверяться до конца. Если ПОСЛЕДОВАТЕЛЬНОСТЬ пуста, перемещение / может быть опущено и соответствия удаляются. Использование ‘@’ или ‘$’ приводит к замене образца для каждого параметра в списке.
- ${ПАРАМЕТР//ОБРАЗЕЦ/ПОСЛЕДОВАТЕЛЬНОСТЬ} Выполняет замену для всех подходящих, а не только для первого.
#!/bin/bash x=»a1 b1 c2 d2″ echo ${x#*1} #b1 c2 d2 echo ${x##*1} #c2 d2 echo ${x%1*} #a1 b echo ${x%%1*} #a echo ${x/1/3} #a3 b1 c2 d2 echo ${x//1/3} #a3 b3 c2 d2 echo ${x//?1/z3} #z3 z3 c2 d2
Перенаправление потока вывода в файл
Дескриптор файла -- это просто число, по которому система идентифицирует открытые файлы. Рассматривайте его как упрощенную версию указателя на файл.
- COMMAND_OUTPUT > # Перенаправление stdout (вывода) в файл. Если файл отсутствовал, то он создается, иначе -- перезаписывается.
ls -lR > dir-tree.list # Создает файл, содержащий список дерева каталогов.
- : > filename # Операция > усекает файл "filename" до нулевой длины. Если до выполнения операции файла не существовало, то создается новый файл с нулевой длиной (тот же эффект дает команда 'touch'). Символ : выступает здесь в роли местозаполнителя, не выводя ничего.
- COMMAND_OUTPUT >> Перенаправление stdout (вывода) в файл. Создает новый файл, если он отсутствовал, иначе -- дописывает в конец файла.
- 1>filename # Перенаправление вывода (stdout) в файл "filename".
- 1>>filename # Перенаправление вывода (stdout) в файл "filename", файл открывается в режиме добавления.
- 2>filename # Перенаправление stderr в файл "filename".
- 2>>filename # Перенаправление stderr в файл "filename", файл открывается в режиме добавления.
- &>filename # Перенаправление stdout и stderr в файл "filename".
Поток ввода
- < FILENAME # Ввод из файла. Парная команде ">", часто встречается в комбинации с ней.
Перенаправление потоков
- 2>&1 # Перенаправляется stderr на stdout. Сообщения об ошибках передаются туда же, куда и стандартный вывод.
- i>&j # Перенаправляется файл с дескриптором i в j. Вывод в файл с дескриптором i передается в файл с дескриптором j.
- >&j # Перенаправляется файл с дескриптором 1 (stdout) в файл с дескриптором j. Вывод на stdout передается в файл с дескриптором j.
- [j]<>filename # Файл "filename" открывается на чтение и запись, и связывается с дескриптором "j". Если "filename" отсутствует, то он создается. Если дескриптор "j" не указан, то, по-умолчанию, берется дескриптор 0, stdin. Как одно из применений этого -- запись в конкретную позицию в файле.
echo 1234567890 > File # Записать строку в файл "File". exec 3<> File # Открыть "File" и связать с дескриптором 3. read -n 4 <&3 # Прочитать 4 символа. echo -n . >&3 # Записать символ точки. exec 3>&- # Закрыть дескриптор 3. cat File # ==> 1234.67890 # Произвольный доступ, да и только!
Закрытие дескрипторов файлов
- n<&- Закрыть дескриптор входного файла n.
- 0<&-, <&- Закрыть stdin.
- n>&- Закрыть дескриптор выходного файла n.
- 1>&-, >&- Закрыть stdout.
Дочерние процессы наследуют дескрипторы открытых файлов. По этой причине и работают конвейеры. Чтобы предотвратить наследование дескрипторов -- закройте их перед запуском дочернего процесса.
# В конвейер передается только stderr. exec 3>&1 # Сохранить текущее "состояние" stdout. ls -l 2>&1 >&3 3>&- | grep bad 3>&- # Закрыть дескр. 3 для 'grep' (но не для 'ls'). # ^^^^ ^^^^ exec 3>&- # Теперь закрыть его для оставшейся части сценария.
Конвееры
- | # Конвейер (канал). Универсальное средство для объединения команд в одну цепочку. Похоже на ">", но на самом деле -- более обширная. Используется для объединения команд, сценариев, файлов и программ в одну цепочку (конвейер).
cat *.txt | sort | uniq > result-file
- Допускается перенаправление нескольких потоков в один файл.
ls -yz >> command.log 2>&1 # Сообщение о неверной опции "yz" в команде "ls" будет записано в файл "command.log". # Поскольку stderr перенаправлен в файл.
Операции перенаправления и/или конвейеры могут комбинироваться в одной командной строке.
command < input-file > output-file command1 | command2 | command3 > output-file
Генерация псевдослучайных чисел
- $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 "-----------------"
Завершение и код завершения
- ...эта часть Bourne shell покрыта мраком, тем не менее все пользуются ею. Chet Ramey
- Команда exit может использоваться для завершения работы сценария, точно так же как и в программах на языке C. Кроме того, она может возвращать некоторое значение, которое может быть проанализировано вызывающим процессом.
- Каждая команда возвращает код завершения (иногда код завершения называют возвращаемым значением ). В случае успеха команда должна возвращать 0, а в случае ошибки -- ненулевое значение, которое, как правило, интерпретируется как код ошибки. Практически все команды и утилиты UNIX возвращают 0 в случае успешного завершения, но имеются и исключения из правил.
- Аналогичным образом ведут себя функции, расположенные внутри сценария, и сам сценарий, возвращая код завершения. Код, возвращаемый функцией или сценарием, определяется кодом возврата последней команды. Команде exit можно явно указать код возврата, в виде: exit nnn, где nnn -- это код возврата (число в диапазоне 0 - 255).
-
Note : Когда работа сценария завершается командой exit без параметров, то код возврата сценария определяется кодом возврата последней исполненной командой.
Код возврата последней команды хранится в специальной переменной $?. После исполнения кода функции, переменная $? хранит код завершения последней команды, исполненной в функции. Таким способом в Bash передается "значение, возвращаемое" функцией. После завершения работы сценария, код возврата можно получить, обратившись из командной строки к переменной $?, т.е. это будет код возврата последней команды, исполненной в сценарии.
#!/bin/bash echo hello echo $? # код возврата = 0, поскольку команда выполнилась успешно. lskdf # Несуществующая команда. echo $? # Ненулевой код возврата, поскольку команду выполнить не удалось. echo exit 113 # Явное указание кода возврата 113. # Проверить можно, если набрать в командной строке "echo $?" # после выполнения этого примера. # В соответствии с соглашениями, 'exit 0' указывает на успешное завершение, #+ в то время как ненулевое значение означает ошибку.
Note Символ !, может выступать как логическое "НЕ" для инверсии кода возврата.
Пример 6-2. Использование символа ! для логической инверсии кода возврата
true # встроенная команда "true". echo "код возврата команды \"true\" = $?" # 0 ! true echo "код возврата команды \"! true\" = $?" # 1 # Обратите внимание: символ "!" от команды необходимо отделять пробелом. # !true вызовет сообщение об ошибке "command not found"
Отладка сразу всего скрипта
Когда дела идут не так, как планировалось, необходимо определить, из-за чего в скрипте возникли проблемы. В Bash для отладки предоставляются широкие возможности. Наиболее распространенным способом является запуск подоболочки с параметром -x, благодаря которому весь скрипт будет запущен в отладочном режиме. После того, как для каждой команды будут выполнены все необходимые подстановки и замены, но перед тем, как команда будет выполнена, в стандартный выходной поток будет выдана трассировка команды и все ее аргументы.
- К примеру у нас есть скрипт
#!/bin/bash # Данный скрипт очищает экран терминала, выдает в терминал приглашение и показывает информацию о пользователях, # подключенных в текущий момент. Устанавливаются значения для двух переменных, которые выдаются в терминал. clear # очищает окно терминала echo "The script starts now." echo "Hi, $USER!" # символ доллара используется для получения значения переменной echo echo "I will now fetch you a list of connected users:" echo w # показывается, кто зарегистрирован в системе echo # и что каждый из них делает echo "I'm setting two variables now." COLOUR="black" # устанавливает значение для переменной в локальной оболочке VALUE="9" # устанавливает значение для переменной в локальной оболочке echo "This is a string: $COLOUR" # показывается содержимое переменной echo "And this is a number: $VALUE" # показывается содержимое переменной echo echo "I'm giving you back your prompt now." echo
- Запустим его с аргументами отладки
bash -x script1.sh
+ clear + echo 'The script starts now.' The script starts now. + echo 'Hi, willy!' Hi, willy! + echo + echo 'I will now fetch you a list of connected users:' I will now fetch you a list of connected users: + echo + w 4:50pm up 18 days, 6:49, 4 users, load average: 0.58, 0.62, 0.40 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT root tty2 - Sat 2pm 5:36m 0.24s 0.05s -bash willy :0 - Sat 2pm ? 0.00s ? - willy pts/3 - Sat 2pm 43:13 36.82s 36.82s BitchX willy ir willy pts/2 - Sat 2pm 43:13 0.13s 0.06s /usr/bin/screen + echo + echo 'I'\''m setting two variables now.' I'm setting two variables now. + COLOUR=black + VALUE=9 + echo 'This is a string: ' This is a string: + echo 'And this is a number: ' And this is a number: + echo + echo 'I'\''m giving you back your prompt now.' I'm giving you back your prompt now. + echo
Отладка скрипта по частям
С помощью встроенной команды set, имеющейся в Bash, вы можете запускать в обычном режиме те части скрипта, относительно которых вы уверены, что они работают без ошибок, и отображать отладочную информацию только там, где есть подозрение на неправильную работу. Скажем, мы не уверены, что будет делать команда w, поэтому мы можем окружить эту команду следующими отладочными командами:
#!/bin/bash -xv set -x # activate debugging from here w set +x # stop debugging from here
Выдаваемая информация будет выглядеть следующим образом:
The script starts now. Hi, willy! I will now fetch you a list of connected users: + w 5:00pm up 18 days, 7:00, 4 users, load average: 0.79, 0.39, 0.33 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT root tty2 - Sat 2pm 5:47m 0.24s 0.05s -bash willy :0 - Sat 2pm ? 0.00s ? - willy pts/3 - Sat 2pm 54:02 36.88s 36.88s BitchX willyke willy pts/2 - Sat 2pm 54:02 0.13s 0.06s /usr/bin/screen + set +x I'm setting two variables now. This is a string: And this is a number: I'm giving you back your prompt now.
- В одном и том же скрипте вы можете включать и выключать отладочный режим столько раз, сколько это необходимо.
set -f (set -o noglob) Отключается генерация имени файла с помощью метасимволов (подстановка). set -v (set -o verbose) Командная оболочка печатает входные строки сразу, как они считываются. set -x (set -o xtrace) Перед исполнением команды выдаются трассировочные данные.
-
Символ "тире" используется для активации параметра командной оболочки, а символ "плюс" - для его деактивации. Не перепутайте это!
- Кроме того, эти режимы можно указать в самом скрипте, для этого добавьте соответствующие параметры в первую строку, в которой указывается командная оболочка. Параметры можно объединить, что является обычным приемом при использовании команд UNIX:
#!/bin/bash -xv
- Как только вы обнаружили ошибочный фрагмент в вашем скрипте, вы можете добавить инструкции echo перед каждой командой, в работе которой вы не уверены, и можете увидеть, где именно и почему что-то не работает.
echo "debug message: now attempting to start w command"; w
- В более сложных скриптах инструкцию echo можно добавлять для отображения значений переменных на различных этапах работы скрипта, что может помочь в обнаружении проблемы:
echo "Variable VARNAME is now set to $VARNAME."