Треки
/
Bash
Bash
/
Салабус
/
Важливість лапок
Ва

Важливість лапок у Bash

{one: "1 вправа", few: "%{count} вправи", many: "%{count} вправ", other: "%{count} вправи"}

Про концепцію Важливість лапок

Одну пораду програмісти-початківці, які пишуть на 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:

  • * відповідає нулю або більше будь-яких символів,

  • ? відповідає рівно одному будь-якому символу,

  • [...] відповідає рівно одному символу з набору символів, перелічених усередині дужок.

    Advanced
    Тут багато подробиць про набори символів. Натисніть, щоб показати їх.
    • [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:]_]
        • підкреслення (вісімкове число 137) належить і до [[: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-шаблони - це не регулярні вирази

Glob-шаблони не є регулярними виразами.

  • відповідає рівно одному символу
    • регулярний вираз .
    • glob ?
  • відповідає нулю або більше символів
    • регулярний вираз .*
    • glob *
      • шаблон glob .* означає «зіставити імʼя файлу, у якому перший символ - буквальна крапка, за якою йде нуль або більше будь-яких символів».
  • відповідає одному або більше символів
    • регулярний вираз .+
    • glob ?*
  • набори символів у регулярних виразах і в glob поводяться однаково.
Advanced

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:]

Caution

Чому це проблема? Шаблони 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 -- якщо жоден файл не збігається, замінити шаблон на порожнечу. Типова поведінка - залишити шаблон на місці як буквальний рядок тексту.
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці