Якщо ви працюєте в ІТ, то як ніхто знаєте про ціну часу. Оптимізація робочого процесу - один з найважливіших аспектів роботи в ІТ. Так чи інакше, наша робота (будь то верстка сайту, написання модулів, чи тестування додатків) вимагає повторення одних і тих самих дій: швидкі скріншоти з завантаженням на сервер, обробка виділеного тексту, конвертація файлів, парсинг данних та багато іншого. Аби не робити зайвих дій, а сконцентруватись на ідеї та самій суті її реалізації, ще в 1978 році Стівен Борн розробив командну оболонку sh, яка згодом, в 1987 році була вдосконалена Браяном Фоксом і переросла в те, що ми знаємо сьогодні як bash (Bourne again shell).
Цілком логічно, що з’являється запитання: "Для чого мені потрібне щось, що написали майже півстоліття тому?" Так от відповідь на нього проста: це "щось" дотепер є найпотужнішим інструментом автоматизації та, де-факто, стандартом для написання простих, але ефективних сценаріїв на всіх unix-based системах. Саме тому знати загальний синтаксис bash та вміти писати на ньому - критичний скіл для розробника.
В цьому пості я опишу синтаксичні конструкції, наведу приклади простих сценаріїв та вкажу на деякі підводні камені в bash. Впевнений, що такий конспект обов’язково стане в нагоді як початківцям, так і досвідченим розробникам, які могли забути деякі особливості цієї надпотужної командної оболонки.
Користувацька оболонка bash може працювати в двох режимах - інтерактивному та, відповідно, неінтерактивному. Відкрити оболонку в Ubuntu можна комбінацією клавіш Ctrl + Alt + F1
, звичний графічний інтерфейс зникне, а перед вами відкриється один з семи віртуальних терміналів, доступних в дистрибутиві Ubuntu.
Якщо оболонка видає запрошення (щось на зразок того, яке можна побачити нижче), то ви працюєте в інтерактивному режимі:
user@host:~$
Тут можна вводити найрізноманітніші unix-команди (як от: ls, grep, cd, mkdir, rm) і бачити результат їх виконання. Інтерактивною ця оболонка називається тому, що вона взаємодіє з користувачем напряму. Оточення робочого столу (графічний інтерфейс), в сімействі систем Debian (до яких належить і Ubuntu), прийнято розміщувати в сьомому віртуальному терміналі, тому щоб повернутись до звичного оточення робочого столу наберіть комбінацію Ctrl + Alt + F7
.
Звісно працювати в віртуальних терміналах не надто зручно, особливо, якщо потрібно редагувати документ та водночас виконувати які-небудь команди, тому надалі ми користуватимемось вбудованим в графічний інтерфейс емулятором віртуального терміналу, вбудованим в Ubuntu. Відкрити його можна комбінацією клавіш Ctrl + Alt + T
, або з Unity Dash, знайшовши його в списку програм.
В неінтерактивному режимі оболонка читає команди з деякого файлу і послідовно виконує їх. Коли інтерпретатор дійде до кінця файлу, робота з оболонкою автоматично завершиться. Запустити оболонку в неінтерактивному режимі можна з допомогою команд:
sh скрипт
bash скрипт
Де скрипт
це шлях до файлу, що містить команди для виконання. Такий файл є звичайним текстовим документом, який можна створити з допомогою будь-якого текстового документу. Втім, можна спростити виклик скрипту всього лише зробивши його виконуваним. Для цього необхідно надати відповідні права доступу цьому файлу з допомогою команди chmod:
chmod +x скрипт
Окрім цього, в першому рядку скрипта необхідно вказати яка оболонка повинна виконувати цей сценарій. Це можна зробити, розмістивши на початку відповідну вказівку #!/bin/sh
(для оболонки sh) або #!/bin/bash
(відповідно для bash). Після цього файл можна буде викликати на виконання звернувшись до нього в терміналі:
./скрипт
Сценарії можуть містити коментарі. Коментарі - це оператори, які можна розміщувати в сценарії оболонки, але який ігнорується при виконанні. Коментарі повинні починатись з символу #
і продовжуються до символу нового рядка.Наприклад:
#!/bin/bash
# Сценарій, що виведе ім’я користувача
whoami
Оболонка дозволяє створювати та видаляти змінні, а також виконувати над ними операції. Змінні в bash можуть знаходитись в 3-х областях видимості:
Локальні змінні - це звичайні змінні в всередині одного сценарію. Вони не доступні іншим програмам та сценаріям, які запускаються з цієї оболонки. Оголошуються змінні з допомогою символу =
(зверніть увагу на те, що перед і після =
немає пробілів), а до їх значення звертаються з допомогою символу $
:
name="Петро Петрович"
echo $name # вивід значення
unset name # видалення змінної
Також можна оголосити локальну змінну всередині функції і яка буде доступна лише в тілі цієї функції:
local локальна_змінна=значення
Змінні оточення - це змінні, які доступні будь-яким програмам, що запущені з данної оболонки. Оголошуються вони так само як і локальні змінні, але з командою export
:
export глобальна_змінна=значення
В bash є багато змінних оточення, які досить часто зустрічаються в сценаріях, наприклад:
- HOME - шлях до домашнього каталогу користувача;
- PATH - список каталогів, в яких оболонка шукає виконувані файли;
- PWD - шлях до робочого каталогу;
- RANDOM - формує ціле випадкове число;
- HOSTNAME - ім’я комп’ютера, на якому виконується оболонка; Детальніше про змінні оточення можна почитати тут.
Змінні оболонки - це змінні, які встановлюються оболонкою і необхідні їй для коректної роботи. Ці змінні мають імена порядкового номера ($1
, $2
, $3
, ...) і містять аргументи, які передавались сценарію при запуску, як от:
./some_script.sh VAL1 VAL2 # всередині сценарію $1='VAL1', $2='VAL2'
Змінним можна присвоювати значення за замовчуванням таким чином:
: ${VAR:='значення за замовчуванням'} # Якщо змінна VAR порожня, присвоїти їй "значення за замовчуванням"
В bash також є можливість працювати з масивами. При роботі з масивами часто користуються змінною оточення IFS - роздільника полів для вхідних рядків (IPS - Input Field Separator). За замовчуванням IFS рівний пробільному символу, але може бути змінений для розбиття рядка на елементи масиву, наприклад, за комами. Зверніть увагу, що для формування змінних оболонки, які доступні через $1
, $2
і т.д., використовується саме змінна IFS, тобто введений після ім’я скрипта рядок аргументів, буде розділений саме за першим символом, який зберігається в цій змінній.
Оголосити масив можна наступним чином:
files[0]=Яблуко
files[1]=Груша
echo ${files[*]} # надрукує елементи масиву без врахування IFS
echo ${files[@]} # надрукує елементи масиву з IFS в якості розільника
Отримувати доступ до елементу масиву можна з допомогою зрізів: ${arr:0:1}
. Видалити перший елемент масиву можна з допомогою зсуву: shift arr
. Додати в елементи в масив: arr=("${arr[@]}" "Item 1" "Item 2")
. Перевірити входження елементу в масив реалізується з допомогою дещо складнішої конструкції:
if [[ ${arr[(r)some]} == some ]]; then
# команди, якщо елемент входить
else
# команди, якщо не входить
fi
В цьому прикладі arr
- деякий масив, а some
- це елемент, який ми перевіряємо на входження.
Присвоїти змінній результат роботи команди чи арифметичних операцый можна з допомогою апострофів, або конструкції $(вираз)
:
now=`data +%T`
# або
now=$(data +%T)
echo now # 19:08:26
Арифметичні операції необхідно огортати в подвійні дужки:
foo=$(( ((10 + 5*3) – 7) / 2 ))
echo $foo #> 9
З допомогою фігурних дужок можна генерувати рядки довільного вигляду, або враховувати різні варіанти написання слова:
echo beg{i,a,u}n #> begin began begun
Варто згадати і про строгість лапок в bash: одинарні лапки - строгі, подвійні - нестрогі. Це означає, що при підстановленні змінних в рядок з подвійними лапками, інтерпретатор підставить відповідне значення змінної. Одинарні лапки виведуть рядок так, як ви його написали. Приклад:
echo "Домашня директорія: $HOME" #> Домашня директорія: /home/user
echo 'Домашня директорія: $HOME' #> Домашня директорія: $HOME
Файл з якого відбувається читання, називають стандартним потоком введення, а в який відбувається запис, відповідно - стандартним потоком виводу. В bash є три стандартних потоки:
Код | Назва | Опис |
---|---|---|
0 | stdin | ввід |
1 | stdout | вивід |
2 | stderr | потік помилок |
Для перенаправлення потоків використовують основні оператори:
>
- перенаправлення потоку виведення в файл (файл буде створений, або перезаписаний);>>
- дописати потік виведення в кінець файлу;<
- перенаправляє данні з файлу в потік введення;<<<
- читання даних з рядка, замість всього вмісту файлу (працює для bash 3+);2>
- перенаправляє потік помилок в файл (файл буде створений, або перезаписаний);2>>
- дописати помилки в кінець файлу;
Стандартні потоки можна перенаправляти не лише в файли, але й на вхід інших сценаріям. З’єднання потоку виведення одної програми з потоком введення іншої називають каналом або пайпом (pipe). Нижче наведений простий конвеєр з трьох команд: команда1
перенаправляє свій вивід на вхід команді2
, яка, в свою чергу, перенаправляє власний вивід на вхід команді3
:
cmd1 | cmd2 | cmd3
Конвеєри - це команди, що з’єднані операторами ;
, &&
, ||
для виконання в певній послідовності. Оператори організації конвеєрів працюють наступним чином:
команда1 ; команда2
-команда2
виконається після команди1 незалежно від результату її роботикоманди1
;команда1 && команда2
-команда2
виконаються лише після успішного виконаннякоманди2
(тобто з кодом завершення 0);команда1 || команда2
-команда2
виконається лише після невдалого виконаннякоманди1
(тобто код завершеннякоманди1
буде відмінним від 0)
В скриптовій мові bash підтримуються два оператори розгалуження: if
та case
. Оператор if
, як і в інших мовах, виконує певний блок вказівок, в залежності від умови. Умову огортають в подвійні квадратні дужки [[ ... ]]
, які bash розглядає як один елемент з кодом виходу. Всередині блоку операторів огорнутого в [[ ]]
дозволяється використовувати оператори &&
та ||
. Приклади:
# Однорядковий запис
if [ ... ]; then echo "true"; else echo "false"; fi;
## Вкладені умови
if [ ... ] && [ ... ]; then
...
elif [[ ... && ... ]]; then
...
else
...
fi;
Зверніть увагу, що [
, умова та ]
обов’язково повинні бути розділені пробілами, інакше оболонка сприйме в якості команди [умова
.
В нижче наведена таблиця з можливими умовами порівняння:
# Робота з файлами
-e Перевірити чи існує файл чи директорія (-f, -d)
-f Файл існує (!-f - не існує)
-d Каталог існує (!-f - не існує)
-s Файл існує і він не порожній
-r Файл існує і доступний для читання
-w ... на запис
-x ... на виконання
-h Є символічним посиланням
# Робота з рядками
-z Порожній рядок
-n Не порожній рядок
== Рівне
!= Не рівне
# Операції з числами
-eq Рівне
-ne Не рівне
-lt Менше
-le Менше або рівне
-gt Більше
-ge Більше або рівне
Приклади:
if [ `uname` == "Adam"]; then
echo "Не їж яблуко!"
elif [ `uname` == "Eva"] then
echo "Не бери яблуко!"
else
echo "Яблука зараз дуже дорогі!"
fi;
Якщо необхідно зробити вибір з кількох альтернатив, в нагоді стане оператор case
. Принцип його роботи найлегше зрозуміти на прикладі:
case "$extension" in
(jpg|jpeg)
echo "Це зображення у форматі jpeg."
;;
png)
echо "Це зображення у форматі png"
;;
gif)
echo "А це гіфочка))"
*)
echo "Оу! Це взагалі не зображення!"
;;
esac
В прикладі оператор перевіряє значення змінної $extension
на співпадіння з одним із шаблонів і у випадку співпадіння виконає відповідний блок коду. У випадку, якщо не буде знайдено співпадінь, виконаються вказівки, що відповідають шаблону *
.
Мова оболонки дає користувачеві можливість організовувати циклічне виконання інструкцій з допомогою циклів:
while
for
select
Оператор while
описується наступним чином:
while умовa do
тіло
done
Інтерпретатор в першу чергу виконує команди, що описані в умові
. Якщо результат виконання нульовий, то виконується тіло, а, після її виконання, перехід до наступної ітерації, в іншому випадку відбувається вихід з циклу. В умові може бути будь-яка допустима команда. Приклад:
#!/bin/sh
# Квадрати чисел від 1 до 10
x=0
while [ $x –lt 10 ] do #значення змінної x менше 10?
echo $(($x*$x))
x=`expr $x + 1` # збільшуємо х на 1
done
Цикл for
виконує тіло для кожного елемента зі списку. Синтаксис циклу for
такий:
for ім’я in елемент1 елемент2 ... елементN do
тіло
done
В якості елементів зазвичай використовують різноманітні шаблони (wildcards). Дуже зручно застосовувати for
для проходження по каталогах та виконання операцій над групою файлів. В прикладі нижче, цикл проходить по всіх файлах з розширенням *.bash
, переміщує їх в директорію ~/scripts
та додає їх права на виконання.
#!/bin/sh
# Переміщення всіх скриптів з ~ в директорію ~/scripts
for FILE in $HOME/*.bash do
mv $FILE ${HOME}/scripts
chmod +x ${HOME}/scripts/${FILE}
done
Цикл select
допомагає організувати зручне меню вибору і застосовується тоді, коли користувач повинен обрати один елемент із запропонованого списку. Загалом цикл select
має такий самий синтаксис як і цикл for
:
select відповідь in елемент1 елемент2 ... елементN do
тіло
done
При виконанні цього оператору, всі елементи зі списку висвічуються на екрані зі своїми порядковими номерами у вигляді списку варіантів відповіді, після списку виводиться спеціальне запрошення для введення. Зазвичай воно має вигляд #?
. Введений користувачем номер списку записується в змінну відповідь
. Якщо відповідь
містить номер пункту меню, то в змінну заноситься значення відповідного елементу зі списку. Якщо в списку немає введеного пункту, список буде показаний знову. Після того, як користувач зробить правильний вибір, виконаються вказівки в тілі, а цикл перейде до наступної ітерації і всі дії повторяться з самого початку - саме тому роботу циклу select
бажано переривати.
#!/bin/sh
echo -n "Введіть назву пакету: " && read PACKAGE
PS3="Оберіть пакетний менеджер : "
select ITEM in bower, npm, pip do
case $ITEM in
bower) bower install $PACKAGE ;;
npm) npm install $PACKAGE ;;
pip) pip install $PACKAGE ;;
esac
break
done
Приклад вище запитує в користувача назву пакету, який він бажає встановити, далі цікавиться який пакетний менеджер використати і в залежності від вибору встановлює потрібний пакет та зупиняє виконання.
Оболонка також має команди, для зміни нормального виконання циклу. Оператор break повністю зупиняє виконання циклу, оператор continue - переходить до наступної ітерації.
В сценаріях оболонки можливе оголошення та виклик функцій. Варто зазначити, що саме поняття функцій в bash дещо урізане. Насправді, функції в bash - це іменована група команд, які виконаються під час звертання до функції. В будь-якому випадку функціями слід користуватись всюди, де є код, що повторюється з невеликими варіаціями.
Оголошення функції має такий вигляд:
ім’я_функції () {
команди
}
ім’я_функції # звертання до функції
Оголошення функції обов’язково повинне передувати її першому виклику. Звертання до функції відбувається шляхом вказання її імені в якості команди.
Функція може приймати аргументи та повертати після свого виконання результат - код виходу. Функція посилається до своїх аргументів точно так само, як і до локальних змінних, з допомогою позиційних змінних - $1
, $2
і тд. Результат роботи можна повертати з допомогою команди return
. Наприклад, функція, яка приймає параметр (ім’я) і завершуючи свою роботу з кодом 0:
#!/bin/sh
#функція з параметром
greeting() {
if [ -n "$1" ]; then
echo "Привіт, $1!"
else
echo "Привіт, невідомий!"
fi
return 0
}
greeting користувач #> Привіт, користувач!
greeting #> Привіт, невідомий!
Команда return
повертає код завершення 0 - це код успішного завершення сценарію. Кожна програма по завершенню роботи записує в змінну оточення #?
код завершення - число від 0 до 255. З допомогою цієї змінної можна визначати статус виконання кожної окремої команди чи скрипта. Якщо програма завершилась помилкою, кодом завершення буде ціле число відмінне від нуля. Зверніть увагу, що якщо сценарій завершується командою exit
без параметрів, кодом завершення сценарію буде код завершення останньої виконаної команди.
Оболонка дає кілька засобів для налагодження сценаріїв. Для активації режиму налагодження, сценарій повинен бути запущений з допомогою спеціальних опцій. Перший рядок сценарію повинен мати вигляд:
#!/bin/sh опция
Можна обирати серед таких опцій:
–n
- читати всі команди, але не виконувати їх;–v
- виводити всі рядки по мірі їх обробки інтерпретатором;–x
- виводити всі команди та їх аргументи по мірі їх виконання.
Для налагодження сценарію частинами, потрібний фрагмент помічають викликом команди set
iз відповідною опцією з таблиці. При чому, для увімкнення режиму налагодження, перед опцією вказують символ -
, для вимкнення режиму налагодження використовують +
:
set –x # вмиваємо режим налагоджування
...
set +x # вимикаємо режим налагоджування
Загальна практика налагоджування полягає в тому, щоб перш ніж запустити його на виконання, необхідно перевірити його синтаксис з допомогою опції -n
. Для більшої детальності можна комбінувати ключі -nv
. Після виправлення синтаксичних помилок проводиться налагожування з допомогою опції -x
.
Сподіваюсь ви знайшли для себе щось нове в цьому конспекті, або, принаймні, освіжили свої знання.
Якщо вас зацікавив скриптинг на Bash, прошу підтримати мене, поширюючи статтю серед своїх друзів.
Радий буду почути конструктивну критику та зауваження в коментарях.