Спец курс (Автоматизация процесса проектирования)/Лекция 1 (BASH)
Введение
Название BASH -- это аббревиатура от "Bourne-Again Shell" и игра слов от, ставшего уже классикой, "Bourne Shell" Стефена Бурна (Stephen Bourne). В последние годы BASH достиг такой популярности, что стал стандартной командной оболочкой de facto для многих разновидностей UNIX. Большинство принципов программирования на BASH одинаково хорошо применимы и в других командных оболочках, таких как Korn Shell (ksh), от которой Bash позаимствовал некоторые особенности, [2] и C Shell и его производных. (Примечательно, что C Shell не рекомендуется к использованию из-за отдельных проблем, отмеченных Томом Кристиансеном (Tom Christiansen) в октябре 1993 года на Usenet post
Первый скрипт
- Создаем файл first_script.sh
- Даем файлу права на запуск ( chmod 755 first_script.sh )
- Содержимое файла
#!/bin/bash echo "hello world"
Заголовок скрипта и комментарии
В shell-скриптах последовательность #! должна стоять самой первой и задает интерпретатор (sh или bash). Интерпретатор, в свою очередь, воспринимает эту строку как комментарий, поскольку она начинается с символа #. Если в сценарии имеются еще такие же строки, то они воспринимаются как обычный комментарий.
#!/bin/bash
Оператор echo
Для вывода текстовых сообщений или инофрмации в поток вывода используется оператор echo
#!/bin/bash echo "Имя скрипта — \$0" echo "Первый аргумент: \$1" echo "Второй аргумент: \${2}" echo "Семнадцатый аргумент: \${17}" echo "Количество аргументов: \$#"
Переменные
- Переменные -- это одна из основ любого языка программирования. Они учавствуют в арифметических операциях, в синтаксическом анализе строк и совершенно необходимы для абстрагирования каких либо величин с помощью символических имен. Физически переменные представляют собой ни что иное как участки памяти, в которые записана некоторая информация.
- В отличие от большинства других языков программирования, Bash не производит разделения переменных по "типам". По сути, переменные Bash являются строковыми переменными, но, в зависимости от контекста, Bash допускает целочисленную арифметику с переменными. Определяющим фактором здесь служит содержимое переменных.
Бремя отслеживания типа той или иной переменной полностью лежит на плечах программиста. Bash не будет делать это за вас!
- Для объявления переменной нужно указать ее имя и задать значение.
#!/bin/bash # Присваивание значений переменным a=375
- Чтобы использовать значение переменной используют специальный символ $
#!/bin/bash # Присваивание значений переменным и подстановка значений переменных a=375 hello=$a
- Использование пробельных символов с обеих сторон символа "=" присваивания недопустимо.
- Если записать "VARIABLE =value", то интерпретатор попытается выполнить команду "VARIABLE" с параметром "=value".
- Если записать "VARIABLE= value", то интерпретатор попытается установить переменную окружения "VARIABLE" в "" и выполнить команду "value".
- Примечательно, что написание $variable фактически является упрощенной формой написания ${variable}
#!/bin/bash hello='hello' echo hello # Это не ссылка на переменную, выведет строку "hello". echo $hello echo ${hello} # Идентично предыдущей строке. echo "$hello" echo "${hello}"
- Если в значениях переменных встречаются пробелы, то использование кавычек обязательно.
#!/bin/bash numbers="один два три" other_numbers="1 2 3" echo "numbers = $numbers" echo "other_numbers = $other_numbers" # other_numbers = 1 2 3
- Неинициализированная переменная хранит "пустое" значение - не ноль!. Объявление неинициализированной переменной (то же, что и присваивание пустого значения, см. выше).
#!/bin/bash echo "uninitialized_variable = $uninitialized_variable" uninitialized_variable= echo "uninitialized_variable = $uninitialized_variable" uninitialized_variable=23 echo "uninitialized_variable = $uninitialized_variable"
- Использование неинициализированных переменных может приводить к ошибкам разного рода в процессе исполнения.
- Оператор unset используется для того, чтобы инициализированную переменную сделать не инициализированной
#!/bin/bash uninitialized_variable=23 # Присваивание. unset uninitialized_variable # Сброс. echo "uninitialized_variable = $uninitialized_variable" # Переменная содержит "пустое" значение.
Присваивание значений переменным
- '=' оператор присваивания (пробельные символы до и после оператора -- недопустимы)
#!/bin/bash a=879 echo "Значение переменной \"a\" -- $a."
- Можно использовать присваивание совместно с оператором let
#!/bin/bash let a=16+5 echo "Значение переменной \"a\" теперь стало равным: $a."
- Неявное присваивание
#!/bin/bash echo -n "Значения переменной \"a\" в цикле: " for a in 7 8 9 11 do echo -n "$a " done
- Присваивание с оператором read
#!/bin/bash echo -n "Введите значение переменной \"a\" " read a echo "Значение переменной \"a\" теперь стало равным: $a."
- Присваивание переменным переменных
#!/bin/bash a=23 # Простейший случай echo $a b=$a echo $b
- Маскированное присваивание переменных с использованием обратных ковычек `...`
#!/bin/bash a=`echo Hello!` # В переменную 'a' попадает результат работы команды 'echo' echo $a a=`ls -l` # В переменную 'a' записывается результат работы команды 'ls -l' echo $a # Кавычки отсутствуют, удаляются лишние пробелы и пустые строки. echo echo "$a" # Переменная в кавычках, все пробелы и пустые строки сохраняются.
- Маскированное присваивание переменных с использованием $(...) (более современный метод, по сравнению с обратными кавычками)
#!/bin/bash a=$(echo Hello\!) # В переменную 'a' попадает результат работы команды 'echo' echo $a
Переменные Bash не имеют типов
- Тип переменной определяется из контекса
#!/bin/bash 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 = 2335
- Пустая переменная становиться целочисленной
#!/bin/bash e="" echo "e = $e" # e = let "e += 1" # Арифметические операции допускают использование "пустых" переменных? echo "e = $e" # e = 1
- Необъявленная переменная трансформируется в целочисленную.
#!/bin/bash echo "f = $f" # f = let "f += 1" # Арифметические операции допустимы? echo "f = $f" # f = 1
Использование ковычек в значения переменных
- Кавычки, ограничивающие строки с обеих сторон, служат для предотвращения интерпретации специальных символов, которые могут находиться в строке.
(Символ называется "специальным", если он несет дополнительную смысловую нагрузку, например символ шаблона -- *.)
- Желательно использовать двойные кавычки (" ") при обращении к переменным. Это предотвратит интерпретацию специальных символов, которые могут содержаться в именах переменных, за исключением $, ` (обратная кавычка) и \ (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 пробелами).
Проверка условий
- В Bash, для проверки условий, имеется команда test, различного вида скобочные операторы и условный оператор if/then.
- Оператор if/then проверяет -- является ли код завершения списка команд 0 (поскольку 0 означает "успех"), и если это так, то выполняет одну, или более, команд, следующие за словом then.
- Существует специальная команда -- [ (левая квадратная скобка). Она является синонимом команды test, и является встроенной командой (т.е. более эффективной, в смысле производительности). Эта команда воспринимает свои аргументы как выражение сравнения или как файловую проверку и возвращает код завершения в соответствии с результатами проверки (0 -- истина, 1 -- ложь).
#!/bin/bash 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 # Пустая переменная -- это ложь.
- Начиная с версии 2.02, Bash предоставляет в распоряжение программиста конструкцию ... расширенный вариант команды test, которая выполняет сравнение способом более знакомым программистам, пишущим на других языках программирования.
- Обратите внимание: [[ -- это зарезервированное слово, а не команда. Bash исполняет $a -lt $b как один элемент, который имеет код возврата.
- Круглые скобки (( ... )) и предложение let ... так же возвращают код 0, если результатом арифметического выражения является ненулевое значение. Таким образом, арифметические выражения могут участвовать в операциях сравнения.
- Условный оператор if проверяет код завершения любой команды, а не только результат выражения, заключенного в квадратные скобки.
#!/bin/bash if cmp a b &> /dev/null # Подавление вывода. then echo "Файлы a и b идентичны." else echo "Файлы a и b имеют различия." fi if grep -q Bash file then echo "Файл содержит, как минимум, одно слово Bash." fi if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED then echo "Команда выполнена успешно." else echo "Обнаружена ошибка при выполнении команды." fi
- Оператор if/then допускает наличие вложенных проверок.
#!/bin/bash if echo "Следующий *if* находится внутри первого *if*." if [[ $comparison = "integer" ]] then (( a < b )) else [[ $a < $b ]] fi then echo '$a меньше $b' fi
- Когда if и then располагаются в одной строке, то конструкция if должна завершаться точкой с запятой. И if, и then -- это зарезервированные слова. Зарезервированные слова начинают инструкцию, которая должна быть завершена прежде, чем в той же строке появится новая инструкция.
if [ -x "$filename" ]; then
- elif -- это краткая форма записи конструкции else if. Применяется для построения многоярусных инструкций if/then.
#!/bin/bash if [ condition1 ] then command1 command2 command3 elif [ condition2 ] # То же самое, что и else if then command4 command5 else default-command fi
- Команда 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 if test -z "$1" then echo "Аргументы командной строки отсутствуют." else echo "Первый аргумент командной строки: $1." fi
- Конструкция [[ ]] более универсальна, по сравнению с [ ]. Этот расширенный вариант команды test перекочевал в Bash из ksh88. Внутри этой конструкции не производится никакой дополнительной интерпретации имен файлов и не производится разбиение аргументов на отдельные слова, но допускается подстановка параметров и команд. Конструкция ... более предпочтительна, нежели [ ... ], поскольку поможет избежать некоторых логических ошибок. Например, операторы &&, ||, < и > внутри [[ ]] вполне допустимы, в то время как внутри [ ] порождают сообщения об ошибках.
#!/bin/bash file=/etc/passwd if [[ -e $file ]] then echo "Файл паролей найден." fi
- Строго говоря, после оператора if, ни команда test, ни квадратные скобки ( [ ] или [[ ]] ) не являются обязательными. Инструкция "if COMMAND" возвращает код возврата команды COMMAND. Точно так же, условие, находящееся внутри квадратных скобок может быть проверено без использования оператора if.
dir=/home/bozo if cd "$dir" 2>/dev/null; then # "2>/dev/null" подавление вывода сообщений об ошибках. echo "Переход в каталог $dir выполнен." else echo "Невозможно перейти в каталог $dir." fi 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 )) 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
Операции проверки файлов
- -a file
истинно если файл существует.
- -d file
истинно если файл существует и является директорией.
- -f file
истинно если файл существует и является обычным файлом.
- -r file
истинно если файл существует и доступен для чтения.
- -s file
истинно если файл существует и его размер больше 0.
- -w file
истинно если файл существует и доступен для записи.
- -x file
истинно если файл существует и является исполняемым.
- file1 -nt file2
истинно если файл file1 новее чем file2 или file1 (в соответствии со временем последнего изменения) существует, а file2 нет.
- file1 -ot file2
истинно если файл file1 старше чем file2 или file2 существует, а file1 нет.
- file1 -ef file2
#!/bin/bash if [ -d $directory ] then linkchk $directory else echo "$directory не является каталогом" echo "Порядок использования: $0 dir1 dir2 ..." fi
Сравнение целых чисел
- -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"))
Операции сравнения строк
- -z string
истинно если строка имеет нулевую длину.
- -n string
истинно если длина строки не нулевая.
- string1 = string2
истинно если строки равны.
- string1 != string2
истинно если не равны.
- string1 < string2
истинно если строка 1 стоит в алфавитном порядке перед строкой 2.
- string1 > string2
истинно если строка 1 стоит в алфавитном порядке после строки 2.
- = равно
if [ "$a" = "$b" ]
- == равно (Синоним оператора =).
if [ "$a" == "$b" ] $a == z* # истина, если $a начинается с символа "z" (сравнение по шаблону) $a == "z*" # истина, если $a равна z* [ $a == z* ] # имеют место подстановка имен файлов и разбиение на слова [ "$a" == "z*" ] # истина, если $a равна z*
- != не равно
if [ "$a" != "$b" ] Этот оператор используется при поиске по шаблону внутри ... .
- < меньше, в смысле величины ASCII-кодов
if [[ "$a" < "$b" ]] if [ "$a" \< "$b" ] Обратите внимание! Символ "<" необходимо экранировать внутри [ ].
- > больше, в смысле величины ASCII-кодов
if [[ "$a" > "$b" ]] if [ "$a" \> "$b" ] Обратите внимание! Символ ">" необходимо экранировать внутри [ ].
- Оператор -n требует, чтобы строка была заключена в кавычки внутри квадратных скобок. Как правило, проверка строк, не заключенных в кавычки, оператором ! -z, или просто указание строки без кавычек внутри квадратных скобок, проходит нормально, однако это небезопасная, с точки зрения отказоустойчивости. Всегда заключайте проверяемую строку в кавычки.
Операторы
- + сложение
- - вычитание
- '*' умножение
- / деление
- '**' В Bash, начиная с версии 2.02, был введен оператор возведения в степень -- "**".
let "z=5**3" echo "z = $z" # z = 125
- % модуль (деление по модулю), возвращает остаток от деления
- += "плюс-равно" (увеличивает значение переменной на заданное число)
let "var += 5" значение переменной var будет увеличено на 5.
- -= "минус-равно" (уменьшение значения переменной на заданное число)
- *= "умножить-равно" (умножить значение переменной на заданное число, результат записать в переменную)
let "var *= 4" значение переменной var будет увеличено в 4 раза.
- /= "слэш-равно" (уменьшение значения переменной в заданное число раз)
- %= "процент-равно" (найти остаток от деления значения переменной на заданное число, результат записать в переменную)
Арифметические операторы очень часто используются совместно с командами expr и let.
Битовые операции
- << сдвигает на 1 бит влево (умножение на 2)
- <<= "сдвиг-влево-равно"
let "var <<= 2" значение переменной var сдвигается влево на 2 бита (умножается на 4)
- >> сдвиг вправо на 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. # Обратите внимание: оператор || не должен использоваться внутри [ ... ].
Прочие операции
- , запятая
С помощью оператора запятая можно связать несколько арифметических в одну последовательность. При разборе таких последовательностей, командный интерпретатор вычисляет все выражения (которые могут иметь побочные эффекты) в последовательности и возвращает результат последнего.
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
Циклы
Код завершения и возвращаемое значение
- Команда exit может использоваться для завершения работы сценария, точно так же как и в программах на языке C. Кроме того, она может возвращать некоторое значение, которое может быть проанализировано вызывающим процессом.
- В соответствии с соглашениями, 'exit 0' указывает на успешное завершение, в то время как ненулевое значение означает ошибку.
#!/bin/bash echo hello echo $? # код возврата = 0, поскольку команда выполнилась успешно. lskdf # Несуществующая команда. echo $? # Ненулевой код возврата, поскольку команду выполнить не удалось. echo exit 113 # Явное указание кода возврата 113. # Проверить можно, если набрать в командной строке "echo $?" # после выполнения этого примера.