В цьому розділі ми детальніше обговоримо вживання змінних та аргументів. По завершенні ви зможете:

  • оголошувати та використовувати масиви змінних
  • визначати тип вживаних змінних
  • робити змінні недоступними для зміни
  • присвоювати значення змінним за допомогою оператора set

Типи змінних

Типове присвоєння значень

Як ми вже бачили, Bash розуміє багато типів змінних та параметрів. Досі ми не переймалися тим, якими типами змінних ми оперуємо, тож наші змінні могли містити будь-які значення, які ми їм присвоювали. Це гарно показує простенький приклад:

[bob in ~] VARIABLE=12

[bob in ~] echo $VARIABLE
12

[bob in ~] VARIABLE=string

[bob in ~] echo $VARIABLE
string

Проте можуть бути ситуації, коли така поведінка змінних є небажаною, наприклад під час обробки телефонних чи інших номерів. Окрім чисел та змінних, ви можете оголошувати сталі змінні. Часто це робиться на початку сценарію, коли сталим присвоюються їхні значення. Далі ми бачимо лише відсилання до імені сталої, тобто якщо сталу потрібно змінити, зробити це можна лише один раз. Також змінна може складатись із набору змінних будь-якого типу, це є так звані масиви змінних.

Вживання вбудованої команди declare

За допомогою оператора declare ми можемо обмежити значення, котрі присвоюються змінній. Його синтаксис наступний:

declare ОПЦІЇ ЗМІННА=значення

Щоб задати тип даних, які можуть бути присвоєні змінній, використовуються наступні опції:

-a : змінна є масивом

-f : вживати лише імена функцій

-i : змінна інтерпретується як ціле число; обчислення виконуються лише після того, як їй присвоюється значення

-p : показати атрибути та значення всіх змінних; всі інші опції ігноруються

-r : зробити змінну доступною лише для читання

-t : додати кожній змінній атрибут trace

-x : зробити змінну доступною для експорту наступним командам через середовище

Вживання + замість – вимикає відповідну опцію. Якщо оператор вживається всередині функцій, він створює локальні змінні, недоступні за межами функцій. Ось простенький приклад, який показує, як задавання типу змінної впливає на її значення:

[bob in ~] declare -i VARIABLE=12

[bob in ~] VARIABLE=string

[bob in ~] echo $VARIABLE
0

[bob in ~] declare -p VARIABLE
declare -i VARIABLE="0"

Зауважте, що Bash має спеціальну опцію для оголошення цілочисельних змінних, але не має такої для оголошення ланцюжків. Це зроблено тому, що зазвичай змінна може містити будь-які типи даних:

[bob in ~] OTHERVAR=blah

[bob in ~] declare -p OTHERVAR
declare -- OTHERVAR="blah"

Як тільки ви обмежуєте присвоєння значень змінній, вона може приймати лише заданий тип даних. Це може бути ціле число, стала або масив. Для детальної довідки про стан завершення зверніться до сторінок Bash info.

Сталі

У Bash сталі створюються з змінних, котрим надається статус «лише для читання». Вбудована команда readonly позначає кожну задану змінну як незмінювану. Її синтаксис такий:

readonly ОПЦІЯ ЗМІННА(І)

Після чого значення ЗМІННОЇ не може бути змінене. Якщо задається опція –f, то кожна змінна посилається на функцію оболонки (див розділ 11). Якщо задано –а, кожна змінна посилається на масив. Якщо аргументів не задано, або ж задано аргумент –р, показується список сталих. Станом завершення зазвичай є нуль, якщо не було задано неправильних опцій, не було посилань на неіснуючі функції чи змінні і опція –f не вживалась до змінних замість функцій.

Масиви

Створення масивів

Масив – це змінна, що містить багато значень. Будь-яка змінна може вживатись як масив. Масиви не мають ані обмежень по довжині ані якихось особливих вимог щодо організації чи типів даних. Нумерація елементів масиву розпочинається з нуля. Непряме оголошення масиву відбувається наступним чином:

array [ІНДЕКС] = значення

ІНДЕКС повинен бути арифметичним виразом, що розкривається у ціле число. Пряме оголошення масиву робиться за допомогою оператора declare:

declare –a НАЗВА_МАСИВУ

Оголошення з номером індексу також буде сприйнято, але сам індекс проігнорується. Атрибути масиву можуть бути вказані за допомогою операторів declare та readonly. Атрибути впливають відразу на всі елементи масиву: ви не можете мати змішаних масивів. Масиви можуть бути також створені за допомогою складених призначень у такому форматі:

МАСИВ=(значення1 значення2 … значенняN)

Кожен елемент масиву задається у формі [номеріндексу=]значення. Номеріндексу є необов’язковим. Якщо він вживається, тоді значення записується у відповідний елемент масиву, в іншому випадку значення вноситься у останній внесений плюс один елемент масиву. Такий формат присвоєння приймається також оператором declare. Якщо індекси не вказуються, нумерація починається з нуля. Додавання додаткових або ж відсутніх елементів масиву відбувається наступним чином:

ІМ’Я_МАСИВУ[номер_індексу] = значення

Запам’ятайте, що вбудований оператор read має опцію –a, котра дозволяє зчитувати та присвоювати значення елементам масиву.

Розкривання^dereferencing елементів масиву

Для того, щоб отримати вміст елементу масиву, використовуйте фігурні дужки. Як ви побачите з наступного прикладу, це необхідно, щоб обійти порядок інтерпретації виразів оболонкою. Якщо номер індексу буде @ чи *, це вважатиметься посиланням на всі елементи масиву.

[bob in ~] ARRAY=(один два три)

[bob in ~] echo ${ARRAY[*]}
один два три

[bob in ~] echo $ARRAY[*]
один[*]

[bob in ~] echo ${ARRAY[2]}
три

[bob in ~] ARRAY[3]=чотири

[bob in ~] echo ${ARRAY[*]}
один два три чотири

Якщо ви не вказуєте номер індексу при посиланні на елемент масиву, вважається, що ви вибираєте перший елемент з порядковим номером нуль.

Знищення масивів

Для видалення масивів або елементів масиву використовується вбудована команда unset.

[bob in ~] unset ARRAY[1]

[bob in ~] echo ${ARRAY[*]}
один три чотири

[bob in ~] unset ARRAY

[bob in ~] echo ${ARRAY[*]}
<--немає виводу-->

Приклади масивів

Практичні приклади вживання масивів знайти важко. Ви зможете знайти сценарії, які нічогісінько не роблять у вашій системі, а лише використовують масиви для розрахунку математичних множин, до прикладу. І це може бути лише одним з багатьох цікавих прикладів, який лише показує, що в теорії ви можете зробити з масивами. Причиною цього є те, що масиви доволі складними структурами. Значно цікавіші приклади з масивами ви можете знайти у джерельних кодах більшості програм UNIX, до прикладу вбудованої команди Bash history. Зацікавленим користувачам рекомендуємо звернутись до файлу built-ins\fc.def у джерельних кодах Bash.

Іншою причиною відсутності хороших сценаріїв є те, що не всі оболонки їх підтримують, отож маємо проблеми несумісності.

Після тривалих пошуків ми нарешті відшукали сценарій, що працює у Інтернет-провайдера. Він поширює конфігураційні файли Apache у хостера^changelog:

!/bin/bash

# Сценарій для синхронізації файлів конфігурації httpd між комп’ютерами хостера.
#
if [ $(whoami) != 'root' ]; then
        echo "Лише root може запускати $0"
        exit 1;
fi
if [ -z $1 ]; then
        echo "Вживання: $0 </шлях/до/httpd.conf>"
        exit 1
fi

httpd_conf_new=$1
httpd_conf_path="/usr/local/apache/conf"
login=htuser

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

Перші дві перевірки дозволяють переконатись, що сценарій запустив правильний користувач і з правильними параметрами. Імена хостів, на котрих мають обновитись файли конфігурації, описані в масиві farm_hosts. Далі на ці хости переписуються оновлені файли конфігурації, після чого Apache перезапускається. Зверніть увагу на використання команд з комплекту Secure Shell (ssh), котрі шифрують дані, що пересилаються між хостами.

Спасибі Eugene та компанії за наданий сценарій.

Наступний сценарій написав Dan Richter. Ось проблема, з котрою він зіткнувся:

«…У нашій компанії ми маємо демонстраційні версії на нашому веб-сайті. І щотижня хтось повинен їх перевіряти. Отож я додав у cron задачу, котра заповнює масив даних можливими кандидатами, за допомогою date +%W визначає номер тижня у році та виконує операцію ділення націло для знаходження коректного індексу. Далі щасливець отримує відповідне повідомлення електронною поштою.»

А ось як він вирішив цю проблему:

#!/bin/bash
# Це сценарій get-tester-address.sh 
#
# Спершу ми перевіримо, чи bash підтримує масиви
# (Підтримка масивів була додана нещодавно.)
#
whotest[0]='test' || (echo 'Невдача: у цій версії bash масиви не підтримуються.' && exit 2)

#
# Наш список кандидатів. Не бійтесь додавати та видаляти людей з нього.
#
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=0
while [ "x${wholist[count]}" != "x" ]
do
   count=$(( $count + 1 ))
done

#
# А далі подивимось, чия черга перевіряти.
#
week=`date '+%W'`    # Номер тижня у році (0..53).
week=${week#0}       # Вилучити можливий передній нуль

let "index = $week % $count"   # тиждень розділений на кількість = щасливець

email=${wholist[index]}     # Отримати e-mail щасливця

echo $email     # Надрукувати його його

Далі цей сценарій використовується іншим

email=`get-tester-address.sh`   # Взнати, кому написати листа
hostname=`hostname`    # Ім’я комп’ютера

#
# Відправити листа 
#
mail $email -s '[Перевірка демо]' <<EOF
Щасливим тестером цього тижня є $email

Нагадування: список демонстраційних сайтів лежить тут:
    <http://web.example.com:8080/DemoSites>

(Цього листа було згенеровано $0 на ${hostname}.)
EOF

Операції над змінними

Арифметичні операції

Ми їх уже обговорювали у параграфі 3.4.6

Довжина змінних

Використовуйте наступний синтаксис, щоб взнати число символів у змінній: ${#ЗМІННА}. Якщо ЗМІННОЮ є "*" або "@", замість неї підставляється кількість позиційних параметрів або число елементів у масиві. Це показано у наведеному прикладі:

[bob in ~] echo $SHELL
/bin/bash

[bob in ~] echo ${#SHELL}
9

[bob in ~] ARRAY=(one two three)

[bob in ~] echo ${#ARRAY}
3

Перетворення змінних

Підстановка

${ЗМІННА:-СЛОВО}

Якщо змінна є невизначеною або порожньою, замість неї підставляється розкривання СЛОВА, в іншому випадку підставляється значення змінної:

[bob in ~] echo ${TEST:-test}
test

[bob in ~] echo $TEST

[bob in ~] export TEST=a_string

[bob in ~] echo ${TEST:-test}
a_string

[bob in ~] echo ${TEST2:-$TEST}
a_string

Такий підхід часто застосовується у перевірках умов, наприклад ось таких:

[ -z "${COLUMNS:-}" ] && COLUMNS=80

Це коротше, аніж писати

if [ -z "${COLUMNS:-}" ]; then
COLUMNS=80
fi

Дивіться параграф 7.1.2.3 для додаткової інформації про такий тип перевірок. Якщо дефіс (-) замінюється знаком рівності, результат розкривання присвоюється параметру, якщо той був порожній:

[bob in ~] echo $TEST2

[bob in ~] echo ${TEST2:=$TEST}
a_string

[bob in ~] echo $TEST2
a_string

Наступний приклад перевіряє змінну на наявність. Якщо вона не задана, результат розкривання СЛОВА друкується на стандартний вивід і неінтерактивний сценарій завершується.

[bob in ~] cat vartest.sh
#!/bin/bash

# Цей сценарій перевіряє, чи задана змінна. Якщо ні, 
# він завершується, виводячи повідомлення.

echo ${TESTVAR:?"А я ще так багато хотів зробити..."}
echo "TESTVAR встановлено, ми можемо продовжувати."

[bob in testdir] ./vartest.sh
./vartest.sh: line 6: TESTVAR: А я ще так багато хотів зробити...

[bob in testdir] export TESTVAR=present

[bob in testdir] ./vartest.sh
present
TESTVAR встановлено, ми можемо продовжувати.

Вживання плюса замість знаку запитання присвоює змінній результат розкривання СЛОВА; якщо ж його немає, нічого не відбувається.

Видалення підланцюжків

Щоб видалити ряд символів із змінної, використовуйте такий синтаксис:

${ЗМІННА:ЗСУВ:КІЛЬКІСТЬ}

Параметр КІЛЬКІСТЬ вказує, скільки символів залишити, починаючи з першого символу після точки ЗСУВУ. Якщо КІЛЬКІСТЬ пропущено, береться весь залишок ланцюжка:

[bob in ~] export STRING="thisisaverylongname"

[bob in ~] echo ${STRING:4}
isaverylongname

[bob in ~] echo ${STRING:6:5}
avery

Конструкції

${ЗМІННА#СЛОВО} 

і

${ЗМІННА##СЛОВО}

вживаються щоб видалити вираз, що відповідає розкриванню СЛОВА у ЗМІННІЙ. СЛОВО розкривається по тих самих правилах, що й імена файлів. Якщо вираз відповідає початку розкритого значення ЗМІННОЇ, тоді результат розкривання є розширеним значенням ЗМІННОЇ з скороченою ("#") чи продовженою ("##")формою відповідності^patterns.

Якщо ЗМІННОЮ є * чи @, операція часткового вилучення застосовується до кожного позиційного параметра і розкриванням є результуючий список.

Якщо ЗМІННА є масивом, операція часткового вилучення проводиться над кожним його елементом і результатом розкривання є результуючий список. Це показано у наступному прикладі:

[bob in ~] echo ${ARRAY[*]}
one two one three one four

[bob in ~] echo ${ARRAY[*]#one}
two three four

[bob in ~] echo ${ARRAY[*]#t}
one wo one hree one four

[bob in ~] echo ${ARRAY[*]#t*}
one wo one hree one four

[bob in ~] echo ${ARRAY[*]##t*}
one one one four

Протилежного ефекту можна добитися вживаючи «%» та «%%», як у наведеному прикладі. СЛОВО повинно відповідати частині ланцюжка:

[bob in ~] echo $STRING
thisisaverylongname

[bob in ~] echo ${STRING%name}
thisisaverylong

Заміна частини імен змінних

Це робиться за допомогою

${ЗМІННА/ВИРАЗ/ЛАНЦЮЖОК}

або

${ЗМІННА//ВИРАЗ/ЛАНЦЮЖОК}

Перша форма замінить лише перше входження ВИРАЗУ на ЛАНЦЮЖОК, друга – всі входження.

[bob in ~] echo ${STRING/name/string}
thisisaverylongstring

Додаткову інформацію можна знайти у сторінках Bash info.

Підсумок

Звичайно змінна може містити довільний тип даних, поки вона не буде заявлена особливим чином. Сталі задаються за допомогою вбудованої команди readonly.

Масиви містять набори змінних. Якщо при цьому задано тип даних, всі елементи масиву будуть цього типу.

Можливості Bash дають змогу змінювати і перетворювати змінні на льоту. Стандартні операції включають можливість визначення довжини змінної, арифметичні дії над ними, заміну всього вмісту змінної чи лише частини.

Вправи

  1. Напишіть сценарій, що робить наступне:

  2. виводить ім’я виконуваного сценарію

  3. виводить перший, третій і десятий аргументи сценарію
  4. виводить загальне число аргументів, передане сценарію
  5. якщо було більше, аніж три позиційних параметри, за допомогою shift перемістіть інші параметри на 3 вліво
  6. роздрукуйте значення всіх аргументів, що залишились
  7. роздрукуйте номери аргументів
    Перевірте на одному, трьох та більш ніж 10 аргументах

  8. Напишіть сценарій, що імітує простий браузер (у текстовому режимі), використовуючи wget та links –dump, щоб показати HTML сторінки користувачеві. Користувач має три можливості: ввести URL, ввести b щоб повернутись або q щоб вийти. Останніх 10 адрес, введених користувачем, зберігаються у масиві, з котрого користувач може їх відновити за допомогою можливості «Назад».