3.3 Урок 2
Сертифікат: |
Linux Essentials |
---|---|
Версія: |
1.6 |
Розділ: |
3 Потужність командного рядку |
Тема: |
3.3 Перетворення команд у скрипт |
Урок: |
2 з 2 |
Вступ
У останньому розділі ми використали цей простий приклад, щоб продемонструвати сценарії Bash:
#!/bin/bash # Простий скрипт для привітання одного користувача. if [ $# -eq 1 ] then username=$1 echo "Hello $username!" else echo "Please enter only one argument." fi echo "Number of arguments: $#."
-
Усі сценарії мають починатися з shebang, який визначає шлях до інтерпретатора.
-
Усі сценарії мають містити коментарі для опису їх використання.
-
Цей конкретний сценарій працює з аргументом, який передається сценарію під час його виклику.
-
Цей сценарій містить оператор if, який перевіряє умови вбудованої змінної
$#
. Ця змінна встановлюється на кількість аргументів. -
Якщо кількість аргументів, переданих сценарію, дорівнює 1, тоді значення першого аргументу передається до нової змінної під назвою
username
, і сценарій друкує привітання користувачеві. В іншому випадку з’явиться повідомлення про помилку. -
Нарешті, сценарій друкує кількість аргументів. Це корисно для виправлення помилок.
Це корисний приклад для початку пояснення деяких інших функцій сценаріїв Bash.
Коди виходу
Ви помітите, що наш сценарій має два можливі стани: він друкує "Hello <user>!"
або друкує повідомлення про помилку. Це цілком нормально для багатьох наших основних утиліт. Подумайте про команду cat
, з якою ви вже знайомі.
Давайте порівняємо успішне використання cat
із ситуацією, коли вона зазнає невдачі. Нагадуємо, що наш приклад вище — це сценарій під назвою new_script.sh
.
$ cat -n new_script.sh 1 #!/bin/bash 2 3 # A simple script to greet a single user. 4 5 if [ $# -eq 1 ] 6 then 7 username=$1 8 9 echo "Hello $username!" 10 else 11 echo "Please enter only one argument." 12 fi 13 echo "Number of arguments: $#."
Ця команда виконується успішно, і ви помітите, що прапор -n
також надрукував номери рядків. Вони дуже корисні під час виправлення помилок у сценаріях, але зверніть увагу, що вони не є частиною сценарію.
Тепер ми збираємося перевірити значення нової вбудованої змінної $?
. Наразі просто зверніть увагу на виведення:
$ echo $? 0
Тепер давайте розглянемо ситуацію, коли cat
зазнає невдачі. Спочатку ми побачимо повідомлення про помилку, а потім перевіримо значення $?
.
$ cat -n dummyfile.sh cat: dummyfile.sh: No such file or directory $ echo $? 1
Пояснення такої поведінки наступне: будь-яке виконання утиліти cat
поверне код виходу. Код виходу повідомить нам, чи була команда успішною, чи сталася помилка. Код виходу нуль вказує, що команда виконана успішно. Це справедливо майже для кожної команди Linux, з якою ви працюєте. Будь-який інший код виходу буде вказувати на якусь помилку. Код виходу останньої команди, яка виконувалася буде збережено у змінній $?
.
Коди виходу зазвичай не бачать користувачі, але вони дуже корисні під час написання сценаріїв. Розглянемо сценарій, у якому ми можемо копіювати файли на віддалений мережевий диск. Існує багато причин, через які завдання копіювання могло бути невдалим: наприклад, наша локальна машина може бути не підключена до мережі, або віддалений диск може бути заповнений. Перевіривши код виходу нашої утиліти копіювання, ми можемо попередити користувача про проблеми під час запуску сценарію.
Обробляти коди виходу – це дуже хороша практика, тому ми зробимо це саме зараз. У нашому сценарії є два шляхи: успіх і невдача. Давайте використаємо нуль, щоб позначити успіх, і одиницю, щоб позначити невдачу.
1 #!/bin/bash 2 3 # Простий скрипт для привітання окремого користувача. 4 5 if [ $# -eq 1 ] 6 then 7 username=$1 8 9 echo "Hello $username!" 10 exit 0 11 else 12 echo "Please enter only one argument." 13 exit 1 14 fi 15 echo "Number of arguments: $#."
$ ./new_script.sh Carol Hello Carol! $ echo $? 0
Зверніть увагу, що команда echo
у рядку 15 була повністю проігнорована. Використання exit
негайно завершить скрипт, тому цей рядок ніколи не буде виконуватися.
Обробка багатьох аргументів
Поки що наш скрипт може обробляти лише одне ім’я користувача за раз. Будь-яка кількість аргументів, відмінна від одного, призведе до помилки. Давайте дослідимо, як зробити цей сценарій більш універсальним.
Перше, що підказує інстинкт користувача може полягати в тому, щоб використовувати більше позиційних змінних, таких як $2
, $3
тощо. На жаль, ми не можемо передбачити кількість аргументів, які може використати користувач. Щоб вирішити цю проблему, буде корисно додати більше вбудованих змінних.
Ми змінимо логіку нашого сценарію. Відсутність аргументів може призвести до помилки, але будь-яка інша кількість аргументів має бути успішною. Цей новий сценарій буде мати назву "friendly2.sh".
1 #!/bin/bash 2 3 # скрипт для привітання користувачів 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 echo "Hello $@!" 11 exit 0 12 fi
$ ./friendly2.sh Carol Dave Henry Hello Carol Dave Henry!
Існує дві вбудовані змінні, які містять усі аргументи, передані скрипту: $@
та $*
. Здебільшого обидві поводяться однаково. Bash буде розбирати аргументи та відокремлювати кожен аргумент, коли між ними зустрічається пробіл. Фактично вміст $@
виглядає так:
|
|
|
|
|
|
Якщо ви знайомі з іншими мовами програмування, ви можете розпізнати цей тип змінної як масив. Масиви в Bash можна створити, просто поставивши пробіл між елементами,иак як в змінній FILES
у скрипті arraytest
нижче:
FILES="/usr/sbin/accept /usr/sbin/pwck/ usr/sbin/chroot"
Ця змінна містить перелік кількох елементів. Поки що це не дуже корисно, оскільки ми ще не запровадили жодного способу обробки цих елементів, кожного окремо.
Цикли For
Давайте звернемося до прикладу скрипта arraytest
, показаного раніше. Якщо ви пам’ятаєте, у цьому прикладі ми вказуємо власний масив під назвою FILES
. Нам потрібен спосіб “розпакувати” цю змінну та отримати доступ до кожного окремого значення одне за одним. Для цього ми будемо використовувати структуру під назвою цикл for, яка присутня у всіх мовах програмування. Ми будемо посилатися на дві змінні: одна — це діапазон, а інша — для окремого значення, над яким ми зараз працюємо. Ось весь сценарій:
#!/bin/bash FILES="/usr/sbin/accept /usr/sbin/pwck/ usr/sbin/chroot" for file in $FILES do ls -lh $file done
$ ./arraytest lrwxrwxrwx 1 root root 10 Apr 24 11:02 /usr/sbin/accept -> cupsaccept -rwxr-xr-x 1 root root 54K Mar 22 14:32 /usr/sbin/pwck -rwxr-xr-x 1 root root 43K Jan 14 07:17 /usr/sbin/chroot
Якщо ви знову звернетеся до прикладу friendly2.sh вище, ви побачите, що ми працюємо з діапазоном значень, які містяться в одній змінній $@
. Для наочності ми будемо називати останню змінну username
. Тепер наш сценарій виглядає так:
1 #!/bin/bash 2 3 # скрипт для привітання користувачів 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 for username in $@ 11 do 12 echo "Hello $username!" 13 done 14 exit 0 15 fi
Пам’ятайте, що змінну, яку ви тут визначаєте, можна назвати як завгодно, і що всі рядки всередині do… done
будуть виконуватися один раз для кожного елемента масиву. Давайте розглянемо результати нашого скрипту:
$ ./friendly2.sh Carol Dave Henry Hello Carol! Hello Dave! Hello Henry!
Тепер припустимо, що ми хочемо, щоб наші результати виглядали зручніше для користувача. Ми хочемо, щоб наше привітання було в одному рядку.
1 #!/bin/bash 2 3 # скрипт для привітання користувачів 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 echo -n "Hello $1" 11 shift 12 for username in $@ 13 do 14 echo -n ", and $username" 15 done 16 echo "!" 17 exit 0 18 fi
Пара приміток:
-
Використання
-n
зecho
відмінить новий рядок після друку. Це означає, що всі виведення командиecho
друкуватимуться в одному рядку, а новий рядок буде надруковано лише після!`
у рядку 16. -
Команда
shift
видалить перший елемент нашого масиву, тож наші дані:
|
|
|
|
|
|
перетворяться на такі:
|
|
|
|
Давайте розглянемо отриманий результат:
$ ./friendly2.sh Carol Hello Carol! $ ./friendly2.sh Carol Dave Henry Hello Carol, and Dave, and Henry!
Використання регулярних виразів для перевірки помилок
Можливо, ми хочемо перевірити всі аргументи, які вводить користувач. Наприклад, ми хочемо переконатися, що всі імена, передані до friendly2.sh
, містять лише літери, а присутність в них будь-яких спеціальних символів або цифр призведе до помилки. Щоб виконати таку перевірку помилок, ми будемо використовувати grep
.
Нагадаємо, що ми можемо використовувати регулярні вирази з grep
.
$ echo Animal | grep "^[A-Za-z]*$" Animal $ echo $? 0
$ echo 4n1ml | grep "^[A-Za-z]*$" $ echo $? 1
^
і $
вказують на початок і кінець рядка відповідно. [A-Za-z]
вказує на діапазон літер верхнього або нижнього регістру. Значок *
є квантифікатором, який відповідає нашому діапазону літер, щоб ми зіставляли його з шаблоном, в якому літер від нуля до будь-якої кількості. У підсумку наш grep
буде успішним, якщо були введені лише літери, і результат буде хибним.
Наступне, на що слід звернути увагу, це те, що grep
повертає коди виходу на основі того, чи була відповідність чи ні. Позитивний збіг повертає 0
, а невідповідність повертає 1
. Ми можемо використовувати цей підхід, щоб перевірити аргументи всередині нашого сценарію.
1 #!/bin/bash 2 3 # скрипт для привітання користувачів 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 for username in $@ 11 do 12 echo $username | grep "^[A-Za-z]*$" > /dev/null 13 if [ $? -eq 1 ] 14 then 15 echo "ERROR: Names must only contains letters." 16 exit 2 17 else 18 echo "Hello $username!" 19 fi 20 done 21 exit 0 22 fi
У рядку 12 ми перенаправляємо стандартне виведення на /dev/null
, що є простим способом його приховати. Ми не хочемо бачити жодного результату команди grep
, ми хочемо лише перевірити її код виходу, що відбувається в рядку 13. Зверніть також увагу, що ми використовуємо код виходу 2
для зазначення недійсного аргументу. Як правило, гарною практикою є використання різних кодів виходу для позначення різних помилок; таким чином, досвідчений користувач може використовувати ці коди виходу для усвідомлення та усунення несправностей.
$ ./friendly2.sh Carol Dave Henry Hello Carol! Hello Dave! Hello Henry! $ ./friendly2.sh 42 Carol Dave Henry ERROR: Names must only contains letters. $ echo $? 2
Вправи до посібника
-
Прочитайте вміст
script1.sh
нижче:#!/bin/bash if [ $# -lt 1 ] then echo "This script requires at least 1 argument." exit 1 fi echo $1 | grep "^[A-Z]*$" > /dev/null if [ $? -ne 0 ] then echo "no cake for you!" exit 2 fi echo "here's your cake!" exit 0
Яким буде результат наступних команд?
-
./script1.sh
-
echo $?
-
./script1.sh cake
-
echo $?
-
./script1.sh CAKE
-
echo $?
-
-
Прочитайте вміст файлу
script2.sh
:for filename in $1/*.txt do cp $filename $filename.bak done
Опишіть мету цього сценарію, як ви її розумієте.
Дослідницькі вправи
-
Створіть сценарій, який прийматиме будь-яку кількість аргументів від користувача та друкуватиме лише ті аргументи, які є числами, більшими за 10.
Підсумки
У цьому уроці ви дізналися:
-
Що таке коди виходу, що вони означають і як їх використовувати.
-
Як перевірити код виходу команди.
-
Що таке цикли
for
і як їх використовувати для роботи з масивами. -
Як використовувати
grep
, регулярні вирази та коди виходу для перевірки даних, введених користувачами до скриптів.
Команди, які використовуються у вправах:
shift
-
Видалить перший елемент масиву.
Спеціальні змінні:
$?
-
Містить код завершення останньої виконаної команди.
$@
,$*
-
Містить усі аргументи, передані сценарію, у вигляді масиву.
Відповіді до вправ посібника
-
Прочитайте вміст
script1.sh
нижче:#!/bin/bash if [ $# -lt 1 ] then echo "This script requires at least 1 argument." exit 1 fi echo $1 | grep "^[A-Z]*$" > /dev/null if [ $? -ne 0 ] then echo "no cake for you!" exit 2 fi echo "here's your cake!" exit 0
Яким буде результат наступних команд?
-
Command:
./script1.sh
Output:
This script requires at least 1 argument.
-
Command:
echo $?
Output:
1
-
Command:
./script1.sh cake
Output:
no cake for you!
-
Command:
echo $?
Output:
2
-
Command:
./script1.sh CAKE
Output:
here’s your cake!
-
Command:
echo $?
Output:
0
-
-
Прочитайте вміст файлу
script2.sh
:for filename in $1/*.txt do cp $filename $filename.bak done
Опишіть мету цього сценарію, як ви її розумієте.
Цей сценарій створить резервні копії всіх файлів, що закінчуються на ".txt", у підкаталозі, визначеному в першому аргументі.
Відповіді до дослідницьких вправ
-
Створіть сценарій, який прийматиме будь-яку кількість аргументів від користувача та друкуватиме лише ті аргументи, які є числами, більшими за 10.
#!/bin/bash for i in $@ do echo $i | grep "^[0-9]*$" > /dev/null if [ $? -eq 0 ] then if [ $i -gt 10 ] then echo -n "$i " fi fi done echo ""