Конспект по Bash

10 хв. читання

Якщо ви працюєте в ІТ, то як ніхто знаєте про ціну часу. Оптимізація робочого процесу - один з найважливіших аспектів роботи в ІТ. Так чи інакше, наша робота (будь то верстка сайту, написання модулів, чи тестування додатків) вимагає повторення одних і тих самих дій: швидкі скріншоти з завантаженням на сервер, обробка виділеного тексту, конвертація файлів, парсинг даних та багато іншого. Аби не робити зайвих дій, а сконцентруватись на ідеї та самій суті її реалізації, ще в 1978 році Стівен Борн розробив командну оболонку [sh][wiki-sh], яка згодом, в 1987 році була вдосконалена Браяном Фоксом і переросла в те, що ми знаємо сьогодні як [bash][wiki-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 - ім'я комп'ютера, на якому виконується оболонка;

Детальніше про змінні оточення можна почитати [тут][env-vars].

Змінні оболонки - це змінні, які встановлюються оболонкою і необхідні їй для коректної роботи. Ці змінні мають імена порядкового номера ($1, $2, $3, ...) і містять аргументи, які передавались сценарію при запуску, як от:

./some_script.sh VAL1 VAL2  # всередині сценарію $1='VAL1', $2='VAL2'

Змінним можна присвоювати значення за замовчуванням таким чином:

: ${VAR:='значення за замовчуванням'} # Якщо змінна VAR порожня, присвоїти їй "значення за замовчуванням"

Масиви та списки

В bash також є можливість працювати з масивами. При роботі з масивами часто користуються змінною оточення IFS - роздільника полів для вхідних рядків (IFS - 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 виконаються лише після успішного виконання команди1 (тобто з кодом завершення 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, прошу підтримати мене, поширюючи статтю серед своїх друзів.

Радий буду почути конструктивну критику та зауваження в коментарях.

wiki-sh wiki-bash env-vars

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.1K
Приєднався: 9 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація