Работа с оператором case в Bash: понятные примеры и лучшие практики

Быстрые ссылки
The case Statement
The Generic case
A Simple Example
Using Multiple Patterns in a Clause
Using Digits In case Statements
Using case Statements in for Loops
Handling Exit Codes With case Statements
Legibility Helps Maintainability
Краткое содержание
- Оператор case в Bash делает скрипты читабельнее и проще для сопровождения по сравнению с длинными if-then-else.
- case сравнивает выражение с наборами шаблонов в клаузаx и выполняет соответствующие команды.
- В одном блоке можно перечислить несколько шаблонов, что сокращает и упрощает код. Числовые строки и символы также можно использовать в шаблонах.
Оператор case — простой и мощный инструмент. При возврате к старым скриптам вы оцените его ясность.
The case Statement
Большинство языков имеют аналог switch/case. Такой оператор направляет поток выполнения в зависимости от значения переменной. Для ожидаемых значений обычно задают отдельные ветки и одну ветку по умолчанию для остальных случаев.
Логически это похоже на длинную последовательность if-then с концовкой else. В Bash case пытается сопоставить выражение с одним из шаблонов, просматривая каждую клауза последовательно. Шаблоны — это строки, поэтому даже числовые значения можно сравнивать как строки.
Важно: в шаблонах можно использовать подстановочные символы Bash (globbing), например *, ?, [], и альтернативы через |.
The Generic case
Общий вид оператора case:
case expression in
pattern-1)
statement
;;
pattern-2)
statement
;;
.
.
.
pattern-N)
statement
;;
*)
statement
;;
esacКлючевые моменты:
- case начинается с ключевого слова case и завершается esac.
- Выражение сравнивается с шаблонами до нахождения совпадения.
- Двойной точка с запятой (;; ) завершает клаузу.
- Asterisk (*) — шаблон по умолчанию.
- Нет ограничений по числу клауз.
A Simple Example
Простой скрипт сообщает часы работы вымышленного магазина. Команда date с форматом +”%a” возвращает сокращённое имя дня недели и сохраняет его в переменной DayName.
#!/bin/bash
DayName=$(date +"%a")
echo "Opening hours for $DayName"
case $DayName in
Mon)
echo "09:00 - 17:30"
;;
Tue)
echo "09:00 - 17:30"
;;
Wed)
echo "09:00 - 12:30"
;;
Thu)
echo "09:00 - 17:30"
;;
Fri)
echo "09:00 - 16:00"
;;
Sat)
echo "09:30 - 16:00"
;;
Sun)
echo "Closed all day"
;;
*)
;;
esacСохраните как open.sh и сделайте исполняемым:
chmod +x open.shЗапуск:
./open.shВ приведённом примере день недели совпадёт с одной из клауз, например Fri, и выполнится соответствующая ветка. Шаблоны не обязательно брать в кавычки, но кавычки нужны, если в шаблоне есть пробелы.
Important: Пустая клауза по умолчанию игнорирует неподходящие варианты. Если требуется обработка ошибок — добавьте логирование или exit с кодом.
Этот вариант рабочий, но можно сделать короче и выразительнее.
Using Multiple Patterns in a Clause
Преимущество case — возможность указывать несколько альтернатив в одной клаузе через | . Это уменьшает дублирование.
Пример: определение количества дней в месяце. Всего три варианта: 28/29, 30, 31.
#!/bin/bash
shopt -s nocasematch
echo "Enter name of a month"
read month
case $month in
February)
echo "28/29 days in $month"
;;
April | June | September | November)
echo "30 days in $month"
;;
January | March | May | July | August | October | December)
echo "31 days in $month"
;;
*)
echo "Unknown month: $month"
;;
esacСохраняем в month.sh:
chmod +x month.shЗапустите и протестируйте разные варианты ввода:
./month.sh
shopt -s nocasematch делает сопоставление нечувствительным к регистру. Хорошая практика для пользовательского ввода.
Using Digits In case Statements
Шаблоны — строки, но вы можете использовать числовые значения как строки. Часто это полезно при простом меню или выборе опции.
#!/bin/bash
echo "Enter 1, 2, or 3: "
read Number
case $Number in
"1")
echo "Clause 1 matched"
;;
"2")
echo "Clause 2 matched"
;;
"3")
echo "Clause 3 matched"
;;
*)
echo "Default clause matched"
;;
esac./number.sh
Using case Statements in for Loops
Если нужно обработать множество выражений, поместите case внутрь цикла. Пример: классификация файлов по расширению.
#!/bin/bash
for File in $(ls)
do
# извлечь расширение файла
Extension=${File##*.}
case "$Extension" in
sh)
echo " Shell script: $File"
;;
md)
echo " Markdown file: $File"
;;
png)
echo "PNG image file: $File"
;;
*)
echo "Unknown: $File"
;;
esac
doneСохраните как filetype.sh, сделайте исполняемым и запустите:
./filetype.sh
Этот шаблон полезен для пакетной обработки файлов, генерации отчётов и применения разных обработчиков в зависимости от типа.
Handling Exit Codes With case Statements
Программы возвращают код выхода: 0 — успех, >0 — ошибки (конвенция). case удобно использовать для обработки кодов выхода, особенно когда программа возвращает несколько разных кодов ошибок.
Пример с гипотетической программой go-geek, которая возвращает 0 или 1.
#!/bin/bash
go-geek
case $? in
"0")
echo "Response was: Success"
echo "Do appropriate processing in here"
;;
"1")
echo "Response was: Error"
echo "Do appropriate error handling in here"
;;
*)
echo "Unrecognised response: $?"
;;
esacСохраните в return-code.sh и сделайте исполняемым. Для тестов замените go-geek на команду, возвращающую нужный код, например попытку cd в несуществующую папку.
./return-code.sh
Legibility Helps Maintainability
Понимание чужого скрипта сложнее, чем написать свой. Простая и чистая логика ветвления помогает экономить время при сопровождении. case обеспечивает компактность и ясность, особенно для множественных путей исполнения.
Когда case не лучший выбор
- Когда ветвление зависит от логических выражений или диапазонов (например, x > 5 и x <= 10). Тогда if/elif более естественен.
- Когда нужна вложенная или комбинированная логика с множеством условий на одной ветке — лучше использовать функции и явные проверки.
- Для производительных обработок больших объёмов данных в tight loops лучше избегать spawning subshells и лишних команд внутри цикла.
Альтернатива: конструкция if/elif/else — более гибкая для числовых сравнения и сложных логических условий.
Практические советы и хитрости
- Используйте shopt -s nocasematch для нечувствительного к регистру сопоставления.
- Экранируйте или берите в кавычки переменные с пробелами: case “$var” in …
- Для шаблонов с подстановками используйте glob: “*.sh”) … ;; — это удобно для обработки файлов.
- Будьте осторожны при использовании $(ls) в for; лучше использовать for File in *; do … чтобы избежать проблем с пробелами и спецсимволами в именах файлов.
- Логируйте ветку по умолчанию и возвращайте ненулевой код выхода, если это ошибка.
Частые шаблоны (cheat sheet)
- Обработка расширений файлов:
case "$file" in
*.sh) echo "script" ;;
*.py) echo "python" ;;
*) echo "other" ;;
esac- Простое меню:
case $opt in
1) do_something ;;
2) do_something_else ;;
q|Q) echo "quit"; exit 0 ;;
*) echo "unknown" ;;
esac- Диапазоны через шаблоны (например, одна цифра):
case $d in
[0-9]) echo "single digit" ;;
[1-9][0-9]) echo "two digits" ;;
*) echo "other" ;;
esacМентальные модели и эвристики
- Визуализируйте case как таблицу соответствия: входное значение → шаблон → действие.
- Если вы видите повторяющийся echo/лог в нескольких ветках — вынесите в функцию.
- Для поддерживаемости держите одну ответственность в скрипте: обработка аргументов, основная логика и вывод состояния — разделите по функциям.
Mermaid-диаграмма принятия решения:
flowchart TD
A[Входное значение] --> B{Совпадает с шаблоном?}
B -- Да --> C[Выполнить клауза]
B -- Нет --> D{Есть ещё шаблоны?}
D -- Да --> B
D -- Нет --> E[Выполнить default]Checklist для ролей
Разработчик:
- Использовать кавычки вокруг переменных с возможными пробелами
- Добавить обработку default с логированием
- Вынести повторяющиеся действия в функции
- Обработать ошибки и вернуть корректный код выхода
Оператор / DevOps:
- Проверить влияние на существующие cron‑job’ы
- Убедиться, что скрипт исполняем и имеет корректные права
- Тестировать с различными локалями и переводами дат
Рецензент кода:
- Нет ли возможности сократить число веток?
- Нет ли потенциальных проблем с пробелами в именах файлов?
- Есть ли unit/integration тесты для основных веток?
Критерии приёмки
- Скрипт корректно обрабатывает все ожидаемые значения и имеет покрытие для default ветки.
- Нет утечек оболочки: переменные корректно кавычатся, отсутствуют непредвиденные команды.
- Возвращаемые коды: 0 при успехе, >0 при ошибке.
Сравнение с альтернативами
- case vs if/elif: case короче и понятнее для множества конкретных значений; if/elif гибче для диапазонов и логических выражений.
- case vs external dispatcher (Python/Perl): Bash case проще и не требует внешних зависимостей для простых задач; для сложной логики целесообразно использовать полноценный язык.
Безопасность и устойчивость
- Избегайте использования $(ls) в конструкциях for — это ломается при пробелах в именах. Предпочтительней: for File in *; do …
- Не выполняйте непроверенный ввод как команду через eval.
- Для работы с путями используйте полные пути или проверяйте текущую рабочую директорию.
Миграция и совместимость
- shopt -s nocasematch специфичен для Bash; при запуске в POSIX sh он недоступен. Если нужен перенос на dash/ash — избегайте этой опции и применяйте нормализацию через tr ‘[:upper:]’ ‘[:lower:]’.
1‑строчные определения (глоссарий)
- case: конструкция ветвления в Bash для сопоставления с шаблонами.
- клауза: одна ветка внутри case с шаблоном и набором команд.
- globbing: механизм подстановки файловых шаблонов (*, ?, []).
FAQ
Q: Можно ли использовать регулярные выражения в шаблонах case?
A: Нет. case использует shell globbing, а не полноценные регулярные выражения. Для регулярных выражений используйте [[ $var =~ regex ]].
Q: Как сделать сопоставление нечувствительным к регистру без shopt?
A: Нормализуйте ввод: month=$(echo “$month” | tr ‘[:upper:]’ ‘[:lower:]’) и используйте нижний регистр в шаблонах.
Q: Почему не работает case с пробелами в имени файла?
A: Это обычно из‑за использования $(ls) в for. Используйте for File in * или while IFS= read -r; do …; done.
Итог
Оператор case — удобный и выразительный инструмент для ветвления по набору ожидаемых значений. Используйте множественные шаблоны, шорткаты для расширений файлов и аккуратно обрабатывайте ввод. Применяйте чек‑листы перед деплоем и выбирайте if/elif, если нужны логические или числовые сравнения.