105.2 Урок 2
Сертифікат: |
LPIC-1 |
---|---|
Версія: |
5.0 |
Розділ: |
105 Оболонки та сценарії оболонки |
Тема: |
105.2 Налаштування або написання простих сценаріїв |
Урок: |
2 з 2 |
Вступ
Сценарії оболонки зазвичай призначені для автоматизації операцій, пов’язаних з файлами та каталогами, тих самих операцій, які можна виконувати вручну в командному рядку. Проте сценарії оболонки не обмежуються лише документами користувача, оскільки конфігурація та взаємодія з багатьма аспектами операційної системи Linux також здійснюються за допомогою файлів сценаріїв.
Оболонка Bash пропонує багато корисних вбудованих команд для написання сценаріїв оболонки, але повна потужність цих сценаріїв залежить від поєднання вбудованих команд Bash із багатьма утилітами командного рядка, доступними в системі Linux.
Розширені тести
Bash як мова сценаріїв здебільшого орієнтована на роботу з файлами, тому вбудована команда Bash test
має багато параметрів для оцінки властивостей об’єктів файлової системи (по суті, файлів і каталогів). Тести, зосереджені на файлах і каталогах, корисні, наприклад, для перевірки наявності файлів і каталогів, необхідних для виконання певного завдання, а також для інформації щодо можливості їх читання. Потім, пов’язаний з умовною конструкцією if, правильний набір дій виконується, якщо тест проходить успішно.
Команда test
може обчислювати вирази за допомогою двох різних синтаксисів: тестові вирази можна вказати як аргумент команди test
або їх можна помістити в квадратні дужки, де команда test
вказана неявно. Таким чином, тест для оцінки того, чи /etc
є дійсним каталогом, можна записати як test -d /etc
або як [ -d /etc]
:
$ test -d /etc $ echo $? 0 $ [ -d /etc ] $ echo $? 0
Як підтверджено кодами статусу виходу в спеціальній змінній $?
- значення 0 означає, що перевірка була успішною - обидві форми оцінили /etc
як дійсний каталог. Якщо припустити, що шлях до файлу чи каталогу зберігається в змінній $VAR
, такі вирази можна використовувати як аргументи test
або в квадратних дужках:
-a "$VAR"
-
Визначає, чи існує шлях у
VAR
у файловій системі і чи є він файлом. -b "$VAR"
-
Визначає, чи шлях у
VAR
є спеціальним файлом блоку. -c "$VAR"
-
Визначає, чи шлях у
VAR
є файлом спеціальних символів. -d "$VAR"
-
Визначає, чи шлях у
VAR
є каталогом. -e "$VAR"
-
Визначає, чи існує шлях у
VAR
у файловій системі. -f "$VAR"
-
Визначає, чи існує шлях у
VAR
і чи це звичайний файл. -g "$VAR"
-
Визначає, чи має шлях у
VAR
дозвіл SGID. -h "$VAR"
-
Визначає, чи шлях у
VAR
є символічним посиланням. -L "$VAR"
-
Визначає, чи шлях у
VAR
є символічним посиланням (наприклад,-h
). -k "$VAR"
-
Визначає, чи має шлях у
VAR
дозвіл sticky bit. -p "$VAR"
-
Визначає, чи шлях у
VAR
є файлом pipe. -r "$VAR"
-
Визначає, чи шлях у
VAR
читається поточним користувачем. -s "$VAR"
-
Визначає, чи існує шлях у
VAR
і чи він не порожній. -S "$VAR"
-
Визначає, чи шлях у
VAR
є файлом сокета. -t "$VAR"
-
Визначає, чи шлях у
VAR
відкритий у терміналі. -u "$VAR"
-
Визначає, чи шлях у
VAR
має дозвіл SUID. -w "$VAR"
-
Визначає, чи шлях у
VAR
доступний для запису поточним користувачем. -x "$VAR"
-
Визначає, чи шлях у
VAR
є доступним для виконання поточним користувачем. -O "$VAR"
-
Визначає, чи шлях у
VAR
належить поточному користувачеві. -G "$VAR"
-
Визначає, чи шлях у
VAR
належить до ефективної групи поточного користувача. -N "$VAR"
-
Визначає, чи шлях у
VAR
був змінений після останнього доступу до нього. "$VAR1" -nt "$VAR2"
-
Визначає, чи шлях у
VAR1
є новішим за шлях уVAR2
, відповідно до їх дат модифікації. "$VAR1" - від "$VAR2"
-
Визначає, чи шлях у
VAR1
не старший заVAR2
. "$VAR1" -ef "$VAR2"
-
Цей вираз має значення True, якщо шлях у VAR1 є жорстким посиланням на VAR2.
Рекомендується використовувати подвійні лапки навколо змінної, яка перевіряється, тому що, якщо змінна порожня, це може спричинити синтаксичну помилку для команди test
. Параметри перевірки вимагають аргументу операнда, а порожня змінна без лапок спричинить помилку через відсутність необхідного аргументу. Існують також тести для довільних текстових змінних, описані таким чином:
-z "$TXT"
-
Визначає, чи змінна
TXT
порожня (нульовий розмір). -n "$TXT"
абоtest "$TXT"
-
Визначає, чи змінна
TXT
не порожня. "$TXT1" = "$TXT2"
або"$TXT1" == "$TXT2"
-
Визначає, чи рівні
TXT1
іTXT2
. "$TXT1" != "$TXT2"
-
Визначає, чи не рівні
TXT1
іTXT2
. "$TXT1" < "$TXT2"
-
Визначає, чи стоїть
TXT1
передTXT2
в алфавітному порядку. "$TXT1" > "$TXT2"
-
Визначає, чи стоїть
TXT1
післяTXT2
в алфавітному порядку.
Різні мови можуть мати різні правила алфавітного впорядкування. Щоб отримати узгоджені результати, незалежно від налаштувань локалізації системи, де виконується сценарій, рекомендується встановити змінну середовища LANG
на C
, як у LANG=C
, перед виконанням операцій, що включають алфавітний порядок. Це визначення також зберігатиме системні повідомлення мовою оригіналу, тому його слід використовувати лише в межах сценарію.
Числові порівняння мають власний набір параметрів перевірки:
$NUM1 -lt $NUM2
-
Визначає, чи значення
NUM1
менше заNUM2
. $NUM1 -gt $NUM2
-
Визначає, чи
NUM1
більше заNUM2
. $NUM1 -le $NUM2
-
Визначає, чи
NUM1
менше або дорівнюєNUM2
. $NUM1 -ge $NUM2
-
Визначає, чи
NUM1
більше або дорівнюєNUM2
. $NUM1 -eq $NUM2
-
Визначає, чи дорівнює
NUM1
NUM2
. $NUM1 -не $NUM2
-
Визначає, чи не дорівнює
NUM1
числуNUM2
.
Усі тести можуть отримати такі модифікатори:
! EXPR
-
Визначає, чи є вираз
EXPR
хибним. EXPR1 -a EXPR2
-
Визначає, чи істинні обидва значення:
EXPR1
іEXPR2
. EXPR1 -o EXPR2
-
Визначає, чи правдивий хоча б один із двох виразів.
Іншу умовну конструкцію, case
, можна розглядати як варіацію конструкції if. Інструкція case
виконає список наданих команд, якщо вказаний елемент, наприклад, вміст змінної, можна знайти в списку елементів, розділених каналами (вертикальна смуга |
) і закінчених символом )
. Наступний приклад сценарію показує, як можна використовувати конструкцію case
для вказівки відповідного формату створення пакунків програмного забезпечення для даного дистрибутива Linux:
#!/bin/bash DISTRO=$1 echo -n "Distribution $DISTRO uses " case "$DISTRO" in debian | ubuntu | mint) echo -n "the DEB" ;; centos | fedora | opensuse ) echo -n "the RPM" ;; *) echo -n "an unknown" ;; esac echo " package format."
Кожен список шаблонів і пов’язаних команд має закінчуватися символами ;;
, ;&
або ;;&
. Останній шаблон, позначений зірочкою, збігатиметься, якщо не було відповідності жодному іншому попередньому шаблону. Інструкція esac
(case назад) завершує конструкцію case
. Якщо припустити, що попередній приклад сценарію мав назву script.sh
і він виконується з opensuse
як першим аргументом, буде згенерована така вихідна інформація:
$ ./script.sh opensuse Distribution opensuse uses the RPM package format.
Tip
|
Bash має опцію під назвою |
Шуканий елемент і шаблони піддаються розширенню тильди, розширенню параметрів, заміні команд і арифметичному розширенню. Якщо шуканий елемент указано в лапках, їх буде видалено перед спробою зіставлення.
Цикли
Сценарії часто використовуються як інструмент для автоматизації завдань, що повторюються, виконуючи той самий набір команд, доки не буде перевірено критерій зупинки. Bash має три типи циклів — for
, until
і while
— дещо різні циклічні конструкції.
Конструкція for
проходить заданим списком елементів - зазвичай це список слів або будь-яких інших текстових сегментів, розділених пробілами - виконуючи той самий набір команд для кожного з цих елементів. Перед кожною ітерацією інструкція for
призначає поточний елемент змінній, яка потім може використовуватися вкладеними командами. Процес повторюється доки не залишиться елементів. Синтаксис конструкції for:
for VARNAME in LIST do COMMANDS done
VARNAME – це довільне ім’я змінної оболонки, а LIST – будь-яка послідовність розділених елементів. Актуальні розмежувальні символи, що розділяють елементи в списку, визначаються змінною середовища IFS
, якою за замовчуванням є символи пробіл, табуляція і новий рядок. Список команд, які потрібно виконати, розмежовано інструкціями do
і done
, тому команди можуть займати стільки рядків, скільки потрібно.
У наступному прикладі команда for
візьме кожен елемент із наданого списку — послідовності чисел — і призначить його змінній NUM
, по одному елементу за раз:
#!/bin/bash for NUM in 1 1 2 3 5 8 13 do echo -n "$NUM is " if [ $(( $NUM % 2 )) -ne 0 ] then echo "odd." else echo "even." fi done
У цьому прикладі вкладена конструкція if
використовується в поєднанні з арифметичним виразом, щоб визначити, чи є число в поточній змінній NUM
парним чи непарним. Якщо припустити, що попередній зразок сценарію мав назву script.sh
і він знаходиться в поточному каталозі, буде згенеровано такі вихідні дані:
$ ./script.sh 1 is odd. 1 is odd. 2 is even. 3 is odd. 5 is odd. 8 is even. 13 is odd.
Bash також підтримує альтернативний формат для конструкцій for
із нотацією подвійних круглих дужок. Ця нотація нагадує синтаксис інструкцій for
з мови програмування C і особливо корисна для роботи з масивами:
#!/bin/bash SEQ=( 1 1 2 3 5 8 13 ) for (( IDX = 0; IDX < ${#SEQ[*]}; IDX++ )) do echo -n "${SEQ[$IDX]} is " if [ $(( ${SEQ[$IDX]} % 2 )) -ne 0 ] then echo "odd." else echo "even." fi done
Цей зразок сценарію генеруватиме той самий результат, що й попередній приклад. Однак замість використання змінної NUM
для зберігання по одному елементу використовується змінна IDX
для відстеження поточного індексу масиву в порядку зростання, починаючи з 0 і постійно збільшуючи його, поки існує номер елементу в масиві SEQ
. Фактичний елемент отримується з його позиції в масиві за допомогою ${SEQ[$IDX]}
.
Так само конструкція until
виконує послідовність команд, поки тестова команда, як і сама команда test
, не закінчиться зі статусом 0 (успішно). Наприклад, ту саму структуру циклу з попереднього прикладу можна реалізувати за допомогою until
наступним чином:
#!/bin/bash SEQ=( 1 1 2 3 5 8 13 ) IDX=0 until [ $IDX -eq ${#SEQ[*]} ] do echo -n "${SEQ[$IDX]} is " if [ $(( ${SEQ[$IDX]} % 2 )) -ne 0 ] then echo "odd." else echo "even." fi IDX=$(( $IDX + 1 )) done
Конструкції з until
можуть вимагати більше інструкцій, ніж конструкції з for
, але вони можуть бути більш придатними для нечислових критеріїв зупинки, які надаються виразами test
або будь-якою іншою командою. Важливо включати дії, які забезпечують дійсні критерії зупинки, наприклад, збільшення змінної лічильника, інакше цикл може працювати нескінченно.
Інструкція while
подібна до інструкції until
, але while
продовжує повторювати набір команд, якщо тестова команда завершується зі статусом 0 (успішно). Таким чином, інструкція until [ $IDX -eq ${#SEQ[*]} ]
з попереднього прикладу еквівалентна while [ $IDX -lt ${#SEQ[*]} ]
, оскільки цикл має повторюватися, поки індекс масиву менший за загальну кількість елементів у масиві.
Більш докладний приклад
Уявіть собі, що користувач хоче періодично синхронізувати колекцію своїх файлів і каталогів з іншим запам’ятовуючим пристроєм, змонтованим у довільній точці монтування у файловій системі, і повнофункціональна система резервного копіювання вважається надмірною. Оскільки це дія, призначена для періодичного виконання, це хороший зразок для прикладу автоматизації за допомогою сценарію оболонки.
Завдання просте: синхронізувати кожен файл і каталог, що міститься у списку, від початкового каталогу, повідомленого як перший аргумент сценарію, до каталогу призначення, повідомленого як другий аргумент сценарію. Щоб було легше додавати або видаляти елементи зі списку, він зберігатиметься в окремому файлі ~/.sync.list
, по одному елементу на рядок:
$ cat ~/.sync.list Documents To do Work Family Album .config .ssh .bash_profile .vimrc
Файл містить суміш файлів і каталогів, деякі з них мають пробіли в іменах. Це придатний сценарій для вбудованої команди Bash mapfile
, яка аналізуватиме будь-який текстовий вміст і створюватиме з нього змінну масиву, розміщуючи кожен рядок як окремий елемент масиву. Файл сценарію матиме назву sync.sh
і міститиме такий сценарій:
#!/bin/bash set -ef # List of items to sync FILE=~/.sync.list # Origin directory FROM=$1 # Destination directory TO=$2 # Check if both directories are valid if [ ! -d "$FROM" -o ! -d "$TO" ] then echo Usage: echo "$0 <SOURCEDIR> <DESTDIR>" exit 1 fi # Create array from file mapfile -t LIST < $FILE # Sync items for (( IDX = 0; IDX < ${#LIST[*]}; IDX++ )) do echo -e "$FROM/${LIST[$IDX]} \u2192 $TO/${LIST[$IDX]}"; rsync -qa --delete "$FROM/${LIST[$IDX]}" "$TO"; done
Перша дія, яку виконує сценарій, це перевизначення двох параметрів оболонки за допомогою команди set
: параметр -e
негайно припинить виконання, якщо команда виходить із ненульовим статусом, а параметр -f
вимкне глоббінг імені файлу. Обидва варіанти можна скоротити як -ef
. Це не обов’язковий крок, але він допомагає зменшити ймовірність неочікуваної поведінки.
Фактичні прикладні інструкції файлу сценарію можна розділити на три частини:
-
Збирання і перевірка параметрів сценарію
Змінна
FILE
— це шлях до файлу, який містить список елементів, що потрібно скопіювати:~/.sync.list
. ЗмінніFROM
іTO
є початковим і кінцевим шляхами відповідно. Оскільки останні два параметри надає користувач, вони проходять простий перевірочний тест, який виконується конструкцієюif
: якщо будь-який із двох не є дійсним каталогом, оцінюється за допомогою тестування[ ! -d "$FROM" -o ! -d "$TO" ]
— цей сценарій покаже коротке довідкове повідомлення, а потім завершиться зі статусом виходу 1. -
Завантаження списку файлів і каталогів
Після визначення всіх параметрів за допомогою команди
mapfile -t LIST < $FILE
створюється масив, що містить список елементів, які потрібно скопіювати. Опція-t
mapfile
видалить кінцевий символ нового рядка з кожного рядка перед тим, як включити його до змінної масиву з назвоюLIST
. Вміст файлу, позначений змінноюFILE
—~/.sync.list
— читається за допомогою перенаправлення введення. -
Виконання копіювання та повідомлення користувача
Цикл
for
із використанням нотації подвійних дужок проходить через масив елементів, а зміннаIDX
відстежує приріст індексу. Командаecho
повідомлятиме користувача про кожен елемент, який копіюється. Екранований символ Юнікоду\u2192
для символу стрілки вправо присутній у вихідному повідомленні, тому необхідно використовувати параметр-e
командиecho
. Командаrsync
вибірково копіюватиме лише змінені частини файлу з джерела, тому її використання рекомендовано для таких завдань. Параметриrsync
-q
і-a
, зведені до-qa
, блокуватимуть повідомленняrsync
і активуватимуть режим архівування, у якому зберігаються всі властивості файлу. Параметр--delete
змуситьrsync
видалити елемент у цільовій системі, який більше не існує в джерелі, тому його слід використовувати обережно.
Якщо припустити, що всі елементи в списку існують у домашньому каталозі користувача carol
, /home/carol
, а цільовий каталог /media/carol/backup
вказує на підключений зовнішній пристрій зберігання даних, команда sync. sh /home/carol /media/carol/backup
згенерує такий результат:
$ sync.sh /home/carol /media/carol/backup /home/carol/Documents → /media/carol/backup/Documents /home/carol/"To do" → /media/carol/backup/"To do" /home/carol/Work → /media/carol/backup/Work /home/carol/"Family Album" → /media/carol/backup/"Family Album" /home/carol/.config → /media/carol/backup/.config /home/carol/.ssh → /media/carol/backup/.ssh /home/carol/.bash_profile → /media/carol/backup/.bash_profile /home/carol/.vimrc → /media/carol/backup/.vimrc
У прикладі також припускається, що сценарій виконується користувачем root або користувачем carol
, оскільки більшість файлів не зможуть прочитати інші користувачі. Якщо script.sh
не знаходиться в каталозі, зазначеному в змінній середовища PATH
, тоді його слід вказати з повним шляхом.
Вправи до посібника
-
Як можна використати команду
test
, щоб перевірити, чи шлях до файлу, що зберігається у зміннійFROM
, є новішим за шлях, який зберігається у зміннійTO
? -
Наступний сценарій має виводити числову послідовність від 0 до 9, але замість цього він необмежено виводить 0. Що потрібно зробити, щоб отримати очікуваний результат?
#!/bin/bash COUNTER=0 while [ $COUNTER -lt 10 ] do echo $COUNTER done
-
Припустимо, що користувач написав сценарій, який потрібен для сортування списку імен користувачів. Отриманий відсортований список представлений на його комп’ютері таким чином:
carol Dave emma Frank Grace henry
Однак цей же список на комп’ютері його колеги сортується так:
Dave Frank Grace carol emma henry
Чим можна пояснити відмінності між двома відсортованими списками?
Дослідницькі вправи
-
Як можна використати всі аргументи командного рядка сценарію для ініціалізації масиву Bash?
-
Чому інтуїтивно зрозуміло, що команда
test 1 > 2
оцінюється як true? -
Як користувач тимчасово може змінити роздільник полів за замовчуванням лише на символ нового рядка, маючи при цьому можливість повернути його до вихідного значення?
Підсумки
- У цьому уроці глибше розглядаються тести, доступні для команди
test
, а також інші умовні та циклічні конструкції, необхідні для написання більш складних сценаріїв оболонки. Простий сценарій синхронізації файлів наведено як приклад практичного застосування сценарію оболонки. Урок розглядає наступні питання -
-
Розширені тести для умовних конструкцій
if
іcase
. -
Циклічні конструкції оболонки:
for
,until
іwhile
. -
Ітерацію масивів і параметрів.
-
Розглянуті команди та процедури:
test
-
Виконує порівняння між елементами, наданими командою.
if
-
Логічна конструкція, яка використовується в сценаріях для оцінки чогось як істинного чи хибного, а потім виконання команди розгалуження на основі результатів.
case
-
оцінка кількох значень щодо однієї змінної. Виконання команди скрипта потім виконується в залежності від результату команди
case
. for
-
повторне виконання команди на основі заданих критеріїв.
until
-
повторне виконання команди, доки вираз не отримає значення false.
while
-
повторне виконання команди, поки даний вираз оцінюється як істинний.
Відповіді до вправ посібника
-
Як можна використати команду
test
, щоб перевірити, чи шлях до файлу, що зберігається у зміннійFROM
, є новішим за шлях, який зберігається у зміннійTO
?Команда
test "$FROM" -nt "$TO"
поверне код статусу 0, якщо файл у зміннійFROM
є новішим за файл у зміннійTO
. -
Наступний сценарій має виводити числову послідовність від 0 до 9, але замість цього він необмежено виводить 0. Що потрібно зробити, щоб отримати очікуваний результат?
#!/bin/bash COUNTER=0 while [ $COUNTER -lt 10 ] do echo $COUNTER done
Змінну
COUNTER
слід збільшити, що можна зробити за допомогою арифметичного виразуCOUNTER=$(( $COUNTER + 1 ))
, щоб зрештою досягти критеріїв зупинки та завершити цикл. -
Припустимо, що користувач написав сценарій, якому потрібен відсортований список імен користувачів. Отриманий відсортований список представлений на його комп’ютері таким чином:
carol Dave emma Frank Grace henry
Однак цей же список на комп’ютері його колеги сортується так:
Dave Frank Grace carol emma henry
Чим можна пояснити відмінності між двома відсортованими списками?
Сортування базується на поточній локалі системи. Щоб уникнути невідповідностей, завдання сортування слід виконувати зі змінною середовища
LANG
, встановленою наC
.
Відповіді до дослідницьких вправ
-
Як можна використати всі аргументи командного рядка сценарію для ініціалізації масиву Bash?
Команди
PARAMS=( $* )
абоPARAMS=( "$@" )
створять масив під назвоюPARAMS
з усіма аргументами. -
Чому інтуїтивно зрозуміло, що команда
test 1 > 2
оцінюється як true?Оператор
>
призначений для використання з тестами рядків, без числових тестів. -
Як користувач тимчасово може змінити роздільник полів за замовчуванням лише на символ нового рядка, маючи при цьому можливість повернути його до вихідного значення?
Копію змінної
IFS
можна зберегти в іншій змінній:OLDIFS=$IFS
. Тоді новий роздільник рядків визначається за допомогоюIFS=$'\n'
, а змінну IFS можна повернути назад за допомогоюIFS=$OLDIFS
.