Усім привіт! Дана стаття охоплює тему змінних і параметрів командного інтерпретатора Bash (Bourne-Again SHell), їхнє створення програмістом і деякі стандартні змінні оболонки. Так як це перша стаття про Bash-скриптинг, пропоную обговорити куди необхідно писати shell-код. Скрипт командного інтерпретатора — це звичайнісінький текстовий файл з будь-яким розширенням, або взагалі без нього. Але в основному, для скриптів командного рядка, в кінці файлу часто вказують розширення “.sh”. Слід також пам'ятати, щоб файл скрипта запустився на виконання, необхідно щоб у нього були встановлені права на виконання для поточного користувача, чи його групи. Це досягається за допомогою Юнікс-команди
chmod a+x script_name.sh
“script_name.sh”, відповідно, необхідно замінити на відповідне ім'я файлу, в якому знаходиться скрипт. Дана команда дозволяє запускати на виконання скрипт “script_name.sh” усім користувачам системи. Обов'язковою вимогою являється те, що у системі, в якій виникає необхідність запускати Bash-скрипти, був встановлений командний інтерпретатор Bash. Також не забувайте коментувати код, що полегшить його читання Вам чи вашим колегам. Коментарі в Bash-скриптах починаються з символу “#” і тривають до кінця рядка:
# це коментар у скрипті Bash
Не слід робити коментарі одразу ж після виклику команд операційної системи — командний інтерпретатор може передати коментар програмі, виклик якої вимагає скрипт.

Змінні

Параметр являється сутністю, який зберігає дані. Він може являти собою ім'я, число, або один з спеціальних символів які вказані у розділі “параметри скрипта”. Змінна являється параметром, яка визначається ім'ям. Змінна має значення і нуль або більше атрибутів. Атрибути присвоюються змінній використовуючи вбудовану команду declare. Параметр встановлено (оголошено), коли йому присвоюється значення. Пустий рядок являється допустимим значенням. Як тільки змінна встановлена (оголошена), вона може бути знищеною тільки за допомогою команди unset. Змінній може бути присвоєне значення виразу за допомогою наступного синтаксису:
# Зверніть увагу на відсутність пробілів!
# зверніть увагу, що апостроф в назвах змінних використовувати не можна!
ім'я=[значення]
Якщо значення не вказано, змінна буде містити рядок нульової довжини. Усі значення підпадають під вирази з знаком тильди (~), вирази з параметрами і змінними, виконання команд, арифметичні вирази і видалення лапок. Якщо змінна має встановлений атрибут integer, значення розглядається у контексті арифметичного виразу, навіть, якщо не використовувалась конструкція $((...)). Розділення слів не виконується, за винятком змінної $@. Не виконується підставлення імен файлів. Вирази присвоєння можуть також використовуватись в якості аргументів до вбудованих команд командного інтерпретатора: alias, declare, typeset, export, readonly i local. Під час режиму POSIX командного інтерпретатора, дані вбудовані команди можуть з'являтися і зберегти свої властивості після одного або більшої кількості примірників вбудованої команди command. У контексті коли вирази присвоєння встановлюють значення змінної командного інтерпретатора або індексу масива, оператор “+=” може бути використаними для додавання або доповнення до існуючого значення змінної. Коли оператор “+=” застосовується до змінної у якої встановлений атрибут integer, значення розглядається в контексті арифметичного виразу і виконується додавання значення виразу до поточного значення змінної. У разі, якщо оператор “+=” використовується відносно змінної-масиву, значення даної змінної не знищується (так як це виконується при використанні оператора “=”) і нові значення додаються до масиву у позиції більшій на одиницю, ніж максимальний поточний індекс масиву (для індексованих масивів), або доповнюється в якості додаткової пари ключ-значення у випадку асоціативного масиву. Під час додавання до рядкової змінної, розраховується значення виразу і додається до поточного її значення. Змінній можна присвоїти атрибут nameref, передаючи прапорець “-n” до вбудованих команд declare або local для створення посилання на іншу змінну. Це дозволяє маніпулювати безпосередньо значенням змінної, а не її копією. Змінні, які містять посилання, часто використовуються разом з функціями командного інтерпретатора, для маніпулювання значеннями, ім'я яких передається в якості аргументу до функції. Наприклад, якщо ім'я змінної передається до функції командного інтерпретатора в якості першого аргументу використовують синтаксис:
declare -n ref=$1
всередині функції створюється посилання на змінну, яка має ім'я ref, значення якої являється ім'я змінної, яка передається в якості першого аргументу. Звернення і присвоєння відносно змінної ref трактуються як звернення і присвоєння до змінної ім'я якій — $1. Атрибут nameref не може використовуватись для масивів. Однак, змінні nameref можуть посилатись на елементи масиву. Посилання можуть бути знищеними використовуючи прапорець -n для вбудованої команди unset. В іншому випадку, якщо unset використовується відносно посилання без даного прапорця — знищеною буде змінна на яку вказувало посилання.

Масиви

Bash забезпечує використання одномірних індексованих і асоціативних масивів. Будь-яка змінна може бути використаною в якості індексованого масиву. Вбудована команда declare може безпосередньо оголошувати масиви. Не існує максимального обмеження на розмір масиву, або на те, що індекси повинні бути неперервними. Індекси являють собою цілі числа, і починаються з нуля. Асоціативні масиви використовують для індексування звичайні рядки символів. Якщо не вказано іншого, індексовані масиви не повинні мати від'ємних індексів. Індексований масив створюється автоматично, якщо будь-якій змінній присвоєно значення у наступній формі:
ім'я[індекс]=значення
Для явного оголошення масиву необхідно скористатися синтаксисом:
declare -a ім'я
Також допустимий наступний синтаксис:
declare -a ім'я[індекс]
в якому “індекс” ігнорується. Асоціативний масив можна створити використовуючи наступний синтаксис:
declare -A ім'я
Атрибути масиву можуть бути встановленими використовуючи вбудовані команди declare i readonly. Кожен атрибут застосовується до усіх членів масиву. У Bash масив також можна створита за допомогою наступного синтаксису:
ім'я=([індекс1]=значення1 ... [індексN]=значенняN)
При даному синтаксисі, індексовані масиви не потребують явне вказування індексів, тому можна вказувати тільки рядки символів. Якщо ж індекс вказано — значення буде розміщуватись під вказаним індексом. Коли створюється асоціативний масив — індекси повинні обов'язково вказуватись. Даний синтаксис також валідний для вбудованої команди declare. Під час використання негативних індексів звичайного масиву, даний негативний індекс інтерпретується як індекс відносно найбільшого індексу масиву. Тобто елемент за індексом “-1” буде інтерпретуватися в якості останнього елементу масива. До будь-якого елементу масиву можна звернутися, використовуючи конструкцію ${ім'я[індекс]}. Фігурні дужки призначені для запобігання хибної інтерпретації в якості роз'яснень імен файлів. Якщо звертатися до масиву без будь-якого індекса — вважається що звернення відбувається до першого елемента масиву. Якщо індексом масиву являється символ “@” або “*”, повернене значенням буде являтися усі об'єднані члени масиву. Дані індекси відрізняються поведінкою у подвійних лапках. Якщо розміщений у подвійних лапках, масив з конструкцією ${ім'я[*]} поверне значення об'єднаних елементів, розділених першим символом змінної середовища IFS; а ${ім'я[@]} — повертає кожен елемент масиву в окреме слово. У випадку відсутності елементів у масиві, ${ім'я[@]} нічого не повертає. Конструкція ${#ім'я_масиву[індекс]} повертає довжину елемента масиву “ім'я_масиву” за індексом “індекс”. Якщо, при даному синтаксисі, в якості індексу масиву передається символ @ або * - дана конструкція повертає кількість елементів у масиві. Існує можливість вияснення індексів (або ключів) масиву, до яких присвоєне значення. Конструкція ${!ім'я_масиву[@]} i ${!ім'я_масиву[*]} повертає індекси, до елементів масиву яких було присвоєне значення. Вбудована команда unset використовується для знищення масивів. Конструкція unset ім'я_масиву[індекс] знищує елемент масиву за індексом “індекс”, а не масив цілком. Вбудовані команди declare, local i readonly можуть отримувати прапорець “-a” для вказування індексованого масиву і прапорець “-A” для вказування асоціативного масиву. Якщо надано два даних прапорця, прапорець “-A” отримує більший пріоритет. Вбудована команда read підтримує прапорець “-a” для присвоєння списку слів зчитаних з стандартного потоку введення у масив, і може читати значення з стандартного вводу у індивідуальні елементи масиву.

Параметри скрипта

Дані параметри оболонки можуть бути тільки прочитані, запис у них заборонений. Вони відображають кількість і значення параметрів, які користувач передає скрипту командного рядка.
  • $* - містить параметри командного рядка розпочинаючи від першого переданого. Якщо параметр переданий не обгорнутим в подвійні лапки, кожен параметр передається в якості окремого слова. Коли дана змінна командного рядка передається обгорнутою у подвійні лапки, вона являється одним словом. Її значення містить слова розділені першим символом спеціальної змінної IFS — тобто, “$*” еквівалентно “$1c$2c...”, де c являється першим символом змінної IFS. Якщо змінна IFS не встановлена, параметри розділяються пробілами. Якщо змінна IFS містить значення null, слова об'єднуються без розділювача.
  • $@ - містить параметри командного рядка починаючи з першого. Коли дана змінна передається обгорнутою у подвійні лапки - кожен параметр передаються в якості окремого слова. Тобто “$@” еквівалентно “$1” “$2”... Коли немає параметрів командного рядка — дана змінна містить пустий рядок.
  • $# - містить кількість переданих параметрів в якості десяткового числа.
  • $? - містить код повернення останньої виконаної команди.
  • $- - містить поточні прапорці виклику оболонки, які встановленні за допомогою команди set, або встановлених самим командним інтерпретатором.
  • $$ - містить поточний ідентифікатор (ID) процесу командного рядка. У дочірньому процесі командного рядка, дана змінна містить ID батьківського процесу, а не власний.
  • $! - містить ідентифікатор процесу (ID) дії, яка останньою переміщена на задній фон виконання, якщо вона виконується в якості асинхронної команди або переміщена за допомогою вбудованої команди bg.
  • $0 — містить ім'я командного рядка або скрипта командного рядка. Дана змінна встановлюється під час ініціалізації процесу командного рядка. Якщо Bash викликається за допомогою файлу команд, значення змінної $0 встановлюється у значення назви даного файлу. Якщо Bash запускаються з прапорцем “-c”, змінна $0 встановлюється у значення першого аргументу, якщо він присутній. В іншому випадку, вона встановлюється у значення імені файлу, який викликав Bash.
  • $_ (знак підкреслення) — під час запуску командного інтерпретатора містить абсолютний шлях, який використовувався для запуску командного інтерпретатора або скрипта командного інтерпретатора, який запущений на виконання.
  • $1-$9..${N} — значення параметрів переданих поточному скрипту.

Змінні середовища

Під час запуску і виконання скрипта Bash, автоматично створюються змінні середовища. Вони також можуть оголошуватися програмістом всередині скрипта за допомогою команди “env” (для С/С++ перегляньте функцію setenv). Ось перелік деяких стандартних змінних середовища:
  • HOME — містить шлях до домашньої директорії поточного користувача;
  • PATH — список шляхів пошуку виконуваних файлів (команд), розділених двокрапкою;
  • PS1 — головний рядок відображення запрошення на ввід команд (рядок, який відображається після кожного натискання клавіші Enter);
  • PWD — містить шлях до поточної директорії розміщення у файловій системі (змінюється за допомогою команди “cd”);
  • SHLVL — містить число, яке означає порядковий номер рівня поточної оболонки відносно батьківських оболонок.
Кількість змінних середовища можуть відрізнятись у різних системах навіть одного дистрибутиву — кожна встановлена програма може створити свою змінну середовища. Це також справедливо і для користувачів, які самостійно можуть створювати будь-які змінні середовища.

Приклади

Розглянемо приклад скрипта командної оболонки, у якому оголошується звичайна змінна і індексний масив:
#!/bin/bash

#змінні
var1="value1"
empty_var=

#змінна-масив
indx_array[0]=$var1
indx_array[1]=$empty_var
indx_array[2]="інше значення"

#виводимо значення змінних на екран
echo "Значення var1: "$var1
echo "Значення пустої змінної:"$empy_var
echo "Кількість елементів індексованого масиву:"${#indx_array[@]}
echo "Значення 3-го елементу масиву:"${indx_array[2]}
echo "Довжина 3-го елементу масиву:"${#indx_array[2]}
echo "Наявні індекси масиву:"${!indx_array[@]}
Після виконання даного скрипта на екрані терміналу можна переглянути наступний результат (скрипт знаходиться у файлі variables.sh): bash_variables_echo_somple_scrip_example Розглянемо приклад з асоціативним масивом:
#!/bin/bash

# явно оголошуємо змінну асоціативний масив Array
declare -A Array=(["перший"]="значення1" ["другий"]="значення2") 

# встановлюємо значення масиву
Array["третій"]="значення3"
Array["четвертий"]="значення4"

# виводимо повідомлення про властивості масиву
echo "Кількість елементів асоціативного масиву: "${#Array[@]}
echo "Наявні індекси масиву: "${!Array[@]}
echo "Елемент масиву за індексом 'другий': "${Array["другий"]}
Після виконання даного скрипта в командному інтерпретаторі (файл “asoc_array.sh”) можна розглядати наступний результат: asociative_array_bash_script_example_output Тепер розглянемо приклад з посиланнями на змінні:
#!/bin/bash

# явно оголошуємо цілочисельну змінну з значенням 2016
declare -i int1=2016

# оголошуємо вказівник на змінну int1
declare -n r1=int1
# тепер усі зміни над r1 впливають на int1

# змінюємо значення змінної r1
r1=$r1+5

# змінюємо значення змінної int1
int1=3 

# виводимо на екран значення змінних
echo "значення змінної int1: "$int1
echo "значення змінної r1: "$r1
Виконання даного скрипта (файл references.sh) виведе на екран терміналу: bash_references_variables_example_output