Одну пораду програмісти-початківці, які пишуть на bash, будуть чути знову і знову:
Беріть свої змінні в лапки!
Чому так?
Коли ми розкриваємо змінну (наприклад, $name), майже завжди ми хочемо використати значення змінної як один рядок тексту (англ. string) (або число). Ми не хочемо, щоб це значення несподівано обробили як два або більше окремих рядків тексту.
Лапки - це основний механізм, який гарантує, що частину тексту буде оброблено як єдине слово. Є два основних типи лапок.
Подвійні лапки дозволяють усередині себе розкриття параметрів, арифметичне розкриття та підстановку команд.
echo "This is bash version $BASH_VERSION"
Усередину рядка тексту в подвійних лапках можна вставити буквальні подвійні лапки, поставивши перед ними зворотний слеш.
echo "She said \"Let me teach you about bash.\""
Одинарні лапки не дозволяють жодних розкриттів усередині себе.
echo 'This is bash version $BASH_VERSION'
# ...^..................................^
# prints exactly: This is bash version $BASH_VERSION
Вставити одинарні лапки всередину рядка тексту в одинарних лапках неможливо.
Що ж саме не так із echo $name?
Коли оболонка розбирає рядок коду, вона послідовно виконує кілька видів розкриття. Два з останніх розкриттів - це розбиття на слова та розкриття імен файлів.
Будь-які результати розкриття параметрів, підстановки команд або арифметичного розкриття, які виникли не всередині подвійних лапок, можуть зазнати «розбиття на слова».
Оболонка бере розкритий результат і розбиває його за будь-якою послідовністю символів, наявних у значенні $IFS.
Типово це пробіл, табуляція та символ нового рядка.
Побачимо це в дії за допомогою циклу оболонки:
sentence="The quick brown fox jumps."
i=0
for word in "$sentence"; do echo "$((++i)) $word"; done
# ..........^.........^
виводить
1 The quick brown fox jumps.
А ось без лапок:
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
виводить
1 The
2 quick
3 brown
4 fox
5 jumps.
Погляньмо на це з іншим значенням $IFS:
csv="first,second,third"
IFS=","
for word in $csv; do echo "$word"; done
виводить
first
second
third
Щоб вимкнути розбиття на слова, можна встановити $IFS у порожній рядок тексту
sentence="The quick brown fox jumps."
IFS= # or explicitly, IFS=""
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
виводить
1 The quick brown fox jumps.
Однак залишати змінні без лапок не рекомендується, якщо тільки не вимкнути також розкриття імен файлів. Як це зробити, побачимо нижче.
«Glob»-шаблони використовуються, щоб стисло задати зразок для зіставлення з набором файлів.
Наприклад, усі файли Markdown у каталозі можна зіставити з *.md.
Glob-шаблони існують від самого винаходу Unix.
Символи-замінники glob:
* відповідає нулю або більше будь-яких символів,
? відповідає рівно одному будь-якому символу,
[...] відповідає рівно одному символу з набору символів, перелічених усередині дужок.
[abc] відповідає рівно одному символу, що є або a, або b, або c.
^)
[][] - це набір символів, який відповідає або закривній, або відкривній дужці.[[\]]
[x-y] відповідає рівно одному символу з діапазону від x до y
^), або останнім символом перед закривною дужкою.
[-xy] або [xy-] кожен відповідає рівно одному символу, що є -, x або y
[x\-y]
[0-9A-Fa-f] відповідає шістнадцятковій цифрі[^abc] відповідає рівно одному символу, який НЕ є a, b або c,[^x-y] відповідає рівно одному символу, який НЕ належить діапазону від x до y
[[:character_class:]] відповідає одному символу з названого «character_class»
lower -- малі літериupper -- великі літериalpha -- літериdigit -- цифриalnum -- літери та цифриspace -- пробільні символиblank -- горизонтальні пробільні символи (пробіл і табуляція)xdigit -- шістнадцяткові цифриword -- символи, дозволені в ідентифікаторі (літери, цифри, підкреслення)punct -- розділові знакиcntrl -- керуючі символиgraph -- «видимі» символиprint -- видимі символи плюс пробіл[[:alpha:]] складається з [[:lower:][:upper:]]
[[:alnum:]] складається з [[:alpha:][:digit:]]
[[:word:]] складається з [[:alnum:]_]
[[:word:]], і до [[:punct:]]
[[:graph:]] складається з [[:alnum:][:punct:]]
[[:print:]] складається з [[:graph:] ] -- лише пробіл (вісімкове число 040), а не будь-які інші пробільні символи.Список файлів можна перебрати циклом for
for file in *.csv; do
do_something_with "$file"
done
Зауважмо, що змінна "$file" узята в лапки.
Шаблон glob може повернути файли з пробілами в назві.
Нам потрібно взяти змінну в лапки, щоб команда do_something_with отримала рівно один аргумент, а саме імʼя файлу.
Glob-шаблони не є регулярними виразами.
.
?
.*
*
.* означає «зіставити імʼя файлу, у якому перший символ - буквальна крапка, за якою йде нуль або більше будь-яких символів»..+
?*
Bash надає розширені шаблони, які ближчі до регулярних виразів.
Іноді потрібно придушити розкриття імен файлів. Один із таких випадків - обробка введення користувача. Розгляньмо цей фрагмент коду
read -p "Enter something: " user_input
echo $user_input
Якщо користувач введе зірочку (*), що буде виведено?
Список файлів у поточному каталозі.
Імовірно, це не те, що ми хочемо зробити.
Ще один приклад - передавати шаблони як аргументи програмам, які приймають шаблони.
Програму tr часто використовують для роботи з текстом. Щоб записати значення змінної в нижньому регістрі, можна зробити так
var="My Puppet"
echo "$var" | tr '[:upper:]' '[:lower:]'
виводить
my puppet
Поширена помилка - забути взяти аргументи tr у лапки: tr [:upper:] [:lower:]
Чому це проблема?
Шаблони glob без лапок розкриються у список файлів, які відповідають шаблону.
Припустімо, у людини, яка користується нашим скриптом, у поточному каталозі є файли з назвами p, u і w:
touch p u w
var="My Puppet"
echo "$var" | tr [:upper:] [:lower:]
Це призводить до помилки!
Шаблон [:upper:] без лапок відповідає будь-якому з символів :, u, p, e або r.
Шаблон [:lower:] без лапок відповідає будь-якому з символів :, l, o, w, e або r.
Bash зіставляє файли p та u з першим шаблоном і файл w з другим, тож tr викликається так
echo "$var" | tr p u w
А це неправильна кількість аргументів для tr.
Щоб вимкнути розкриття імен файлів, можна скористатися командою set.
echo * # prints a list of files
set -f # disable filename expansion
echo * # prints a literal asterisk
set +f # enable filename expansion
У вбудованої команди shopt є кілька налаштувань, які керують тим, як працює розкриття імен файлів.
Кілька цікавих із них:
shopt -s nocaseglob -- зіставляти без урахування регіструshopt -s extglob -- увімкнути розширені шаблониshopt -s nullglob -- якщо жоден файл не збігається, замінити шаблон на порожнечу.
Типова поведінка - залишити шаблон на місці як буквальний рядок тексту.